diff options
Diffstat (limited to '')
1312 files changed, 72215 insertions, 62678 deletions
diff --git a/.gitignore b/.gitignore index 3c8552099..42cf135f1 100644 --- a/.gitignore +++ b/.gitignore @@ -12,35 +12,29 @@ Makefile Makefile.in aclocal.m4 autom4te.cache -compile -config.guess config.h config.h.in config.log config.mk config.status -config.sub config_detected.h config_detected.mk configure configure.lineno -depcomp depmode -install-sh libtool ltmain.sh -missing mkinstalldirs -/test-driver -mpd -mpd.service +/build +/src/mpd +/systemd/mpd.service stamp-h1 tags *~ .#* .stgit* src/dsd2pcm/dsd2pcm -src/win/mpd_win32_rc.rc +src/win32/mpd_win32_rc.rc doc/doxygen.conf doc/protocol.html doc/protocol @@ -1,5 +1,5 @@ Music Player Daemon - http://www.musicpd.org -Copyright (C) 2003-2013 The Music Player Daemon Project +Copyright (C) 2003-2014 The Music Player Daemon Project The following people have contributed code to MPD: diff --git a/Makefile.am b/Makefile.am index c7cf631bb..ffa92e851 100644 --- a/Makefile.am +++ b/Makefile.am @@ -17,6 +17,7 @@ noinst_LIBRARIES = \ libtag.a \ libinput.a \ libfs.a \ + libneighbor.a \ libdb_plugins.a \ libplaylist_plugins.a \ libdecoder_plugins.a \ @@ -30,6 +31,7 @@ src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \ $(LIBWRAP_CFLAGS) \ $(SQLITE_CFLAGS) src_mpd_LDADD = \ + $(NEIGHBOR_LIBS) \ $(DB_LIBS) \ $(PLAYLIST_LIBS) \ $(AVAHI_LIBS) \ @@ -55,7 +57,6 @@ src_mpd_LDADD = \ mpd_headers = \ src/check.h \ src/gerror.h \ - src/TextInputStream.hxx \ src/AudioCompress/config.h \ src/AudioCompress/compress.h \ src/open.h \ @@ -82,6 +83,7 @@ src_mpd_SOURCES = \ src/command/CommandError.cxx src/command/CommandError.hxx \ src/command/AllCommands.cxx src/command/AllCommands.hxx \ src/command/QueueCommands.cxx src/command/QueueCommands.hxx \ + src/command/TagCommands.cxx src/command/TagCommands.hxx \ src/command/PlayerCommands.cxx src/command/PlayerCommands.hxx \ src/command/PlaylistCommands.cxx src/command/PlaylistCommands.hxx \ src/command/DatabaseCommands.cxx src/command/DatabaseCommands.hxx \ @@ -94,66 +96,72 @@ src_mpd_SOURCES = \ 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/decoder/DecoderError.cxx src/decoder/DecoderError.hxx \ + src/decoder/DecoderThread.cxx src/decoder/DecoderThread.hxx \ + src/decoder/DecoderCommand.hxx \ + src/decoder/DecoderControl.cxx src/decoder/DecoderControl.hxx \ + src/decoder/DecoderAPI.cxx src/decoder/DecoderAPI.hxx \ + src/decoder/DecoderPlugin.hxx \ + src/decoder/DecoderInternal.cxx src/decoder/DecoderInternal.hxx \ + src/decoder/DecoderPrint.cxx src/decoder/DecoderPrint.hxx \ + src/db/Directory.cxx src/db/Directory.hxx \ + src/db/DirectorySave.cxx src/db/DirectorySave.hxx \ + src/db/DatabaseSimple.hxx \ + src/db/DatabaseGlue.cxx src/db/DatabaseGlue.hxx \ + src/db/DatabaseSong.cxx src/db/DatabaseSong.hxx \ + src/db/DatabasePrint.cxx src/db/DatabasePrint.hxx \ + src/db/DatabaseQueue.cxx src/db/DatabaseQueue.hxx \ + src/db/DatabasePlaylist.cxx src/db/DatabasePlaylist.hxx \ + src/db/DatabaseError.cxx src/db/DatabaseError.hxx \ + src/db/DatabaseLock.cxx src/db/DatabaseLock.hxx \ + src/db/DatabaseSave.cxx src/db/DatabaseSave.hxx \ + src/db/DatabasePlugin.hxx \ + src/db/DatabaseListener.hxx \ + src/db/Visitor.hxx \ + src/db/Selection.cxx src/db/Selection.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/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/ClientFile.cxx src/ClientFile.hxx \ + src/filter/FilterConfig.cxx src/filter/FilterConfig.hxx \ + src/filter/FilterPlugin.cxx src/filter/FilterPlugin.hxx \ + src/filter/FilterInternal.hxx \ + src/filter/FilterRegistry.cxx src/filter/FilterRegistry.hxx \ + src/db/update/UpdateDomain.cxx src/db/update/UpdateDomain.hxx \ + src/db/update/UpdateGlue.cxx src/db/update/UpdateGlue.hxx \ + src/db/update/UpdateQueue.cxx src/db/update/UpdateQueue.hxx \ + src/db/update/UpdateIO.cxx src/db/update/UpdateIO.hxx \ + src/db/update/UpdateDatabase.cxx src/db/update/UpdateDatabase.hxx \ + src/db/update/UpdateWalk.cxx src/db/update/UpdateWalk.hxx \ + src/db/update/UpdateSong.cxx src/db/update/UpdateSong.hxx \ + src/db/update/UpdateContainer.cxx src/db/update/UpdateContainer.hxx \ + src/db/update/UpdateInternal.hxx \ + src/db/update/UpdateRemove.cxx src/db/update/UpdateRemove.hxx \ + src/client/Client.cxx src/client/Client.hxx \ + src/client/ClientInternal.hxx \ + src/client/ClientEvent.cxx \ + src/client/ClientExpire.cxx \ + src/client/ClientGlobal.cxx \ + src/client/ClientIdle.cxx \ + src/client/ClientList.cxx src/client/ClientList.hxx \ + src/client/ClientNew.cxx \ + src/client/ClientProcess.cxx \ + src/client/ClientRead.cxx \ + src/client/ClientWrite.cxx \ + src/client/ClientMessage.cxx src/client/ClientMessage.hxx \ + src/client/ClientSubscribe.cxx \ + src/client/ClientFile.cxx src/client/ClientFile.hxx \ src/Listen.cxx src/Listen.hxx \ src/LogInit.cxx src/LogInit.hxx \ + src/LogBackend.cxx src/LogBackend.hxx \ src/Log.cxx src/Log.hxx src/LogV.hxx \ + src/LogLevel.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/win32/Win32Main.cxx \ src/GlobalEvents.cxx src/GlobalEvents.hxx \ - src/Daemon.cxx src/Daemon.hxx \ + src/unix/SignalHandlers.cxx src/unix/SignalHandlers.hxx \ + src/unix/Daemon.cxx src/unix/Daemon.hxx \ + src/unix/PidFile.hxx \ src/AudioCompress/compress.c \ src/MixRampInfo.hxx \ src/MusicBuffer.cxx src/MusicBuffer.hxx \ @@ -170,81 +178,95 @@ src_mpd_SOURCES = \ src/PlaylistGlobal.cxx src/PlaylistGlobal.hxx \ src/PlaylistControl.cxx \ src/PlaylistEdit.cxx \ + src/PlaylistTag.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/playlist/PlaylistMapper.cxx src/playlist/PlaylistMapper.hxx \ + src/playlist/PlaylistAny.cxx src/playlist/PlaylistAny.hxx \ + src/playlist/PlaylistSong.cxx src/playlist/PlaylistSong.hxx \ src/PlaylistState.cxx src/PlaylistState.hxx \ - src/PlaylistQueue.cxx src/PlaylistQueue.hxx \ + src/playlist/PlaylistQueue.cxx src/playlist/PlaylistQueue.hxx \ + src/playlist/Print.cxx src/playlist/Print.hxx \ src/PlaylistVector.cxx src/PlaylistVector.hxx \ src/PlaylistInfo.hxx \ src/PlaylistDatabase.cxx src/PlaylistDatabase.hxx \ src/PlaylistUpdate.cxx \ - src/IdTable.hxx \ - src/Queue.cxx src/Queue.hxx \ - src/QueuePrint.cxx src/QueuePrint.hxx \ - src/QueueSave.cxx src/QueueSave.hxx \ + src/queue/IdTable.hxx \ + src/queue/Queue.cxx src/queue/Queue.hxx \ + src/queue/QueuePrint.cxx src/queue/QueuePrint.hxx \ + src/queue/QueueSave.cxx src/queue/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/DetachedSong.cxx src/DetachedSong.hxx \ + src/db/LightSong.cxx src/db/LightSong.hxx \ + src/db/LightDirectory.hxx \ + src/db/Song.cxx src/db/Song.hxx \ src/SongUpdate.cxx \ src/SongPrint.cxx src/SongPrint.hxx \ src/SongSave.cxx src/SongSave.hxx \ - src/SongSort.cxx src/SongSort.hxx \ + src/db/SongSort.cxx src/db/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/TagStream.cxx src/TagStream.hxx \ + src/mixer/Volume.cxx src/mixer/Volume.hxx \ src/SongFilter.cxx src/SongFilter.hxx \ - src/SongPointer.hxx \ src/PlaylistFile.cxx src/PlaylistFile.hxx \ src/Timer.cxx +UPNP_SOURCES = \ + src/lib/upnp/Init.cxx src/lib/upnp/Init.hxx \ + src/lib/upnp/ClientInit.cxx src/lib/upnp/ClientInit.hxx \ + src/lib/upnp/Device.cxx src/lib/upnp/Device.hxx \ + src/lib/upnp/ContentDirectoryService.cxx src/lib/upnp/ContentDirectoryService.hxx \ + src/lib/upnp/Discovery.cxx src/lib/upnp/Discovery.hxx \ + src/lib/upnp/Domain.cxx src/lib/upnp/Domain.hxx \ + src/lib/upnp/ixmlwrap.cxx src/lib/upnp/ixmlwrap.hxx \ + src/lib/upnp/Callback.hxx \ + src/lib/upnp/Util.cxx src/lib/upnp/Util.hxx \ + src/lib/upnp/WorkQueue.hxx \ + src/lib/upnp/Action.hxx + # # Windows resource file # -src/win/mpd_win32_rc.$(OBJEXT): src/win/mpd_win32_rc.rc +src/win32/mpd_win32_rc.$(OBJEXT): src/win32/mpd_win32_rc.rc $(WINDRES) -i $< -o $@ if HAVE_WINDOWS -noinst_DATA = src/win/mpd_win32_rc.rc +noinst_DATA = src/win32/mpd_win32_rc.rc -src_mpd_DEPENDENCIES = src/win/mpd_win32_rc.$(OBJEXT) -src_mpd_LDFLAGS = -Wl,src/win/mpd_win32_rc.$(OBJEXT) -endif - -if ENABLE_DESPOTIFY -src_mpd_SOURCES += \ - src/DespotifyUtils.cxx src/DespotifyUtils.hxx +src_mpd_DEPENDENCIES = src/win32/mpd_win32_rc.$(OBJEXT) +src_mpd_LDFLAGS = -Wl,src/win32/mpd_win32_rc.$(OBJEXT) endif if ENABLE_INOTIFY src_mpd_SOURCES += \ - src/InotifyDomain.cxx src/InotifyDomain.hxx \ - src/InotifySource.cxx src/InotifySource.hxx \ - src/InotifyQueue.cxx src/InotifyQueue.hxx \ - src/InotifyUpdate.cxx src/InotifyUpdate.hxx + src/db/update/InotifyDomain.cxx src/db/update/InotifyDomain.hxx \ + src/db/update/InotifySource.cxx src/db/update/InotifySource.hxx \ + src/db/update/InotifyQueue.cxx src/db/update/InotifyQueue.hxx \ + src/db/update/InotifyUpdate.cxx src/db/update/InotifyUpdate.hxx endif if ENABLE_SQLITE src_mpd_SOURCES += \ src/command/StickerCommands.cxx src/command/StickerCommands.hxx \ - src/StickerDatabase.cxx src/StickerDatabase.hxx \ - src/StickerPrint.cxx src/StickerPrint.hxx \ - src/SongSticker.cxx src/SongSticker.hxx + src/sticker/StickerDatabase.cxx src/sticker/StickerDatabase.hxx \ + src/sticker/StickerPrint.cxx src/sticker/StickerPrint.hxx \ + src/sticker/SongSticker.cxx src/sticker/SongSticker.hxx endif # Generic utility library libutil_a_SOURCES = \ src/util/Macros.hxx \ + src/util/Cast.hxx \ + src/util/Clamp.hxx \ + src/util/Alloc.cxx src/util/Alloc.hxx \ + src/util/VarSize.hxx \ src/util/Error.cxx src/util/Error.hxx \ src/util/Domain.hxx \ src/util/ReusableArray.hxx \ @@ -252,19 +274,22 @@ libutil_a_SOURCES = \ src/util/CharUtil.hxx \ src/util/NumberParser.hxx \ src/util/StringUtil.cxx src/util/StringUtil.hxx \ + src/util/SplitString.cxx src/util/SplitString.hxx \ src/util/FormatString.cxx src/util/FormatString.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/FifoBuffer.hxx \ + src/util/DynamicFifoBuffer.hxx \ + src/util/ConstBuffer.hxx \ src/util/WritableBuffer.hxx \ - 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/OptionParser.cxx src/util/OptionParser.hxx \ + src/util/OptionDef.hxx \ src/util/list.h \ src/util/list_sort.c src/util/list_sort.h \ src/util/ByteReverse.cxx src/util/ByteReverse.hxx \ @@ -273,6 +298,8 @@ libutil_a_SOURCES = \ # Multi-threading library libthread_a_SOURCES = \ + src/thread/Util.hxx \ + src/thread/Name.hxx \ src/thread/Mutex.hxx \ src/thread/PosixMutex.hxx \ src/thread/CriticalSection.hxx \ @@ -297,12 +324,18 @@ libsystem_a_SOURCES = \ 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/PeriodClock.hxx \ src/system/Clock.cxx src/system/Clock.hxx # Event loop library libevent_a_SOURCES = \ src/event/WakeFD.hxx \ + src/event/PollGroup.hxx \ + src/event/PollGroupEPoll.hxx \ + src/event/PollGroupPoll.hxx src/event/PollGroupPoll.cxx \ + src/event/PollGroupWinSelect.hxx src/event/PollGroupWinSelect.cxx \ + src/event/PollResultGeneric.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 \ @@ -318,32 +351,45 @@ libevent_a_SOURCES = \ # PCM library libpcm_a_SOURCES = \ + src/pcm/Domain.cxx src/pcm/Domain.hxx \ + src/pcm/Traits.hxx \ 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/Volume.cxx src/pcm/Volume.hxx \ src/pcm/PcmMix.cxx src/pcm/PcmMix.hxx \ src/pcm/PcmChannels.cxx src/pcm/PcmChannels.hxx \ src/pcm/PcmPack.cxx src/pcm/PcmPack.hxx \ 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/FormatConverter.cxx src/pcm/FormatConverter.hxx \ + src/pcm/ChannelsConverter.cxx src/pcm/ChannelsConverter.hxx \ + src/pcm/Resampler.hxx \ + src/pcm/GlueResampler.cxx src/pcm/GlueResampler.hxx \ + src/pcm/FallbackResampler.cxx src/pcm/FallbackResampler.hxx \ + src/pcm/ConfiguredResampler.cxx src/pcm/ConfiguredResampler.hxx \ src/pcm/PcmDither.cxx src/pcm/PcmDither.hxx \ src/pcm/PcmPrng.hxx \ src/pcm/PcmUtils.hxx libpcm_a_CPPFLAGS = $(AM_CPPFLAGS) \ + $(SOXR_CFLAGS) \ $(SAMPLERATE_CFLAGS) PCM_LIBS = \ libpcm.a \ + $(SOXR_LIBS) \ $(SAMPLERATE_LIBS) if HAVE_LIBSAMPLERATE -libpcm_a_SOURCES += src/pcm/PcmResampleLibsamplerate.cxx +libpcm_a_SOURCES += \ + src/pcm/LibsamplerateResampler.cxx src/pcm/LibsamplerateResampler.hxx +endif + +if HAVE_SOXR +libpcm_a_SOURCES += \ + src/pcm/SoxrResampler.cxx src/pcm/SoxrResampler.hxx endif # File system library @@ -356,25 +402,81 @@ libfs_a_SOURCES = \ src/fs/Charset.cxx src/fs/Charset.hxx \ src/fs/Path.cxx src/fs/Path.hxx \ src/fs/AllocatedPath.cxx src/fs/AllocatedPath.hxx \ + src/fs/TextFile.cxx src/fs/TextFile.hxx \ src/fs/FileSystem.cxx src/fs/FileSystem.hxx \ + src/fs/StandardDirectory.cxx src/fs/StandardDirectory.hxx \ src/fs/DirectoryReader.hxx +# neighbor plugins + +if ENABLE_NEIGHBOR_PLUGINS + +src_mpd_SOURCES += \ + src/command/NeighborCommands.cxx \ + src/command/NeighborCommands.hxx + +libneighbor_a_SOURCES = \ + src/neighbor/Registry.cxx src/neighbor/Registry.hxx \ + src/neighbor/Glue.cxx src/neighbor/Glue.hxx \ + src/neighbor/Info.hxx \ + src/neighbor/Listener.hxx \ + src/neighbor/Explorer.hxx \ + src/neighbor/NeighborPlugin.hxx + +libneighbor_a_CPPFLAGS = $(AM_CPPFLAGS) \ + $(SMBCLIENT_CFLAGS) + +if ENABLE_SMBCLIENT +libneighbor_a_SOURCES += \ + src/lib/smbclient/Init.cxx src/lib/smbclient/Init.hxx \ + src/neighbor/plugins/SmbclientNeighborPlugin.cxx src/neighbor/plugins/SmbclientNeighborPlugin.hxx +endif + +NEIGHBOR_LIBS = \ + $(SMBCLIENT_LIBS) \ + libneighbor.a + +if HAVE_LIBUPNP +libneighbor_a_SOURCES += \ + $(UPNP_SOURCES) \ + src/neighbor/plugins/UpnpNeighborPlugin.cxx src/neighbor/plugins/UpnpNeighborPlugin.hxx +NEIGHBOR_LIBS += \ + $(EXPAT_LIBS) \ + $(UPNP_LIBS) +endif + +endif + # 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 + src/db/Registry.cxx src/db/Registry.hxx \ + src/db/Helpers.cxx src/db/Helpers.hxx \ + src/db/plugins/LazyDatabase.cxx src/db/plugins/LazyDatabase.hxx \ + src/db/plugins/SimpleDatabasePlugin.cxx src/db/plugins/SimpleDatabasePlugin.hxx if HAVE_LIBMPDCLIENT libdb_plugins_a_SOURCES += \ - src/db/ProxyDatabasePlugin.cxx src/db/ProxyDatabasePlugin.hxx + src/db/plugins/ProxyDatabasePlugin.cxx src/db/plugins/ProxyDatabasePlugin.hxx endif DB_LIBS = \ libdb_plugins.a \ $(LIBMPDCLIENT_LIBS) +if HAVE_LIBUPNP +libdb_plugins_a_SOURCES += \ + $(UPNP_SOURCES) \ + src/db/plugins/UpnpDatabasePlugin.cxx src/db/plugins/UpnpDatabasePlugin.hxx \ + src/db/plugins/upnp/Tags.cxx src/db/plugins/upnp/Tags.hxx \ + src/db/plugins/upnp/ContentDirectoryService.cxx \ + src/db/plugins/upnp/Directory.cxx src/db/plugins/upnp/Directory.hxx \ + src/db/plugins/upnp/Object.cxx src/db/plugins/upnp/Object.hxx +DB_LIBS += \ + $(EXPAT_LIBS) \ + $(UPNP_LIBS) +endif + # archive plugins if ENABLE_ARCHIVE @@ -382,16 +484,16 @@ if ENABLE_ARCHIVE noinst_LIBRARIES += libarchive.a src_mpd_SOURCES += \ - src/UpdateArchive.cxx src/UpdateArchive.hxx + src/db/update/UpdateArchive.cxx src/db/update/UpdateArchive.hxx libarchive_a_SOURCES = \ - src/ArchiveDomain.cxx src/ArchiveDomain.hxx \ - 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 + src/archive/ArchiveDomain.cxx src/archive/ArchiveDomain.hxx \ + src/archive/ArchiveLookup.cxx src/archive/ArchiveLookup.hxx \ + src/archive/ArchiveList.cxx src/archive/ArchiveList.hxx \ + src/archive/ArchivePlugin.cxx src/archive/ArchivePlugin.hxx \ + src/archive/ArchiveVisitor.hxx \ + src/archive/ArchiveFile.hxx \ + src/input/plugins/ArchiveInputPlugin.cxx src/input/plugins/ArchiveInputPlugin.hxx libarchive_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(BZ2_CFLAGS) \ $(ISO9660_CFLAGS) \ @@ -405,20 +507,20 @@ ARCHIVE_LIBS = \ if HAVE_BZ2 libarchive_a_SOURCES += \ - src/archive/Bzip2ArchivePlugin.cxx \ - src/archive/Bzip2ArchivePlugin.hxx + src/archive/plugins/Bzip2ArchivePlugin.cxx \ + src/archive/plugins/Bzip2ArchivePlugin.hxx endif if HAVE_ZZIP libarchive_a_SOURCES += \ - src/archive/ZzipArchivePlugin.cxx \ - src/archive/ZzipArchivePlugin.hxx + src/archive/plugins/ZzipArchivePlugin.cxx \ + src/archive/plugins/ZzipArchivePlugin.hxx endif if HAVE_ISO9660 libarchive_a_SOURCES += \ - src/archive/Iso9660ArchivePlugin.cxx \ - src/archive/Iso9660ArchivePlugin.hxx + src/archive/plugins/Iso9660ArchivePlugin.cxx \ + src/archive/plugins/Iso9660ArchivePlugin.hxx endif else @@ -428,15 +530,15 @@ 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 + src/config/ConfigDefaults.hxx \ + src/config/ConfigPath.cxx src/config/ConfigPath.hxx \ + src/config/ConfigData.cxx src/config/ConfigData.hxx \ + src/config/ConfigParser.cxx src/config/ConfigParser.hxx \ + src/config/ConfigGlobal.cxx src/config/ConfigGlobal.hxx \ + src/config/ConfigFile.cxx src/config/ConfigFile.hxx \ + src/config/ConfigTemplates.cxx src/config/ConfigTemplates.hxx \ + src/config/ConfigError.cxx src/config/ConfigError.hxx \ + src/config/ConfigOption.hxx # tag plugins @@ -473,17 +575,17 @@ endif # decoder plugins libdecoder_plugins_a_SOURCES = \ - 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 + src/decoder/plugins/PcmDecoderPlugin.cxx \ + src/decoder/plugins/PcmDecoderPlugin.hxx \ + src/decoder/plugins/DsdiffDecoderPlugin.cxx \ + src/decoder/plugins/DsdiffDecoderPlugin.hxx \ + src/decoder/plugins/DsfDecoderPlugin.cxx \ + src/decoder/plugins/DsfDecoderPlugin.hxx \ + src/decoder/plugins/DsdLib.cxx \ + src/decoder/plugins/DsdLib.hxx \ + src/decoder/DecoderBuffer.cxx src/decoder/DecoderBuffer.hxx \ + src/decoder/DecoderPlugin.cxx \ + src/decoder/DecoderList.cxx src/decoder/DecoderList.hxx libdecoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(VORBIS_CFLAGS) $(TREMOR_CFLAGS) \ $(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \ @@ -526,96 +628,96 @@ DECODER_SRC = if HAVE_MAD libdecoder_plugins_a_SOURCES += \ - src/decoder/MadDecoderPlugin.cxx \ - src/decoder/MadDecoderPlugin.hxx + src/decoder/plugins/MadDecoderPlugin.cxx \ + src/decoder/plugins/MadDecoderPlugin.hxx endif if HAVE_MPG123 libdecoder_plugins_a_SOURCES += \ - src/decoder/Mpg123DecoderPlugin.cxx \ - src/decoder/Mpg123DecoderPlugin.hxx + src/decoder/plugins/Mpg123DecoderPlugin.cxx \ + src/decoder/plugins/Mpg123DecoderPlugin.hxx endif if HAVE_MPCDEC libdecoder_plugins_a_SOURCES += \ - src/decoder/MpcdecDecoderPlugin.cxx \ - src/decoder/MpcdecDecoderPlugin.hxx + src/decoder/plugins/MpcdecDecoderPlugin.cxx \ + src/decoder/plugins/MpcdecDecoderPlugin.hxx endif 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 + src/decoder/plugins/OggUtil.cxx \ + src/decoder/plugins/OggUtil.hxx \ + src/decoder/plugins/OggSyncState.hxx \ + src/decoder/plugins/OggFind.cxx src/decoder/plugins/OggFind.hxx \ + src/decoder/plugins/OpusDomain.cxx src/decoder/plugins/OpusDomain.hxx \ + src/decoder/plugins/OpusReader.hxx \ + src/decoder/plugins/OpusHead.hxx \ + src/decoder/plugins/OpusHead.cxx \ + src/decoder/plugins/OpusTags.cxx \ + src/decoder/plugins/OpusTags.hxx \ + src/decoder/plugins/OpusDecoderPlugin.cxx \ + src/decoder/plugins/OpusDecoderPlugin.h endif if HAVE_WAVPACK libdecoder_plugins_a_SOURCES += \ - src/decoder/WavpackDecoderPlugin.cxx \ - src/decoder/WavpackDecoderPlugin.hxx + src/decoder/plugins/WavpackDecoderPlugin.cxx \ + src/decoder/plugins/WavpackDecoderPlugin.hxx endif if HAVE_ADPLUG libdecoder_plugins_a_SOURCES += \ - src/decoder/AdPlugDecoderPlugin.cxx \ - src/decoder/AdPlugDecoderPlugin.h + src/decoder/plugins/AdPlugDecoderPlugin.cxx \ + src/decoder/plugins/AdPlugDecoderPlugin.h endif if HAVE_FAAD libdecoder_plugins_a_SOURCES += \ - src/decoder/FaadDecoderPlugin.cxx src/decoder/FaadDecoderPlugin.hxx + src/decoder/plugins/FaadDecoderPlugin.cxx src/decoder/plugins/FaadDecoderPlugin.hxx endif if HAVE_XIPH libdecoder_plugins_a_SOURCES += \ - src/decoder/XiphTags.cxx src/decoder/XiphTags.hxx \ - src/decoder/OggCodec.cxx src/decoder/OggCodec.hxx + src/decoder/plugins/XiphTags.cxx src/decoder/plugins/XiphTags.hxx \ + src/decoder/plugins/OggCodec.cxx src/decoder/plugins/OggCodec.hxx endif if ENABLE_VORBIS_DECODER libdecoder_plugins_a_SOURCES += \ - src/decoder/VorbisDomain.cxx src/decoder/VorbisDomain.hxx \ - src/decoder/VorbisComments.cxx src/decoder/VorbisComments.hxx \ - src/decoder/VorbisDecoderPlugin.cxx src/decoder/VorbisDecoderPlugin.h + src/decoder/plugins/VorbisDomain.cxx src/decoder/plugins/VorbisDomain.hxx \ + src/decoder/plugins/VorbisComments.cxx src/decoder/plugins/VorbisComments.hxx \ + src/decoder/plugins/VorbisDecoderPlugin.cxx src/decoder/plugins/VorbisDecoderPlugin.h endif if HAVE_FLAC 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 + src/decoder/plugins/FlacInput.cxx src/decoder/plugins/FlacInput.hxx \ + src/decoder/plugins/FlacIOHandle.cxx src/decoder/plugins/FlacIOHandle.hxx \ + src/decoder/plugins/FlacMetadata.cxx src/decoder/plugins/FlacMetadata.hxx \ + src/decoder/plugins/FlacPcm.cxx src/decoder/plugins/FlacPcm.hxx \ + src/decoder/plugins/FlacDomain.cxx src/decoder/plugins/FlacDomain.hxx \ + src/decoder/plugins/FlacCommon.cxx src/decoder/plugins/FlacCommon.hxx \ + src/decoder/plugins/FlacDecoderPlugin.cxx \ + src/decoder/plugins/FlacDecoderPlugin.h endif if HAVE_AUDIOFILE libdecoder_plugins_a_SOURCES += \ - src/decoder/AudiofileDecoderPlugin.cxx \ - src/decoder/AudiofileDecoderPlugin.hxx + src/decoder/plugins/AudiofileDecoderPlugin.cxx \ + src/decoder/plugins/AudiofileDecoderPlugin.hxx endif if ENABLE_MIKMOD_DECODER libdecoder_plugins_a_SOURCES += \ - src/decoder/MikmodDecoderPlugin.cxx \ - src/decoder/MikmodDecoderPlugin.hxx + src/decoder/plugins/MikmodDecoderPlugin.cxx \ + src/decoder/plugins/MikmodDecoderPlugin.hxx endif if HAVE_MODPLUG libmodplug_decoder_plugin_a_SOURCES = \ - src/decoder/ModplugDecoderPlugin.cxx \ - src/decoder/ModplugDecoderPlugin.hxx + src/decoder/plugins/ModplugDecoderPlugin.cxx \ + src/decoder/plugins/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 @@ -624,39 +726,39 @@ endif if ENABLE_SIDPLAY libdecoder_plugins_a_SOURCES += \ - src/decoder/SidplayDecoderPlugin.cxx \ - src/decoder/SidplayDecoderPlugin.hxx + src/decoder/plugins/SidplayDecoderPlugin.cxx \ + src/decoder/plugins/SidplayDecoderPlugin.hxx endif if ENABLE_FLUIDSYNTH libdecoder_plugins_a_SOURCES += \ - src/decoder/FluidsynthDecoderPlugin.cxx \ - src/decoder/FluidsynthDecoderPlugin.hxx + src/decoder/plugins/FluidsynthDecoderPlugin.cxx \ + src/decoder/plugins/FluidsynthDecoderPlugin.hxx endif if ENABLE_WILDMIDI libdecoder_plugins_a_SOURCES += \ - src/decoder/WildmidiDecoderPlugin.cxx \ - src/decoder/WildmidiDecoderPlugin.hxx + src/decoder/plugins/WildmidiDecoderPlugin.cxx \ + src/decoder/plugins/WildmidiDecoderPlugin.hxx endif if HAVE_FFMPEG libdecoder_plugins_a_SOURCES += \ - src/decoder/FfmpegMetaData.cxx \ - src/decoder/FfmpegMetaData.hxx \ - src/decoder/FfmpegDecoderPlugin.cxx \ - src/decoder/FfmpegDecoderPlugin.hxx + src/decoder/plugins/FfmpegMetaData.cxx \ + src/decoder/plugins/FfmpegMetaData.hxx \ + src/decoder/plugins/FfmpegDecoderPlugin.cxx \ + src/decoder/plugins/FfmpegDecoderPlugin.hxx endif if ENABLE_SNDFILE libdecoder_plugins_a_SOURCES += \ - src/decoder/SndfileDecoderPlugin.cxx \ - src/decoder/SndfileDecoderPlugin.hxx + src/decoder/plugins/SndfileDecoderPlugin.cxx \ + src/decoder/plugins/SndfileDecoderPlugin.hxx endif if HAVE_GME libdecoder_plugins_a_SOURCES += \ - src/decoder/GmeDecoderPlugin.cxx src/decoder/GmeDecoderPlugin.hxx + src/decoder/plugins/GmeDecoderPlugin.cxx src/decoder/plugins/GmeDecoderPlugin.hxx endif # encoder plugins @@ -670,6 +772,7 @@ libencoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(TWOLAME_CFLAGS) \ $(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \ $(OPUS_CFLAGS) \ + $(SHINE_CFLAGS) \ $(VORBISENC_CFLAGS) ENCODER_LIBS = \ @@ -678,54 +781,64 @@ ENCODER_LIBS = \ $(TWOLAME_LIBS) \ $(FLAC_LIBS) \ $(OPUS_LIBS) \ + $(SHINE_LIBS) \ $(VORBISENC_LIBS) 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 + src/encoder/EncoderAPI.hxx \ + src/encoder/EncoderPlugin.hxx \ + src/encoder/plugins/OggStream.hxx \ + src/encoder/plugins/NullEncoderPlugin.cxx \ + src/encoder/plugins/NullEncoderPlugin.hxx \ + src/encoder/EncoderList.cxx src/encoder/EncoderList.hxx if HAVE_OGG_ENCODER libencoder_plugins_a_SOURCES += \ - src/encoder/OggSerial.cxx src/encoder/OggSerial.hxx \ - src/encoder/OggStream.hxx + src/encoder/plugins/OggSerial.cxx \ + src/encoder/plugins/OggSerial.hxx \ + src/encoder/plugins/OggStream.hxx endif if ENABLE_WAVE_ENCODER libencoder_plugins_a_SOURCES += \ - src/encoder/WaveEncoderPlugin.cxx \ - src/encoder/WaveEncoderPlugin.hxx + src/encoder/plugins/WaveEncoderPlugin.cxx \ + src/encoder/plugins/WaveEncoderPlugin.hxx endif if ENABLE_VORBIS_ENCODER libencoder_plugins_a_SOURCES += \ - src/encoder/VorbisEncoderPlugin.cxx \ - src/encoder/VorbisEncoderPlugin.hxx + src/encoder/plugins/VorbisEncoderPlugin.cxx \ + src/encoder/plugins/VorbisEncoderPlugin.hxx endif if HAVE_OPUS libencoder_plugins_a_SOURCES += \ - src/encoder/OpusEncoderPlugin.cxx \ - src/encoder/OpusEncoderPlugin.hxx + src/encoder/plugins/OpusEncoderPlugin.cxx \ + src/encoder/plugins/OpusEncoderPlugin.hxx endif if ENABLE_LAME_ENCODER libencoder_plugins_a_SOURCES += \ - src/encoder/LameEncoderPlugin.cxx \ - src/encoder/LameEncoderPlugin.hxx + src/encoder/plugins/LameEncoderPlugin.cxx \ + src/encoder/plugins/LameEncoderPlugin.hxx endif if ENABLE_TWOLAME_ENCODER libencoder_plugins_a_SOURCES += \ - src/encoder/TwolameEncoderPlugin.cxx \ - src/encoder/TwolameEncoderPlugin.hxx + src/encoder/plugins/TwolameEncoderPlugin.cxx \ + src/encoder/plugins/TwolameEncoderPlugin.hxx endif if ENABLE_FLAC_ENCODER libencoder_plugins_a_SOURCES += \ - src/encoder/FlacEncoderPlugin.cxx src/encoder/FlacEncoderPlugin.hxx + src/encoder/plugins/FlacEncoderPlugin.cxx \ + src/encoder/plugins/FlacEncoderPlugin.hxx +endif + +if ENABLE_SHINE_ENCODER +libencoder_plugins_a_SOURCES += \ + src/encoder/plugins/ShineEncoderPlugin.cxx \ + src/encoder/plugins/ShineEncoderPlugin.hxx endif else @@ -735,17 +848,17 @@ endif if HAVE_ZEROCONF src_mpd_SOURCES += \ - src/ZeroconfInternal.hxx \ - src/ZeroconfGlue.cxx src/ZeroconfGlue.hxx + src/zeroconf/ZeroconfInternal.hxx \ + src/zeroconf/ZeroconfGlue.cxx src/zeroconf/ZeroconfGlue.hxx if HAVE_AVAHI src_mpd_SOURCES += \ - src/AvahiPoll.cxx src/AvahiPoll.hxx \ - src/ZeroconfAvahi.cxx src/ZeroconfAvahi.hxx + src/zeroconf/AvahiPoll.cxx src/zeroconf/AvahiPoll.hxx \ + src/zeroconf/ZeroconfAvahi.cxx src/zeroconf/ZeroconfAvahi.hxx endif if HAVE_BONJOUR -src_mpd_SOURCES += src/ZeroconfBonjour.cxx src/ZeroconfBonjour.hxx +src_mpd_SOURCES += src/zeroconf/ZeroconfBonjour.cxx src/zeroconf/ZeroconfBonjour.hxx endif endif @@ -754,15 +867,17 @@ endif # libinput_a_SOURCES = \ - src/InputInit.cxx src/InputInit.hxx \ - src/InputRegistry.cxx src/InputRegistry.hxx \ - src/InputStream.cxx src/InputStream.hxx \ - src/InputPlugin.hxx \ - src/input/RewindInputPlugin.cxx src/input/RewindInputPlugin.hxx \ - src/input/FileInputPlugin.cxx src/input/FileInputPlugin.hxx + src/input/Init.cxx src/input/Init.hxx \ + src/input/Registry.cxx src/input/Registry.hxx \ + src/input/InputStream.cxx src/input/InputStream.hxx \ + src/input/InputPlugin.hxx \ + src/input/TextInputStream.cxx src/input/TextInputStream.hxx \ + src/input/plugins/RewindInputPlugin.cxx src/input/plugins/RewindInputPlugin.hxx \ + src/input/plugins/FileInputPlugin.cxx src/input/plugins/FileInputPlugin.hxx libinput_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(CURL_CFLAGS) \ + $(SMBCLIENT_CFLAGS) \ $(CDIO_PARANOIA_CFLAGS) \ $(FFMPEG_CFLAGS) \ $(DESPOTIFY_CFLAGS) \ @@ -771,37 +886,54 @@ libinput_a_CPPFLAGS = $(AM_CPPFLAGS) \ INPUT_LIBS = \ libinput.a \ $(CURL_LIBS) \ + $(SMBCLIENT_LIBS) \ $(CDIO_PARANOIA_LIBS) \ $(FFMPEG_LIBS) \ $(DESPOTIFY_LIBS) \ $(MMS_LIBS) +if HAVE_ALSA +libinput_a_SOURCES += \ + src/input/plugins/AlsaInputPlugin.cxx \ + src/input/plugins/AlsaInputPlugin.hxx +INPUT_LIBS += $(ALSA_LIBS) +endif + + if ENABLE_CURL libinput_a_SOURCES += \ - src/input/CurlInputPlugin.cxx src/input/CurlInputPlugin.hxx \ + src/input/plugins/CurlInputPlugin.cxx src/input/plugins/CurlInputPlugin.hxx \ src/IcyMetaDataParser.cxx src/IcyMetaDataParser.hxx endif +if ENABLE_SMBCLIENT +libinput_a_SOURCES += \ + src/lib/smbclient/Init.cxx src/lib/smbclient/Init.hxx \ + src/input/plugins/SmbclientInputPlugin.cxx src/input/plugins/SmbclientInputPlugin.hxx +endif + if ENABLE_CDIO_PARANOIA libinput_a_SOURCES += \ - src/input/CdioParanoiaInputPlugin.cxx \ - src/input/CdioParanoiaInputPlugin.hxx + src/input/plugins/CdioParanoiaInputPlugin.cxx \ + src/input/plugins/CdioParanoiaInputPlugin.hxx endif if HAVE_FFMPEG libinput_a_SOURCES += \ - src/input/FfmpegInputPlugin.cxx src/input/FfmpegInputPlugin.hxx + src/input/plugins/FfmpegInputPlugin.cxx src/input/plugins/FfmpegInputPlugin.hxx endif if ENABLE_MMS libinput_a_SOURCES += \ - src/input/MmsInputPlugin.cxx src/input/MmsInputPlugin.hxx + src/input/plugins/MmsInputPlugin.cxx src/input/plugins/MmsInputPlugin.hxx endif if ENABLE_DESPOTIFY libinput_a_SOURCES += \ - src/input/DespotifyInputPlugin.cxx \ - src/input/DespotifyInputPlugin.hxx + src/lib/despotify/DespotifyUtils.cxx \ + src/lib/despotify/DespotifyUtils.hxx \ + src/input/plugins/DespotifyInputPlugin.cxx \ + src/input/plugins/DespotifyInputPlugin.hxx endif @@ -826,128 +958,140 @@ OUTPUT_LIBS = \ $(SHOUT_LIBS) OUTPUT_API_SRC = \ - 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 + src/output/OutputAPI.hxx \ + src/output/OutputInternal.hxx \ + src/output/OutputList.cxx src/output/OutputList.hxx \ + src/output/OutputAll.cxx src/output/OutputAll.hxx \ + src/output/OutputThread.cxx src/output/OutputThread.hxx \ + src/output/OutputError.cxx src/output/OutputError.hxx \ + src/output/OutputControl.cxx src/output/OutputControl.hxx \ + src/output/OutputState.cxx src/output/OutputState.hxx \ + src/output/OutputPrint.cxx src/output/OutputPrint.hxx \ + src/output/OutputCommand.cxx src/output/OutputCommand.hxx \ + src/output/OutputPlugin.cxx src/output/OutputPlugin.hxx \ + src/output/OutputFinish.cxx \ + src/output/OutputInit.cxx liboutput_plugins_a_SOURCES = \ - src/output/NullOutputPlugin.cxx \ - src/output/NullOutputPlugin.hxx + src/output/plugins/NullOutputPlugin.cxx \ + src/output/plugins/NullOutputPlugin.hxx MIXER_LIBS = \ libmixer_plugins.a \ $(PULSE_LIBS) MIXER_API_SRC = \ - 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 + src/mixer/MixerPlugin.hxx \ + src/mixer/MixerList.hxx \ + src/mixer/MixerControl.cxx src/mixer/MixerControl.hxx \ + src/mixer/MixerType.cxx src/mixer/MixerType.hxx \ + src/mixer/MixerAll.cxx src/mixer/MixerAll.hxx \ + src/mixer/MixerInternal.hxx libmixer_plugins_a_SOURCES = \ - src/mixer/SoftwareMixerPlugin.cxx \ - src/mixer/SoftwareMixerPlugin.hxx + src/mixer/plugins/SoftwareMixerPlugin.cxx \ + src/mixer/plugins/SoftwareMixerPlugin.hxx libmixer_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(ALSA_CFLAGS) \ $(PULSE_CFLAGS) if HAVE_ALSA liboutput_plugins_a_SOURCES += \ - src/output/AlsaOutputPlugin.cxx \ - src/output/AlsaOutputPlugin.hxx -libmixer_plugins_a_SOURCES += src/mixer/AlsaMixerPlugin.cxx + src/output/plugins/AlsaOutputPlugin.cxx \ + src/output/plugins/AlsaOutputPlugin.hxx +libmixer_plugins_a_SOURCES += src/mixer/plugins/AlsaMixerPlugin.cxx endif if HAVE_ROAR liboutput_plugins_a_SOURCES += \ - src/output/RoarOutputPlugin.cxx src/output/RoarOutputPlugin.hxx -libmixer_plugins_a_SOURCES += src/mixer/RoarMixerPlugin.cxx + src/output/plugins/RoarOutputPlugin.cxx \ + src/output/plugins/RoarOutputPlugin.hxx +libmixer_plugins_a_SOURCES += src/mixer/plugins/RoarMixerPlugin.cxx endif if HAVE_AO liboutput_plugins_a_SOURCES += \ - src/output/AoOutputPlugin.cxx src/output/AoOutputPlugin.hxx + src/output/plugins/AoOutputPlugin.cxx \ + src/output/plugins/AoOutputPlugin.hxx endif if HAVE_FIFO liboutput_plugins_a_SOURCES += \ - src/output/FifoOutputPlugin.cxx src/output/FifoOutputPlugin.hxx + src/output/plugins/FifoOutputPlugin.cxx \ + src/output/plugins/FifoOutputPlugin.hxx endif if ENABLE_PIPE_OUTPUT liboutput_plugins_a_SOURCES += \ - src/output/PipeOutputPlugin.cxx src/output/PipeOutputPlugin.hxx + src/output/plugins/PipeOutputPlugin.cxx \ + src/output/plugins/PipeOutputPlugin.hxx endif if HAVE_JACK liboutput_plugins_a_SOURCES += \ - src/output/JackOutputPlugin.cxx src/output/JackOutputPlugin.hxx + src/output/plugins/JackOutputPlugin.cxx \ + src/output/plugins/JackOutputPlugin.hxx endif if HAVE_OSS liboutput_plugins_a_SOURCES += \ - src/output/OssOutputPlugin.cxx \ - src/output/OssOutputPlugin.hxx -libmixer_plugins_a_SOURCES += src/mixer/OssMixerPlugin.cxx + src/output/plugins/OssOutputPlugin.cxx \ + src/output/plugins/OssOutputPlugin.hxx +libmixer_plugins_a_SOURCES += src/mixer/plugins/OssMixerPlugin.cxx endif if HAVE_OPENAL liboutput_plugins_a_SOURCES += \ - src/output/OpenALOutputPlugin.cxx src/output/OpenALOutputPlugin.hxx + src/output/plugins/OpenALOutputPlugin.cxx \ + src/output/plugins/OpenALOutputPlugin.hxx endif if HAVE_OSX liboutput_plugins_a_SOURCES += \ - src/output/OSXOutputPlugin.cxx \ - src/output/OSXOutputPlugin.hxx + src/output/plugins/OSXOutputPlugin.cxx \ + src/output/plugins/OSXOutputPlugin.hxx endif if HAVE_PULSE liboutput_plugins_a_SOURCES += \ - src/output/PulseOutputPlugin.cxx src/output/PulseOutputPlugin.hxx + src/output/plugins/PulseOutputPlugin.cxx \ + src/output/plugins/PulseOutputPlugin.hxx libmixer_plugins_a_SOURCES += \ - src/mixer/PulseMixerPlugin.cxx src/mixer/PulseMixerPlugin.hxx + src/mixer/plugins/PulseMixerPlugin.cxx src/mixer/plugins/PulseMixerPlugin.hxx endif if HAVE_SHOUT liboutput_plugins_a_SOURCES += \ - src/output/ShoutOutputPlugin.cxx src/output/ShoutOutputPlugin.hxx + src/output/plugins/ShoutOutputPlugin.cxx \ + src/output/plugins/ShoutOutputPlugin.hxx endif if ENABLE_RECORDER_OUTPUT liboutput_plugins_a_SOURCES += \ - src/output/RecorderOutputPlugin.cxx src/output/RecorderOutputPlugin.hxx + src/output/plugins/RecorderOutputPlugin.cxx \ + src/output/plugins/RecorderOutputPlugin.hxx endif if ENABLE_HTTPD_OUTPUT liboutput_plugins_a_SOURCES += \ 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 + src/output/plugins/HttpdInternal.hxx \ + src/output/plugins/HttpdClient.cxx \ + src/output/plugins/HttpdClient.hxx \ + src/output/plugins/HttpdOutputPlugin.cxx \ + src/output/plugins/HttpdOutputPlugin.hxx endif if ENABLE_SOLARIS_OUTPUT liboutput_plugins_a_SOURCES += \ - src/output/SolarisOutputPlugin.cxx src/output/SolarisOutputPlugin.hxx + src/output/plugins/SolarisOutputPlugin.cxx src/output/plugins/SolarisOutputPlugin.hxx endif if ENABLE_WINMM_OUTPUT liboutput_plugins_a_SOURCES += \ - src/output/WinmmOutputPlugin.cxx src/output/WinmmOutputPlugin.hxx -libmixer_plugins_a_SOURCES += src/mixer/WinmmMixerPlugin.cxx + src/output/plugins/WinmmOutputPlugin.cxx \ + src/output/plugins/WinmmOutputPlugin.hxx +libmixer_plugins_a_SOURCES += src/mixer/plugins/WinmmMixerPlugin.cxx endif @@ -956,65 +1100,79 @@ endif # libplaylist_plugins_a_SOURCES = \ - 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 + src/playlist/PlaylistPlugin.hxx \ + src/playlist/SongEnumerator.hxx \ + src/playlist/MemorySongEnumerator.cxx \ + src/playlist/MemorySongEnumerator.hxx \ + src/playlist/plugins/ExtM3uPlaylistPlugin.cxx \ + src/playlist/plugins/ExtM3uPlaylistPlugin.hxx \ + src/playlist/plugins/M3uPlaylistPlugin.cxx \ + src/playlist/plugins/M3uPlaylistPlugin.hxx \ + src/playlist/plugins/CuePlaylistPlugin.cxx \ + src/playlist/plugins/CuePlaylistPlugin.hxx \ + src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx \ + src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx \ + src/playlist/PlaylistRegistry.cxx src/playlist/PlaylistRegistry.hxx libplaylist_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ + $(EXPAT_CFLAGS) \ $(YAJL_CFLAGS) \ $(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) PLAYLIST_LIBS = \ libplaylist_plugins.a \ + $(EXPAT_LIBS) \ $(FLAC_LIBS) if ENABLE_DESPOTIFY libplaylist_plugins_a_SOURCES += \ - src/playlist/DespotifyPlaylistPlugin.cxx \ - src/playlist/DespotifyPlaylistPlugin.hxx + src/lib/despotify/DespotifyUtils.cxx \ + src/lib/despotify/DespotifyUtils.hxx \ + src/playlist/plugins/DespotifyPlaylistPlugin.cxx \ + src/playlist/plugins/DespotifyPlaylistPlugin.hxx endif if ENABLE_SOUNDCLOUD libplaylist_plugins_a_SOURCES += \ - src/playlist/SoundCloudPlaylistPlugin.cxx \ - src/playlist/SoundCloudPlaylistPlugin.hxx + src/playlist/plugins/SoundCloudPlaylistPlugin.cxx \ + src/playlist/plugins/SoundCloudPlaylistPlugin.hxx PLAYLIST_LIBS += $(YAJL_LIBS) endif +if HAVE_EXPAT +libplaylist_plugins_a_SOURCES += \ + src/lib/expat/ExpatParser.cxx src/lib/expat/ExpatParser.hxx \ + src/playlist/plugins/XspfPlaylistPlugin.cxx \ + src/playlist/plugins/XspfPlaylistPlugin.hxx \ + src/playlist/plugins/AsxPlaylistPlugin.cxx \ + src/playlist/plugins/AsxPlaylistPlugin.hxx \ + src/playlist/plugins/RssPlaylistPlugin.cxx \ + src/playlist/plugins/RssPlaylistPlugin.hxx +endif + +if HAVE_GLIB +libplaylist_plugins_a_SOURCES += \ + src/playlist/plugins/PlsPlaylistPlugin.cxx \ + src/playlist/plugins/PlsPlaylistPlugin.hxx +endif + # # Filter plugins # libfilter_plugins_a_SOURCES = \ - 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 + src/filter/plugins/NullFilterPlugin.cxx \ + src/filter/plugins/ChainFilterPlugin.cxx \ + src/filter/plugins/ChainFilterPlugin.hxx \ + src/filter/plugins/AutoConvertFilterPlugin.cxx \ + src/filter/plugins/AutoConvertFilterPlugin.hxx \ + src/filter/plugins/ConvertFilterPlugin.cxx \ + src/filter/plugins/ConvertFilterPlugin.hxx \ + src/filter/plugins/RouteFilterPlugin.cxx \ + src/filter/plugins/NormalizeFilterPlugin.cxx \ + src/filter/plugins/ReplayGainFilterPlugin.cxx \ + src/filter/plugins/ReplayGainFilterPlugin.hxx \ + src/filter/plugins/VolumeFilterPlugin.cxx \ + src/filter/plugins/VolumeFilterPlugin.hxx FILTER_LIBS = \ libfilter_plugins.a \ @@ -1027,29 +1185,9 @@ FILTER_LIBS = \ if HAVE_SYSTEMD systemdsystemunit_DATA = \ - mpd.service + systemd/mpd.service endif -# -# Sparse code analysis -# -# sparse is a semantic parser -# URL: git://www.kernel.org/pub/scm/devel/sparse/sparse.git -# - -SPARSE = sparse -SPARSE_FLAGS = -SPARSE_CPPFLAGS = $(DEFAULT_INCLUDES) \ - -I$(shell $(CC) -print-file-name=include) \ - -I$(shell $(CC) -print-file-name=include-fixed) -SPARSE_CPPFLAGS += -D__SCHAR_MAX__=127 -D__SHRT_MAX__=32767 \ - -D__INT_MAX__=2147483647 -D__LONG_MAX__=2147483647 -SPARSE_SRC = $(addprefix $(top_srcdir)/,$(filter %.c,$(src_mpd_SOURCES))) -sparse-check: - $(SPARSE) -I. $(src_mpd_CFLAGS) $(src_mpd_CPPFLAGS) $(SPARSE_FLAGS) $(SPARSE_CPPFLAGS) $(SPARSE_SRC) - -.PHONY: sparse-check - # # Test programs @@ -1063,6 +1201,7 @@ C_TESTS = \ test/test_mixramp \ test/test_icy_parser \ test/test_pcm \ + test/test_translate_song \ test/test_queue_priority if ENABLE_ARCHIVE @@ -1087,6 +1226,10 @@ noinst_PROGRAMS = \ test/run_normalize \ test/software_volume +if ENABLE_NEIGHBOR_PLUGINS +noinst_PROGRAMS += test/run_neighbor_explorer +endif + if HAVE_AVAHI noinst_PROGRAMS += test/run_avahi endif @@ -1111,7 +1254,7 @@ test_read_conf_LDADD = \ libfs.a \ $(GLIB_LIBS) test_read_conf_SOURCES = \ - src/Log.cxx \ + src/Log.cxx src/LogBackend.cxx \ test/read_conf.cxx test_run_resolver_LDADD = \ @@ -1119,7 +1262,7 @@ test_run_resolver_LDADD = \ libutil.a \ $(GLIB_LIBS) test_run_resolver_SOURCES = \ - src/Log.cxx \ + src/Log.cxx src/LogBackend.cxx \ test/run_resolver.cxx test_DumpDatabase_LDADD = \ @@ -1127,22 +1270,27 @@ test_DumpDatabase_LDADD = \ $(TAG_LIBS) \ libconf.a \ libutil.a \ + libevent.a \ libsystem.a \ libfs.a \ $(GLIB_LIBS) test_DumpDatabase_SOURCES = test/DumpDatabase.cxx \ src/protocol/Ack.cxx \ - src/Log.cxx \ - src/DatabaseError.cxx \ - src/DatabaseRegistry.cxx \ - src/DatabaseSelection.cxx \ - src/Directory.cxx src/DirectorySave.cxx \ + src/Log.cxx src/LogBackend.cxx \ + src/db/DatabaseError.cxx \ + src/db/Registry.cxx \ + src/db/Selection.cxx \ + src/db/Directory.cxx src/db/DirectorySave.cxx \ src/PlaylistVector.cxx src/PlaylistDatabase.cxx \ - src/DatabaseLock.cxx src/DatabaseSave.cxx \ - src/Song.cxx src/SongSave.cxx src/SongSort.cxx \ + src/db/DatabaseLock.cxx src/db/DatabaseSave.cxx \ + src/db/Song.cxx src/SongSave.cxx src/db/SongSort.cxx \ + src/DetachedSong.cxx \ src/TagSave.cxx \ - src/SongFilter.cxx \ - src/TextFile.cxx + src/SongFilter.cxx + +if HAVE_LIBUPNP +test_DumpDatabase_SOURCES += src/lib/expat/ExpatParser.cxx +endif test_run_input_LDADD = \ $(INPUT_LIBS) \ @@ -1157,10 +1305,41 @@ test_run_input_LDADD = \ $(GLIB_LIBS) test_run_input_SOURCES = test/run_input.cxx \ test/stdbin.h \ - src/Log.cxx \ + src/Log.cxx src/LogBackend.cxx \ src/IOThread.cxx \ src/TagSave.cxx +if ENABLE_NEIGHBOR_PLUGINS + +test_run_neighbor_explorer_SOURCES = \ + src/Log.cxx src/LogBackend.cxx \ + src/IOThread.cxx \ + test/run_neighbor_explorer.cxx +test_run_neighbor_explorer_LDADD = \ + $(GLIB_LIBS) \ + $(NEIGHBOR_LIBS) \ + $(INPUT_LIBS) \ + $(ARCHIVE_LIBS) \ + libtag.a \ + libconf.a \ + libevent.a \ + libfs.a \ + libsystem.a \ + libthread.a \ + libutil.a + +if HAVE_LIBUPNP +test_run_neighbor_explorer_SOURCES += src/lib/expat/ExpatParser.cxx +endif + +if ENABLE_DESPOTIFY +test_run_neighbor_explorer_SOURCES += \ + src/lib/despotify/DespotifyUtils.cxx \ + src/lib/despotify/DespotifyUtils.hxx +endif + +endif + if ENABLE_ARCHIVE test_visit_archive_LDADD = \ @@ -1175,13 +1354,9 @@ test_visit_archive_LDADD = \ libfs.a \ $(GLIB_LIBS) test_visit_archive_SOURCES = test/visit_archive.cxx \ - src/Log.cxx \ + src/Log.cxx src/LogBackend.cxx \ src/IOThread.cxx \ - src/InputStream.cxx - -if ENABLE_DESPOTIFY -test_visit_archive_SOURCES += src/DespotifyUtils.cxx -endif + src/input/InputStream.cxx endif @@ -1198,9 +1373,8 @@ test_dump_text_file_LDADD = \ $(GLIB_LIBS) test_dump_text_file_SOURCES = test/dump_text_file.cxx \ test/stdbin.h \ - src/Log.cxx \ - src/IOThread.cxx \ - src/TextInputStream.cxx + src/Log.cxx src/LogBackend.cxx \ + src/IOThread.cxx test_dump_playlist_LDADD = \ $(PLAYLIST_LIBS) \ @@ -1218,19 +1392,20 @@ test_dump_playlist_LDADD = \ libpcm.a \ $(GLIB_LIBS) test_dump_playlist_SOURCES = test/dump_playlist.cxx \ + test/FakeDecoderAPI.cxx \ $(DECODER_SRC) \ - src/Log.cxx \ + src/Log.cxx src/LogBackend.cxx \ src/IOThread.cxx \ - src/Song.cxx src/TagSave.cxx \ + src/TagSave.cxx \ src/TagFile.cxx \ src/CheckAudioFormat.cxx \ - src/TextInputStream.cxx \ + src/DetachedSong.cxx \ src/cue/CueParser.cxx src/cue/CueParser.hxx if HAVE_FLAC test_dump_playlist_SOURCES += \ src/ReplayGainInfo.cxx \ - src/decoder/FlacMetadata.cxx + src/decoder/plugins/FlacMetadata.cxx endif test_run_decoder_LDADD = \ @@ -1248,7 +1423,7 @@ test_run_decoder_LDADD = \ $(GLIB_LIBS) test_run_decoder_SOURCES = test/run_decoder.cxx \ test/stdbin.h \ - src/Log.cxx \ + src/Log.cxx src/LogBackend.cxx \ src/IOThread.cxx \ src/ReplayGainInfo.cxx \ src/AudioFormat.cxx src/CheckAudioFormat.cxx \ @@ -1271,7 +1446,8 @@ test_read_tags_LDADD = \ libutil.a \ $(GLIB_LIBS) test_read_tags_SOURCES = test/read_tags.cxx \ - src/Log.cxx \ + test/FakeDecoderAPI.cxx \ + src/Log.cxx src/LogBackend.cxx \ src/IOThread.cxx \ src/ReplayGainInfo.cxx \ src/CheckAudioFormat.cxx \ @@ -1283,7 +1459,7 @@ test_dump_rva2_LDADD = \ libutil.a \ $(GLIB_LIBS) test_dump_rva2_SOURCES = \ - src/Log.cxx \ + src/Log.cxx src/LogBackend.cxx \ test/dump_rva2.cxx endif @@ -1297,27 +1473,19 @@ test_run_filter_LDADD = \ test_run_filter_SOURCES = test/run_filter.cxx \ test/FakeReplayGainConfig.cxx \ test/stdbin.h \ - src/Log.cxx \ - src/FilterPlugin.cxx src/FilterRegistry.cxx \ + src/Log.cxx src/LogBackend.cxx \ + src/filter/FilterPlugin.cxx src/filter/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/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.cxx \ test/stdbin.h \ - src/Log.cxx \ + src/Log.cxx src/LogBackend.cxx \ src/CheckAudioFormat.cxx \ src/AudioFormat.cxx \ src/AudioParser.cxx @@ -1337,7 +1505,7 @@ if ENABLE_VORBIS_ENCODER noinst_PROGRAMS += test/test_vorbis_encoder test_test_vorbis_encoder_SOURCES = test/test_vorbis_encoder.cxx \ test/stdbin.h \ - src/Log.cxx \ + src/Log.cxx src/LogBackend.cxx \ src/CheckAudioFormat.cxx \ src/AudioFormat.cxx \ src/AudioParser.cxx \ @@ -1357,7 +1525,8 @@ endif test_software_volume_SOURCES = test/software_volume.cxx \ test/stdbin.h \ - src/CheckAudioFormat.cxx \ + src/Log.cxx src/LogBackend.cxx \ + src/AudioFormat.cxx src/CheckAudioFormat.cxx \ src/AudioParser.cxx test_software_volume_LDADD = \ $(PCM_LIBS) \ @@ -1365,8 +1534,8 @@ test_software_volume_LDADD = \ $(GLIB_LIBS) test_run_avahi_SOURCES = \ - src/Log.cxx \ - src/ZeroconfAvahi.cxx src/AvahiPoll.cxx \ + src/Log.cxx src/LogBackend.cxx \ + src/zeroconf/ZeroconfAvahi.cxx src/zeroconf/AvahiPoll.cxx \ test/ShutdownHandler.cxx test/ShutdownHandler.hxx \ test/run_avahi.cxx test_run_avahi_CPPFLAGS = $(AM_CPPFLAGS) \ @@ -1387,7 +1556,7 @@ test_run_normalize_LDADD = \ $(GLIB_LIBS) test_run_convert_SOURCES = test/run_convert.cxx \ - src/Log.cxx \ + src/Log.cxx src/LogBackend.cxx \ src/AudioFormat.cxx \ src/CheckAudioFormat.cxx \ src/AudioParser.cxx @@ -1413,20 +1582,20 @@ test_run_output_LDADD = $(MPD_LIBS) \ test_run_output_SOURCES = test/run_output.cxx \ test/FakeReplayGainConfig.cxx \ test/stdbin.h \ - src/Log.cxx \ + src/Log.cxx src/LogBackend.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/output/OutputError.cxx \ + src/output/OutputInit.cxx src/output/OutputFinish.cxx src/output/OutputList.cxx \ + src/output/OutputPlugin.cxx \ + src/mixer/MixerControl.cxx \ + src/mixer/MixerType.cxx \ + src/filter/FilterPlugin.cxx \ + src/filter/FilterConfig.cxx \ src/AudioCompress/compress.c \ src/ReplayGainInfo.cxx @@ -1441,10 +1610,11 @@ test_read_mixer_LDADD = \ libfs.a \ $(GLIB_LIBS) test_read_mixer_SOURCES = test/read_mixer.cxx \ - src/Log.cxx \ - src/MixerControl.cxx \ - src/FilterPlugin.cxx \ - src/filter/VolumeFilterPlugin.cxx + src/Log.cxx src/LogBackend.cxx \ + src/mixer/MixerControl.cxx \ + src/filter/FilterPlugin.cxx \ + src/AudioFormat.cxx \ + src/filter/plugins/VolumeFilterPlugin.cxx if ENABLE_BZIP2_TEST TESTS += test/test_archive_bzip2.sh @@ -1462,9 +1632,9 @@ if ENABLE_INOTIFY noinst_PROGRAMS += test/run_inotify test_run_inotify_SOURCES = test/run_inotify.cxx \ test/ShutdownHandler.cxx test/ShutdownHandler.hxx \ - src/Log.cxx \ - src/InotifyDomain.cxx \ - src/InotifySource.cxx + src/Log.cxx src/LogBackend.cxx \ + src/db/update/InotifyDomain.cxx \ + src/db/update/InotifySource.cxx test_run_inotify_LDADD = \ libevent.a \ libsystem.a \ @@ -1489,7 +1659,7 @@ test_test_byte_reverse_LDADD = \ $(CPPUNIT_LIBS) test_test_mixramp_SOURCES = \ - src/Log.cxx \ + src/Log.cxx src/LogBackend.cxx \ test/test_mixramp.cxx test_test_mixramp_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0 test_test_mixramp_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations @@ -1498,16 +1668,18 @@ test_test_mixramp_LDADD = \ $(CPPUNIT_LIBS) test_test_icy_parser_SOURCES = \ - src/Log.cxx \ + src/Log.cxx src/LogBackend.cxx \ test/test_icy_parser.cxx test_test_icy_parser_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0 test_test_icy_parser_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations test_test_icy_parser_LDADD = \ libtag.a \ + libutil.a \ $(GLIB_LIBS) \ $(CPPUNIT_LIBS) test_test_pcm_SOURCES = \ + src/AudioFormat.cxx \ test/test_pcm_util.hxx \ test/test_pcm_dither.cxx \ test/test_pcm_pack.cxx \ @@ -1526,7 +1698,7 @@ test_test_pcm_LDADD = \ $(GLIB_LIBS) test_test_archive_SOURCES = \ - src/Log.cxx \ + src/Log.cxx src/LogBackend.cxx \ test/test_archive.cxx test_test_archive_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0 test_test_archive_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations @@ -1535,8 +1707,24 @@ test_test_archive_LDADD = \ $(GLIB_LIBS) \ $(CPPUNIT_LIBS) +test_test_translate_song_SOURCES = \ + src/playlist/PlaylistSong.cxx \ + src/DetachedSong.cxx \ + src/Log.cxx \ + test/test_translate_song.cxx +test_test_translate_song_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0 +test_test_translate_song_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations +test_test_translate_song_LDADD = \ + libtag.a \ + libfs.a \ + libsystem.a \ + libutil.a \ + $(GLIB_LIBS) \ + $(CPPUNIT_LIBS) + test_test_queue_priority_SOURCES = \ - src/Queue.cxx \ + src/queue/Queue.cxx \ + src/DetachedSong.cxx \ test/test_queue_priority.cxx test_test_queue_priority_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0 test_test_queue_priority_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations @@ -1563,7 +1751,7 @@ endif # man_MANS = doc/mpd.1 doc/mpd.conf.5 -doc_DATA = AUTHORS COPYING NEWS README UPGRADING doc/mpdconf.example +doc_DATA = AUTHORS COPYING NEWS README doc/mpdconf.example DOCBOOK_FILES = doc/protocol.xml doc/user.xml doc/developer.xml @@ -1591,7 +1779,7 @@ DOCBOOK_HTML = endif doc/api/html/index.html: doc/doxygen.conf - @mkdir -p $(@D) + @$(MKDIR_P) $(@D) $(DOXYGEN) $< all-local: $(DOCBOOK_HTML) doc/api/html/index.html @@ -1630,4 +1818,4 @@ EXTRA_DIST = $(doc_DATA) autogen.sh \ test/test_archive_zzip.sh \ $(wildcard scripts/*.sh) \ $(man_MANS) $(DOCBOOK_FILES) doc/mpdconf.example doc/doxygen.conf \ - src/win/mpd_win32_rc.rc.in src/win/mpd.ico + src/win32/mpd_win32_rc.rc.in src/win32/mpd.ico @@ -1,3 +1,27 @@ +ver 0.19 (not yet released) +* protocol + - new commands "addtagid", "cleartagid" + - "lsinfo" and "readcomments" allowed for remote files + - "listneighbors" lists file servers on the local network +* database + - proxy: forward "idle" events + - proxy: copy "Last-Modified" from remote directories + - upnp: new plugin +* playlist + - soundcloud: use https instead of http +* archive + - read tags from songs in an archive +* input + - alsa: new input plugin + - smbclient: new input plugin +* filter + - volume: improved software volume dithering +* encoder: + - shine: new encoder plugin +* new resampler option using libsoxr +* the update thread runs at "idle" priority +* the output thread runs at "real-time" priority + ver 0.18.8 (not yet released) * decoder - ffmpeg: support libav v10_alpha1 diff --git a/UPGRADING b/UPGRADING deleted file mode 100644 index e838371da..000000000 --- a/UPGRADING +++ /dev/null @@ -1,92 +0,0 @@ - Music Player Daemon (MPD) - UPGRADING - -Upgrading to 0.14 ------------------ - -The filesystem character set is determined by GLib, if it is not -configured. GLib has an affinity towards UTF-8, while older MPD -versions used to choose ISO-Latin-1. - - -Upgrading to 0.13.0 -------------------- - -JACK, Avahi, and libsamplerate have been added as optional dependencies. -FLAC/OggFLAC now supports the 1.1.3 API, and libmikmod 3.2.0 betas are -supported as well. - -New mpd.conf parameters include zeroconf_name, samplerate_converter, and -gapless_mp3_playback. See the mpd.conf man page or updated mpconf.example for -more information on these parameters. - -Support for the ID3v2 "Original Artist/Performer" tag has been added. Your -MP3s will need to be rescanned for these tags to be included in the database. -This can be done by running mpd --create-db. - -Upgrading to 0.12.0 -------------------- - -The ao_driver and ao_driver_options config parameters have been removed and -replaced with the audio_output config section. You will have to update your -config file to use this instead. See the mpd.conf man page or the new -mpdconf.example for details on specifying audio_output sections. - -The db_file parameter is no longer optional. If you did not specify it in your -old config file then you will have to add it in order to run 0.12.0. - -Support for OggFLAC and Musepack audio files has been added. Additionally, -scanning of MP3 files has been improved. To make use of these updates it is -highly recommended that you run mpd --create-db to recreate your entire -database. - -Upgrading to 0.11.0 -------------------- - -The database format has changed a little bit, but in a backward compatible way. -This means that if you upgrade to 0.11.0 from 0.10.x, you do not need to make -any changes. However, if you downgrade back to 0.10.x, then you will need -to recreate your db. - -The default port for MPD is now 6600, so update your mpd and client -configurations appropriately. - -Upgrading to 0.10.0 -------------------- - -All information is now stored in the db in UTF-8 format, and the character -set used for the filesystem is stored in the db. Thus, it is highly -recommended that you recreate the db. To do so, run mpd with the -"--create-db" command line option. Also, note that the filesystem -character set will be determined from your current locale settings. -If your locale settings are not the same as those used for the filesystem, -then use the config file parameter "filesystem_charset" to specify the -correct character set (this maybe necessary if you create the db with root). - -Upgrading to 0.9.3 ------------------- - -Wave support was added, so to have your wave files added, update the db (mpc -update). - -Also, song lengths are now stored in the db. To get this stuff -added to the db, you will need to recreate the db from scratch. To do this, -run mpd with the "--create-db" commandline option. - -Upgrading to 0.9.0 ------------------- - -The "stop_on_error" config parameter was removed, so be sure to remove this -parameter from your config file. - -Upgrading to 0.8.x ------------------- - -If you have FLACs, then to have them added to your list of available music, -just use "update". - -Upgrading from 0.5.x to 0.6.x ------------------------------ -If you have not compiled MPD with "make ogg", then nothing is needed. - -If you compiled with "make ogg", just use "update" (available via the phpMp -interface) to add your OGGs to MPD's list of available music. diff --git a/autogen.sh b/autogen.sh index f163e35a7..ebd2b814e 100755 --- a/autogen.sh +++ b/autogen.sh @@ -1,137 +1,11 @@ #!/bin/sh -# Run this to set up the build system: configure, makefiles, etc. -# (at one point this was based on the version in enlightenment's cvs) -package="mpd" +set -e -olddir="`pwd`" -srcdir="`dirname $0`" -test -z "$srcdir" && srcdir=. -cd "$srcdir" -DIE= -AM_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9][0-9]*\).*/\1/" -AC_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9][0-9]\).*/\1/" -VERSIONMKINT="sed -e s/[^0-9]//" -if test -n "$AM_FORCE_VERSION" -then - AM_VERSIONS="$AM_FORCE_VERSION" -else - AM_VERSIONS='1.11' -fi -if test -n "$AC_FORCE_VERSION" -then - AC_VERSIONS="$AC_FORCE_VERSION" -else - AC_VERSIONS='2.60 2.61' -fi +rm -rf config.cache build +mkdir build -versioned_bins () -{ - bin="$1" - needed_int=`echo $VERNEEDED | $VERSIONMKINT` - for i in $VERSIONS - do - i_int=`echo $i | $VERSIONMKINT` - if test $i_int -ge $needed_int - then - echo $bin-$i $bin$i $bin-$i_int $bin$i_int - fi - done - echo $bin -} - -for c in autoconf autoheader automake aclocal -do - uc=`echo $c | tr '[:lower:]' '[:upper:]'` - eval "val=`echo '$'$uc`" - if test -n "$val" - then - echo "$uc=$val in environment, will not attempt to auto-detect" - continue - fi - - case "$c" in - autoconf|autoheader) - VERNEEDED=`fgrep AC_PREREQ configure.ac | $AC_VERSIONGREP` - VERSIONS="$AC_VERSIONS" - pkg=autoconf - ;; - automake|aclocal) - VERNEEDED=`fgrep AUTOMAKE_OPTIONS Makefile.am | $AM_VERSIONGREP` - VERSIONS="$AM_VERSIONS" - pkg=automake - ;; - esac - printf "checking for $c ... " - for x in `versioned_bins $c`; do - ($x --version < /dev/null > /dev/null 2>&1) > /dev/null 2>&1 - if test $? -eq 0 - then - echo $x - eval $uc=$x - break - fi - done - eval "val=`echo '$'$uc`" - if test -z "$val" - then - if test $c = $pkg - then - DIE="$DIE $c=$VERNEEDED" - else - DIE="$DIE $c($pkg)=$VERNEEDED" - fi - fi -done - -if test -n "$DIE" -then - echo "You must have the following installed to compile $package:" - for i in $DIE - do - printf ' ' - echo $i | sed -e 's/(/ (from /' -e 's/=\(.*\)/ (>= \1)/' - done - echo "Download the appropriate package(s) for your system," - echo "or get the source from one of the GNU ftp sites" - echo "listed in http://www.gnu.org/order/ftp.html" - exit 1 -fi - -echo "Generating configuration files for $package, please wait...." - -ACLOCAL_FLAGS="$ACLOCAL_FLAGS -I m4" - -# /usr/share/aclocal is most likely included by default, already... -ac_local_paths=' -/usr/local/share/aclocal -/sw/share/aclocal -/usr/pkg/share/aclocal -/opt/share/aclocal -/usr/gnu/share/aclocal -' - -for i in $ac_local_paths; do - if test -d "$i"; then - ACLOCAL_FLAGS="$ACLOCAL_FLAGS -I $i" - # we probably only want one of these... - break - fi -done - -echo " $ACLOCAL $ACLOCAL_FLAGS" -$ACLOCAL $ACLOCAL_FLAGS || exit 1 - -echo " $AUTOHEADER" -$AUTOHEADER || exit 1 - -echo " $AUTOMAKE --add-missing $AUTOMAKE_FLAGS" -$AUTOMAKE --add-missing $AUTOMAKE_FLAGS || exit 1 - -echo " $AUTOCONF" -$AUTOCONF || exit 1 - -cd "$olddir" -if test x$NOCONFIGURE = x; then - "$srcdir"/configure "$@" || exit 1 -fi +aclocal -I m4 +autoheader +automake --add-missing --foreign +autoconf diff --git a/configure.ac b/configure.ac index fc94f4c1e..42bf9898d 100644 --- a/configure.ac +++ b/configure.ac @@ -1,20 +1,25 @@ AC_PREREQ(2.60) -AC_INIT(mpd, 0.18.8, mpd-devel@musicpd.org) +AC_INIT(mpd, 0.19~git, musicpd-dev-team@lists.sourceforge.net) VERSION_MAJOR=0 -VERSION_MINOR=18 +VERSION_MINOR=19 VERSION_REVISION=0 VERSION_EXTRA=0 AC_CONFIG_SRCDIR([src/Main.cxx]) +AC_CONFIG_AUX_DIR(build) AM_INIT_AUTOMAKE([foreign 1.11 dist-xz subdir-objects]) AM_SILENT_RULES AC_CONFIG_HEADERS(config.h) AC_CONFIG_MACRO_DIR([m4]) -AC_DEFINE(PROTOCOL_VERSION, "0.18.0", [The MPD protocol version]) +AC_DEFINE(PROTOCOL_VERSION, "0.19.0", [The MPD protocol version]) +GIT_COMMIT=`GIT_DIR="$srcdir/.git" git describe --dirty --always 2>/dev/null` +if test x$GIT_COMMIT != x; then + AC_DEFINE_UNQUOTED(GIT_COMMIT, ["$GIT_COMMIT"], [The current git commit]) +fi dnl --------------------------------------------------------------------------- dnl Programs @@ -65,25 +70,41 @@ dnl OS Specific Defaults dnl --------------------------------------------------------------------------- AC_CANONICAL_HOST +host_is_unix=yes +host_is_linux=no host_is_darwin=no +host_is_solaris=no +host_is_windows=no + +linux_auto=no case "$host_os" in +linux*) + host_is_linux=yes + linux_auto=auto + ;; + mingw32* | windows*) AC_CONFIG_FILES([ - src/win/mpd_win32_rc.rc + src/win32/mpd_win32_rc.rc ]) AC_CHECK_TOOL(WINDRES, windres) 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 + host_is_windows=yes + host_is_unix=no ;; darwin*) host_is_darwin=yes ;; + +solaris*) + host_is_solaris=yes + ;; esac -AM_CONDITIONAL([HAVE_WINDOWS], [test x$HAVE_WINDOWS = x1]) +AM_CONDITIONAL([HAVE_WINDOWS], [test x$host_is_windows = xyes]) if test -z "$prefix" || test "x$prefix" = xNONE; then local_lib= @@ -137,7 +158,6 @@ fi dnl --------------------------------------------------------------------------- dnl Header/Library Checks dnl --------------------------------------------------------------------------- -AC_CHECK_FUNCS(daemon fork) AC_SEARCH_LIBS([syslog], [bsd socket inet], [AC_DEFINE(HAVE_SYSLOG, 1, [Define if syslog() is available])]) @@ -145,10 +165,16 @@ AC_SEARCH_LIBS([syslog], [bsd socket inet], 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) +if test x$host_is_linux = xyes; then + AC_CHECK_FUNCS(pipe2 accept4) +fi + +AC_CHECK_FUNCS(getpwnam_r getpwuid_r) + +if test x$host_is_linux = xyes; then + MPD_OPTIONAL_FUNC(eventfd, eventfd, USE_EVENTFD) + MPD_OPTIONAL_FUNC(signalfd, signalfd, USE_SIGNALFD) +fi AC_SEARCH_LIBS([exp], [m],, [AC_MSG_ERROR([exp() not found])]) @@ -156,6 +182,55 @@ AC_SEARCH_LIBS([exp], [m],, AC_CHECK_HEADERS(locale.h) AC_CHECK_HEADERS(valgrind/memcheck.h) +AC_CHECK_LIB([pthread], [pthread_setname_np], + [have_pthread_setname_np=yes], + [have_pthread_setname_np=no]) +if test x$have_pthread_setname_np = xyes; then + AC_DEFINE(HAVE_PTHREAD_SETNAME_NP, 1, [Is pthread_setname_np() available?]) +fi + +dnl --------------------------------------------------------------------------- +dnl Event loop selection +dnl --------------------------------------------------------------------------- + +MPD_OPTIONAL_FUNC_NODEF(poll, poll) + +if test x$host_is_linux = xyes; then + MPD_OPTIONAL_FUNC_NODEF(epoll, epoll_create1) +fi + +AC_ARG_WITH(pollmethod, + AS_HELP_STRING( + [--with-pollmethod=@<:@epoll|poll|winselect|auto@:>@], + [specify poll method for internal event loop (default=auto)]),, + [with_pollmethod=auto]) + +if test "x$with_pollmethod" = xauto; then + if test "x$enable_epoll" = xyes; then + with_pollmethod=epoll + elif test "x$enable_poll" = xyes; then + with_pollmethod=poll + elif test "x$host_is_windows" = xyes; then + with_pollmethod=winselect + else + AC_MSG_ERROR([no poll method is available for your platform]) + fi +fi +case "$with_pollmethod" in +epoll) + AC_DEFINE(USE_EPOLL, 1, [Define to poll sockets with epoll]) + ;; +poll) + AC_DEFINE(USE_POLL, 1, [Define to poll sockets with poll]) + ;; +winselect) + AC_DEFINE(USE_WINSELECT, 1, + [Define to poll sockets with Windows select]) + ;; +*) + AC_MSG_ERROR([unknown pollmethod option: $with_pollmethod]) +esac + dnl --------------------------------------------------------------------------- dnl Allow tools to be specifically built dnl --------------------------------------------------------------------------- @@ -165,6 +240,16 @@ AC_ARG_ENABLE(libmpdclient, [enable support for the MPD client]),, enable_libmpdclient=auto) +AC_ARG_ENABLE(expat, + AS_HELP_STRING([--enable-expat], + [enable the expat XML parser]),, + enable_expat=auto) + +AC_ARG_ENABLE(upnp, + AS_HELP_STRING([--enable-upnp], + [enable UPnP client support (default: auto)]),, + enable_upnp=auto) + AC_ARG_ENABLE(adplug, AS_HELP_STRING([--enable-adplug], [enable the AdPlug decoder plugin (default: auto)]),, @@ -172,7 +257,7 @@ AC_ARG_ENABLE(adplug, AC_ARG_ENABLE(alsa, AS_HELP_STRING([--enable-alsa], [enable ALSA support]),, - [enable_alsa=auto]) + [enable_alsa=$linux_auto]) AC_ARG_ENABLE(roar, AS_HELP_STRING([--enable-roar], @@ -204,6 +289,11 @@ AC_ARG_ENABLE(curl, [enable support for libcurl HTTP streaming (default: auto)]),, [enable_curl=auto]) +AC_ARG_ENABLE(smbclient, + AS_HELP_STRING([--enable-smbclient], + [enable support for libsmbclient (default: auto)]),, + [enable_smbclient=auto]) + AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug], [enable debugging (default: disabled)]),, @@ -295,6 +385,11 @@ AC_ARG_ENABLE(lsr, [enable libsamplerate support]),, enable_lsr=auto) +AC_ARG_ENABLE(soxr, + AS_HELP_STRING([--enable-soxr], + [enable the libsoxr resampler]),, + enable_soxr=auto) + AC_ARG_ENABLE(mad, AS_HELP_STRING([--enable-mad], [enable libmad mp3 decoder plugin]),, @@ -365,6 +460,10 @@ AC_ARG_ENABLE(sidplay, [enable C64 SID support via libsidplay2]),, enable_sidplay=auto) +AC_ARG_ENABLE(shine-encoder, + AS_HELP_STRING([--enable-shine-encoder], + [enables shine encoder]),, + [enable_shine_encoder=auto]) AC_ARG_ENABLE(shout, AS_HELP_STRING([--enable-shout], @@ -379,7 +478,7 @@ AC_ARG_ENABLE(sndfile, AC_ARG_ENABLE(solaris_output, AS_HELP_STRING([--enable-solaris-output], [enables the Solaris /dev/audio output]),, - [enable_solaris_output=auto]) + [enable_solaris_output=$host_is_solaris]) AC_ARG_ENABLE(sqlite, AS_HELP_STRING([--enable-sqlite], @@ -389,7 +488,7 @@ AC_ARG_ENABLE(sqlite, AC_ARG_ENABLE(systemd-daemon, AS_HELP_STRING([--enable-systemd-daemon], [use the systemd daemon library (default=auto)]),, - [enable_systemd_daemon=auto]) + [enable_systemd_daemon=$linux_auto]) AC_ARG_ENABLE(tcp, AS_HELP_STRING([--disable-tcp], @@ -414,7 +513,7 @@ AC_ARG_ENABLE(twolame-encoder, AC_ARG_ENABLE(un, AS_HELP_STRING([--disable-un], [disable support for clients connecting via unix domain sockets (default: enable)]),, - [enable_un=yes]) + [enable_un=$host_is_unix]) AC_ARG_ENABLE(vorbis, AS_HELP_STRING([--enable-vorbis], @@ -470,13 +569,24 @@ AC_ARG_WITH(tremor-includes, dnl --------------------------------------------------------------------------- dnl Mandatory Libraries dnl --------------------------------------------------------------------------- -PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.28 gthread-2.0],, + +AC_ARG_ENABLE(glib, + AS_HELP_STRING([--enable-glib], + [enable GLib usage (default: enabled)]),, + enable_glib=yes) + +if test x$enable_glib = xyes; then + PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.28 gthread-2.0],, [AC_MSG_ERROR([GLib 2.28 is required])]) -if test x$GCC = xyes; then - # suppress warnings in the GLib headers - GLIB_CFLAGS=`echo $GLIB_CFLAGS |sed -e 's,-I/,-isystem /,g'` + if test x$GCC = xyes; then + # suppress warnings in the GLib headers + GLIB_CFLAGS=`echo $GLIB_CFLAGS |sed -e 's,-I/,-isystem /,g'` + fi + + AC_DEFINE(HAVE_GLIB, 1, [Define if GLib is used]) fi +AM_CONDITIONAL(HAVE_GLIB, test x$enable_glib = xyes) dnl --------------------------------------------------------------------------- dnl Protocol Options @@ -514,12 +624,6 @@ if test x$enable_tcp = xyes; then AC_DEFINE(HAVE_TCP, 1, [Define if TCP socket support is enabled]) fi -case "$host_os" in -mingw* | windows* | cygwin*) - enable_un=no - ;; -esac - if test x$enable_un = xyes; then AC_DEFINE(HAVE_UN, 1, [Define if unix domain socket support is enabled]) STRUCT_UCRED @@ -560,6 +664,15 @@ fi AM_CONDITIONAL(HAVE_LIBMPDCLIENT, test x$enable_libmpdclient = xyes) +dnl -------------------------------- expat -------------------------------- +MPD_AUTO_PKG(expat, EXPAT, [expat], + [expat XML parser], [expat not found]) +if test x$enable_expat = xyes; then + AC_DEFINE(HAVE_EXPAT, 1, [Define to use the expat XML parser]) +fi + +AM_CONDITIONAL(HAVE_EXPAT, test x$enable_expat = xyes) + dnl --------------------------------- inotify --------------------------------- AC_CHECK_FUNCS(inotify_init inotify_init1) @@ -692,21 +805,22 @@ dnl Converter Plugins dnl --------------------------------------------------------------------------- dnl ------------------------------ libsamplerate ------------------------------ -MPD_AUTO_PKG(lsr, SAMPLERATE, [samplerate >= 0.0.15], +MPD_AUTO_PKG(lsr, SAMPLERATE, [samplerate >= 0.1.3], [libsamplerate resampling], [libsamplerate not found]) if test x$enable_lsr = xyes; then AC_DEFINE([HAVE_LIBSAMPLERATE], 1, [Define to enable libsamplerate]) fi +AM_CONDITIONAL(HAVE_LIBSAMPLERATE, test x$enable_lsr = xyes) -if test x$enable_lsr = xyes; then - PKG_CHECK_MODULES([SAMPLERATE_013], - [samplerate >= 0.1.3],, - [AC_DEFINE([HAVE_LIBSAMPLERATE_NOINT], 1, - [libsamplerate doesn't provide src_int_to_float_array() (<0.1.3)])]) +dnl ------------------------------ libsoxr ------------------------------------ +MPD_AUTO_PKG(soxr, SOXR, [soxr], + [libsoxr resampler], [libsoxr not found]) +if test x$enable_soxr = xyes; then + AC_DEFINE([HAVE_SOXR], 1, [Define to enable libsoxr]) fi -AM_CONDITIONAL(HAVE_LIBSAMPLERATE, test x$enable_lsr = xyes) +AM_CONDITIONAL(HAVE_SOXR, test x$enable_soxr = xyes) dnl --------------------------------------------------------------------------- dnl Input Plugins @@ -720,6 +834,14 @@ if test x$enable_curl = xyes; then fi AM_CONDITIONAL(ENABLE_CURL, test x$enable_curl = xyes) +dnl ----------------------------------- smbclient ----------------------------- +MPD_AUTO_PKG(smbclient, SMBCLIENT, [smbclient >= 0.2], + [smbclient input plugin], [libsmbclient not found]) +if test x$enable_smbclient = xyes; then + AC_DEFINE(ENABLE_SMBCLIENT, 1, [Define when libsmbclient is used]) +fi +AM_CONDITIONAL(ENABLE_SMBCLIENT, test x$enable_smbclient = xyes) + dnl --------------------------------- Despotify --------------------------------- MPD_AUTO_PKG(despotify, DESPOTIFY, [despotify], [Despotify support], [despotify not found]) @@ -764,6 +886,30 @@ fi AM_CONDITIONAL(ENABLE_MMS, test x$enable_mms = xyes) dnl --------------------------------------------------------------------------- +dnl Neighbor Plugins +dnl --------------------------------------------------------------------------- + +AC_ARG_ENABLE(neighbor-plugins, + AS_HELP_STRING([--enable-neighbor-plugins], + [enable support for neighbor discovery (default: auto)]),, + [enable_neighbor_plugins=auto]) + +if test x$enable_neighbor_plugins = xauto; then + if test x$enable_smbclient = xyes; then + enable_neighbor_plugins=yes + fi + if test x$enable_upnp = xyes; then + enable_neighbor_plugins=yes + fi +fi + +if test x$enable_neighbor_plugins = xyes; then + AC_DEFINE(ENABLE_NEIGHBOR_PLUGINS, 1, + [Define to enable support for neighbor discovery]) +fi +AM_CONDITIONAL(ENABLE_NEIGHBOR_PLUGINS, test x$enable_neighbor_plugins = xyes) + +dnl --------------------------------------------------------------------------- dnl Archive Plugins dnl --------------------------------------------------------------------------- @@ -798,6 +944,24 @@ fi AM_CONDITIONAL(ENABLE_BZIP2_TEST, test x$BZIP2 != xno) +dnl ---------------------------------- libupnp --------------------------------- + +if test x$enable_expat = xno; then + if test x$enable_upnp = xauto; then + AC_MSG_WARN([expat disabled -- disabling UPnP]) + enable_upnp=no + elif test x$enable_upnp = xyes; then + AC_MSG_ERROR([expat disabled -- required for UPnP]) + fi +fi + +MPD_AUTO_PKG(upnp, UPNP, [libupnp], + [UPnP client support], [libupnp not found]) +if test x$enable_upnp = xyes; then + AC_DEFINE(HAVE_LIBUPNP, 1, [Define when libupnp is used]) +fi +AM_CONDITIONAL(HAVE_LIBUPNP, test x$enable_upnp = xyes) + dnl --------------------------------- libzzip --------------------------------- MPD_AUTO_PKG(zzip, ZZIP, [zziplib >= 0.13], [libzzip archive library], [libzzip not found]) @@ -1127,6 +1291,7 @@ else enable_vorbis_encoder=no enable_lame_encoder=no enable_twolame_encoder=no + enable_shine_encoder=no enable_wave_encoder=no enable_flac_encoder=no fi @@ -1138,6 +1303,17 @@ if test x$enable_flac_encoder = xyes; then fi AM_CONDITIONAL(ENABLE_FLAC_ENCODER, test x$enable_flac_encoder = xyes) +dnl ------------------------------- Shine Encoder ------------------------------ + +MPD_AUTO_PKG(shine_encoder, SHINE, [shine >= 3], + [shine encoder], [libshine not found]) + +if test x$enable_shine_encoder = xyes; then + AC_DEFINE(ENABLE_SHINE_ENCODER, 1, + [Define to enable the shine encoder plugin]) +fi +AM_CONDITIONAL(ENABLE_SHINE_ENCODER, test x$enable_shine_encoder = xyes) + dnl ---------------------------- Ogg Vorbis Encoder --------------------------- MPD_AUTO_PKG(vorbis_encoder, VORBISENC, [vorbisenc], [Ogg Vorbis encoder], [libvorbisenc not found]) @@ -1181,6 +1357,7 @@ if test x$enable_vorbis_encoder != xno || test x$enable_lame_encoder != xno || test x$enable_twolame_encoder != xno || test x$enable_flac_encoder != xno || + test x$enable_shine_encoder != xno || test x$enable_wave_encoder != xno; then # at least one encoder plugin is enabled enable_encoder=yes @@ -1376,18 +1553,6 @@ AM_CONDITIONAL(HAVE_SHOUT, test x$enable_shout = xyes) dnl --------------------------------- Solaris --------------------------------- -if test x$enable_solaris_output = xauto; then - case "$host_os" in - solaris*) - enable_solaris_output=yes - ;; - - *) - enable_solaris_output=no - ;; - esac -fi - if test x$enable_solaris_output = xyes; then AC_DEFINE(ENABLE_SOLARIS_OUTPUT, 1, [Define to enable Solaris /dev/audio support]) fi @@ -1396,17 +1561,13 @@ AM_CONDITIONAL(ENABLE_SOLARIS_OUTPUT, test x$enable_solaris_output = xyes) dnl --------------------------------- WinMM --------------------------------- -case "$host_os" in - mingw32* | windows*) - AC_DEFINE(ENABLE_WINMM_OUTPUT, 1, [Define to enable WinMM support]) - enable_winmm_output=yes - LIBS="$LIBS -lwinmm" - ;; - - *) - enable_winmm_output=no - ;; -esac +if test "x$host_is_windows" = xyes; then + AC_DEFINE(ENABLE_WINMM_OUTPUT, 1, [Define to enable WinMM support]) + enable_winmm_output=yes + LIBS="$LIBS -lwinmm" +else + enable_winmm_output=no +fi AM_CONDITIONAL(ENABLE_WINMM_OUTPUT, test x$enable_winmm_output = xyes) @@ -1574,6 +1735,7 @@ results(wildmidi, [WildMidi]) printf '\nOther features:\n\t' results(lsr, [libsamplerate]) +results(soxr, [libsoxr]) results(libmpdclient, [libmpdclient]) results(inotify, [inotify]) results(sqlite, [SQLite]) @@ -1607,6 +1769,7 @@ if printf '\nStreaming encoder support:\n\t' results(flac_encoder, [FLAC]) results(lame_encoder, [LAME]) + results(shine_encoder, [Shine]) results(vorbis_encoder, [Ogg Vorbis]) results(opus, [Opus]) results(twolame_encoder, [TwoLAME]) @@ -1616,11 +1779,15 @@ fi printf '\nStreaming support:\n\t' results(cdio_paranoia, [CDIO_PARANOIA]) results(curl,[CURL]) +results(smbclient,[SMBCLIENT]) results(despotify,[Despotify]) results(soundcloud,[Soundcloud]) printf '\n\t' results(mms,[MMS]) +printf '\nEvent loop:\n\t' +printf $with_pollmethod + printf '\n\n##########################################\n\n' echo 'Generating files needed for compilation' @@ -1630,7 +1797,7 @@ dnl Generate files dnl --------------------------------------------------------------------------- AC_CONFIG_FILES(Makefile) AC_CONFIG_FILES(doc/doxygen.conf) -AC_CONFIG_FILES(mpd.service) +AC_CONFIG_FILES(systemd/mpd.service) AC_OUTPUT echo 'MPD is ready for compilation, type "make" to begin.' diff --git a/doc/mpd.conf.5 b/doc/mpd.conf.5 index 6431613d1..6e3bae7b2 100644 --- a/doc/mpd.conf.5 +++ b/doc/mpd.conf.5 @@ -136,53 +136,6 @@ for the format of this parameter. Multiple audio_output sections may be specified. If no audio_output section is specified, then MPD will scan for a usable audio output. .TP -.B audio_output_format <sample_rate:bits:channels> -This specifies the sample rate, bits per sample, and number of channels of -audio that is sent to each audio output. Note that audio outputs may specify -their own audio format which will be used for actual output to the audio -device. An example is "44100:16:2" for 44100Hz, 16 bits, and 2 channels. The -default is to use the audio format of the input file. -Any of the three attributes may be an asterisk to specify that this -attribute should not be enforced -.TP -.B samplerate_converter <integer or prefix> -This specifies the libsamplerate converter to use. The supplied value should -either be an integer or a prefix of the name of a converter. The default is -"Fastest Sinc Interpolator". - -At the time of this writing, the following converters are available: -.RS -.TP -Best Sinc Interpolator (0) - -Band limited sinc interpolation, best quality, 97dB SNR, 96% BW. -.TP -Medium Sinc Interpolator (1) - -Band limited sinc interpolation, medium quality, 97dB SNR, 90% BW. -.TP -Fastest Sinc Interpolator (2) - -Band limited sinc interpolation, fastest, 97dB SNR, 80% BW. -.TP -ZOH Interpolator (3) - -Zero order hold interpolator, very fast, very poor quality with audible -distortions. -.TP -Linear Interpolator (4) - -Linear interpolator, very fast, poor quality. -.TP -internal - -Poor quality, no floating point operations. This is the default (and -only choice) if MPD was compiled without libsamplerate. -.RE -.IP -For an up-to-date list of available converters, please see the libsamplerate -documentation (available online at <\fBhttp://www.mega\-nerd.com/SRC/\fP>). -.TP .B replaygain <off or album or track or auto> If specified, mpd will adjust the volume of songs played using ReplayGain tags (see <\fBhttp://www.replaygain.org/\fP>). Setting this to "album" will adjust diff --git a/doc/protocol.xml b/doc/protocol.xml index abc74e4e6..625fef874 100644 --- a/doc/protocol.xml +++ b/doc/protocol.xml @@ -1258,6 +1258,44 @@ OK </para> </listitem> </varlistentry> + + <varlistentry id="command_addtagid"> + <term> + <cmdsynopsis> + <command>addtagid</command> + <arg choice="req"><replaceable>SONGID</replaceable></arg> + <arg choice="req"><replaceable>TAG</replaceable></arg> + <arg choice="req"><replaceable>VALUE</replaceable></arg> + </cmdsynopsis> + </term> + <listitem> + <para> + Adds a tag to the specified song. Editing song tags is + only possible for remote songs. This change is + volatile: it may be overwritten by tags received from + the server, and the data is gone when the song gets + removed from the queue. + </para> + </listitem> + </varlistentry> + + <varlistentry id="command_cleartagid"> + <term> + <cmdsynopsis> + <command>cleartagid</command> + <arg choice="req"><replaceable>SONGID</replaceable></arg> + <arg choice="opt"><replaceable>TAG</replaceable></arg> + </cmdsynopsis> + </term> + <listitem> + <para> + Removes tags from the specified song. If + <varname>TAG</varname> is not specified, then all tag + values will be removed. Editing song tags is only + possible for remote songs. + </para> + </listitem> + </varlistentry> </variablelist> </section> @@ -1580,6 +1618,10 @@ OK deprecated; use "listplaylists" instead. </para> <para> + This command may be used to list metadata of remote + files (e.g. URI beginning with "http://" or "smb://"). + </para> + <para> Clients that are connected via UNIX domain socket may use this command to read the tags of an arbitrary local file (URI beginning with "file:///"). @@ -1601,6 +1643,10 @@ OK "file:///foo/bar.ogg". </para> <para> + This command may be used to list metadata of remote + files (e.g. URI beginning with "http://" or "smb://"). + </para> + <para> The response consists of lines in the form "KEY: VALUE". Comments with suspicious characters (e.g. newlines) are ignored silently. @@ -1952,6 +1998,32 @@ OK <para> Shows information about all outputs. </para> + <screen> +outputid: 0 +outputname: My ALSA Device +outputenabled: 0 +OK + </screen> + <para> + Return information: + </para> + <itemizedlist> + <listitem> + <para> + <varname>outputid</varname>: ID of the output. May change between executions + </para> + </listitem> + <listitem> + <para> + <varname>outputname</varname>: Name of the output. It can be any. + </para> + </listitem> + <listitem> + <para> + <varname>outputenabled</varname>: Status of the output. 0 if disabled, 1 if enabled. + </para> + </listitem> + </itemizedlist> </listitem> </varlistentry> </variablelist> diff --git a/doc/user.xml b/doc/user.xml index ccc9e7119..c78ffcf20 100644 --- a/doc/user.xml +++ b/doc/user.xml @@ -393,7 +393,7 @@ systemctl start mpd.socket</programlisting> a name registered in the PULSE server. </entry> </row> - <row> + <row id="ao_format"> <entry> <varname>format</varname> </entry> @@ -417,8 +417,6 @@ systemctl start mpd.socket</programlisting> (signed 8 bit integer samples), <varname>16</varname>, <varname>24</varname> (signed 24 bit integer samples padded to 32 bit), - <varname>24_3</varname> (signed 24 bit integer - samples, no padding, 3 bytes per sample), <varname>32</varname> (signed 32 bit integer samples), <varname>f</varname> (32 bit floating point, -1.0 to 1.0). @@ -609,6 +607,202 @@ systemctl start mpd.socket</programlisting> </tgroup> </informaltable> </section> + + <section> + <title>Audio Format Settings</title> + + <section> + <title>Global Audio Format</title> + + <para> + The setting <varname>audio_output_format</varname> forces + MPD to use one audio format for all outputs. Doing that is + usually not a good idea. The values are the same as in + <link linkend="ao_format"><varname>format</varname> in the + <varname>audio_output</varname> section</link>. + </para> + </section> + + <section> + <title>Resampler</title> + + <para> + Sometimes, music needs to be resampled before it can be + played; for example, CDs use a sample rate of 44,100 Hz + while many cheap audio chips can only handle 48,000 Hz. + Resampling reduces the quality and consumes a lot of CPU. + There are different options, some of them optimized for high + quality and others for low CPU usage, but you can't have + both at the same time. Often, the resampler is the + component that is responsible for most of MPD's CPU usage. + Since MPD comes with high quality defaults, it may appear + that MPD consumes more CPU than other software. + </para> + + <para> + The following resamplers are available (if enabled at + compile time): + </para> + + <itemizedlist> + <listitem> + <para> + <ulink + url="http://www.mega-nerd.com/SRC/">libsamplerate</ulink> + a.k.a. Secret Rabbit Code (SRC). + </para> + </listitem> + + <listitem> + <para> + <ulink + url="http://sourceforge.net/projects/soxr/">libsoxr</ulink>, + the SoX Resampler library + </para> + </listitem> + + <listitem> + <para> + internal: low CPU usage, but very poor quality. This is + the fallback if MPD was compiled without an external + resampler. + </para> + </listitem> + </itemizedlist> + + <para> + The setting <varname>samplerate_converter</varname> controls + how MPD shall resample music. Possible values: + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry> + Value + </entry> + <entry> + Description + </entry> + </row> + </thead> + <tbody> + <row> + <entry> + "<parameter>internal</parameter>" + </entry> + <entry> + The internal resampler. Low CPU usage, but very + poor quality. + </entry> + </row> + + <row> + <entry> + "<parameter>soxr very high</parameter>" + </entry> + <entry> + Use libsoxr with "Very High Quality" setting. + </entry> + </row> + + <row> + <entry> + "<parameter>soxr high</parameter>" or + "<parameter>soxr</parameter>" + </entry> + <entry> + Use libsoxr with "High Quality" setting. + </entry> + </row> + + <row> + <entry> + "<parameter>soxr medium</parameter>" + </entry> + <entry> + Use libsoxr with "Medium Quality" setting. + </entry> + </row> + + <row> + <entry> + "<parameter>soxr low</parameter>" + </entry> + <entry> + Use libsoxr with "Low Quality" setting. + </entry> + </row> + + <row> + <entry> + "<parameter>soxr quick</parameter>" + </entry> + <entry> + Use libsoxr with "Quick" setting. + </entry> + </row> + + <row> + <entry> + "<parameter>Best Sinc Interpolator</parameter>" or + "<parameter>0</parameter>" + </entry> + <entry> + libsamplerate: Band limited sinc interpolation, best + quality, 97dB SNR, 96% BW. + </entry> + </row> + + <row> + <entry> + "<parameter>Medium Sinc Interpolator</parameter>" or + "<parameter>1</parameter>" + </entry> + <entry> + libsamplerate: Band limited sinc interpolation, + medium quality, 97dB SNR, 90% BW. + </entry> + </row> + + <row> + <entry> + "<parameter>Fastest Sinc Interpolator</parameter>" or + "<parameter>2</parameter>" + </entry> + <entry> + libsamplerate: Band limited sinc interpolation, + fastest, 97dB SNR, 80% BW. + </entry> + </row> + + <row> + <entry> + "<parameter>ZOH Sinc Interpolator</parameter>" or + "<parameter>3</parameter>" + </entry> + <entry> + libsamplerate: Zero order hold interpolator, very + fast, very poor quality with audible distortions. + </entry> + </row> + + <row> + <entry> + "<parameter>Linear Interpolator</parameter>" or + "<parameter>4</parameter>" + </entry> + <entry> + libsamplerate: Linear interpolator, very fast, poor + quality. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + </section> </chapter> <chapter> @@ -749,6 +943,14 @@ systemctl start mpd.socket</programlisting> </tgroup> </informaltable> </section> + + <section> + <title><varname>upnp</varname></title> + + <para> + Provides access to UPnP media servers. + </para> + </section> </section> <section> @@ -902,6 +1104,38 @@ systemctl start mpd.socket</programlisting> </tgroup> </informaltable> </section> + + <section> + <title><varname>smbclient</varname></title> + + <para> + Allows MPD to access files on SMB/CIFS servers (e.g. Samba + or Microsoft Windows). All URIs with the + <filename>smb://</filename> scheme are used. Example: + </para> + + <para> + <filename>mpc add smb://servername/sharename/filename.ogg</filename> + </para> + </section> + + <section> + <title><varname>alsa</varname></title> + + <para> + Allows MPD on Linux to play audio directly from a soundcard using + the scheme <filename>alsa://</filename>. Audio is formatted as + 44.1 kHz 16-bit stereo (CD format). Examples: + </para> + + <para> + <filename>mpc add alsa://</filename> plays audio from device hw:0,0 + </para> + <para> + <filename>mpc add alsa://hw:1,0</filename> plays audio from device + hw:1,0 + </para> + </section> </section> <section> @@ -1173,6 +1407,35 @@ systemctl start mpd.socket</programlisting> </section> <section> + <title><varname>shine</varname></title> + + <para> + Encodes into MP3 using the shine library. + </para> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>bitrate</varname> + </entry> + <entry> + Sets the bit rate in kilobit per second. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </section> + + <section> <title><varname>twolame</varname></title> <para> @@ -2169,6 +2432,47 @@ systemctl start mpd.socket</programlisting> </para> </section> + <section> + <title><varname>soundcloud</varname></title> + + <para> + Adds <ulink url="https://www.soundcloud.com/">Soundcloud</ulink> + playlists. SoundCloud playlists use the <filename>soundcloud://</filename> URI, + and with a number of arguments, you may load different playlists with + </para> + + <programlisting> +mpc load soundcloud://track/TRACK_ID +mpc load soundcloud://playlist/PLAYLIST_ID +mpc load soundcloud://user/USERNAME +mpc load soundcloud://search/SEARCH_QUERY +mpc load soundcloud://url/https://soundcloud.com/ARTIST/TRACK-NAME + </programlisting> + + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Setting</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <varname>apikey</varname> + <parameter>client_id</parameter> + </entry> + <entry> + User apikey/client_id can override the MPD token provided by SoundCloud. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + + </section> + </section> </chapter> </book> diff --git a/m4/mpd_func.m4 b/m4/mpd_func.m4 index d12d27062..5f2bf8f3d 100644 --- a/m4/mpd_func.m4 +++ b/m4/mpd_func.m4 @@ -10,3 +10,16 @@ AC_DEFUN([MPD_OPTIONAL_FUNC], [ [AC_CHECK_FUNC([$2], [AC_DEFINE([$3], 1, [Define to use $1])],)]) ]) + +dnl MPD_OPTIONAL_FUNC_NODEF(name, func) +dnl +dnl Allow the user to enable or disable the use of a function. +dnl Works similar to MPD_OPTIONAL_FUNC, however MPD_OPTIONAL_FUNC_NODEF +dnl does not invoke AC_DEFINE when function is enabled. Shell variable +dnl enable_$name is set to "yes" instead. +AC_DEFUN([MPD_OPTIONAL_FUNC_NODEF], [ + AC_ARG_ENABLE([$1], + AS_HELP_STRING([--enable-$1], + [use the function "$1" (default: auto)]),, + [AC_CHECK_FUNC([$2], [enable_$1=yes],)]) +]) diff --git a/src/ArchiveDomain.cxx b/src/ArchiveDomain.cxx deleted file mode 100644 index 2beeebcbb..000000000 --- a/src/ArchiveDomain.cxx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "ArchiveDomain.hxx" -#include "util/Domain.hxx" - -const Domain archive_domain("archive"); diff --git a/src/ArchiveDomain.hxx b/src/ArchiveDomain.hxx deleted file mode 100644 index 7bc8a223e..000000000 --- a/src/ArchiveDomain.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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_DOMAIN_HXX -#define MPD_ARCHIVE_DOMAIN_HXX - -extern const class Domain archive_domain; - -#endif diff --git a/src/ArchiveFile.hxx b/src/ArchiveFile.hxx deleted file mode 100644 index 4bdba62ab..000000000 --- a/src/ArchiveFile.hxx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 Mutex; -class Cond; -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 InputStream of a file within the archive. - * - * @param path the path within the archive - */ - virtual InputStream *OpenStream(const char *path, - Mutex &mutex, Cond &cond, - Error &error) = 0; -}; - -#endif diff --git a/src/ArchiveList.cxx b/src/ArchiveList.cxx deleted file mode 100644 index f4a530506..000000000 --- a/src/ArchiveList.cxx +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "util/Macros.hxx" - -#include <string.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 - nullptr -}; - -/** which plugins have been initialized successfully? */ -static bool archive_plugins_enabled[ARRAY_SIZE(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 == nullptr) - return nullptr; - - archive_plugins_for_each_enabled(plugin) - if (plugin->suffixes != nullptr && - string_array_contains(plugin->suffixes, suffix)) - return plugin; - - return nullptr; -} - -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 nullptr; -} - -void archive_plugin_init_all(void) -{ - for (unsigned i = 0; archive_plugins[i] != nullptr; ++i) { - const struct archive_plugin *plugin = archive_plugins[i]; - if (plugin->init == nullptr || archive_plugins[i]->init()) - archive_plugins_enabled[i] = true; - } -} - -void archive_plugin_deinit_all(void) -{ - archive_plugins_for_each_enabled(plugin) - if (plugin->finish != nullptr) - plugin->finish(); -} - diff --git a/src/ArchiveList.hxx b/src/ArchiveList.hxx deleted file mode 100644 index cbf159b2f..000000000 --- a/src/ArchiveList.hxx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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) != nullptr; \ - ++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 deleted file mode 100644 index 7a93c136a..000000000 --- a/src/ArchiveLookup.cxx +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with 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 "ArchiveDomain.hxx" -#include "Log.hxx" - -#include <string.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> - -gcc_pure -static char * -FindSlash(char *p, size_t i) -{ - for (; i > 0; --i) - if (p[i] == '/') - return p + i; - - return nullptr; -} - -gcc_pure -static const char * -FindSuffix(const char *p, const char *i) -{ - for (; i > p; --i) { - if (*i == '.') - return i + 1; - } - - return nullptr; -} - -bool -archive_lookup(char *pathname, const char **archive, - const char **inpath, const char **suffix) -{ - size_t idx = strlen(pathname); - - char *slash = nullptr; - - while (true) { - //try to stat if its real directory - struct stat st_info; - if (stat(pathname, &st_info) == -1) { - if (errno != ENOTDIR) { - FormatErrno(archive_domain, - "Failed to stat %s", pathname); - return false; - } - } else { - //is something found ins original path (is not an archive) - if (slash == nullptr) - return false; - - //its a file ? - if (S_ISREG(st_info.st_mode)) { - //so the upper should be file - *archive = pathname; - *inpath = slash + 1; - - //try to get suffix - *suffix = FindSuffix(pathname, slash - 1); - return true; - } else { - FormatError(archive_domain, - "Not a regular file: %s", - pathname); - return false; - } - } - - //find one dir up - if (slash != nullptr) - *slash = '/'; - - slash = FindSlash(pathname, idx - 1); - if (slash == nullptr) - return false; - - *slash = 0; - idx = slash - pathname; - } -} - diff --git a/src/ArchiveLookup.hxx b/src/ArchiveLookup.hxx deleted file mode 100644 index 0c4da9c93..000000000 --- a/src/ArchiveLookup.hxx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 - -/** - * - * 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, const char **archive, - const char **inpath, const char **suffix); - -#endif - diff --git a/src/ArchivePlugin.cxx b/src/ArchivePlugin.cxx deleted file mode 100644 index 05085fb33..000000000 --- a/src/ArchivePlugin.cxx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 != nullptr); - assert(plugin->open != nullptr); - assert(path != nullptr); - - ArchiveFile *file = plugin->open(path, error); - assert((file == nullptr) == error.IsDefined()); - - return file; -} diff --git a/src/ArchivePlugin.hxx b/src/ArchivePlugin.hxx deleted file mode 100644 index 6439c7242..000000000 --- a/src/ArchivePlugin.hxx +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 - -struct InputStream; -class ArchiveFile; -class ArchiveVisitor; -class Error; - -struct archive_plugin { - const char *name; - - /** - * optional, set this to nullptr 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 nullptr 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 nullptr 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 nullptr - */ - 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 deleted file mode 100644 index e951cb5e9..000000000 --- a/src/ArchiveVisitor.hxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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/AudioConfig.cxx b/src/AudioConfig.cxx index a8fc7aab3..d54f59e17 100644 --- a/src/AudioConfig.cxx +++ b/src/AudioConfig.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,9 +21,9 @@ #include "AudioConfig.hxx" #include "AudioFormat.hxx" #include "AudioParser.hxx" -#include "ConfigData.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" #include "util/Error.hxx" #include "system/FatalError.hxx" diff --git a/src/AudioConfig.hxx b/src/AudioConfig.hxx index ebe202974..471e60e51 100644 --- a/src/AudioConfig.hxx +++ b/src/AudioConfig.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/AudioFormat.cxx b/src/AudioFormat.cxx index 04636c1e2..edfb9d8fe 100644 --- a/src/AudioFormat.cxx +++ b/src/AudioFormat.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/AudioFormat.hxx b/src/AudioFormat.hxx index fae7625ea..0937ab8ae 100644 --- a/src/AudioFormat.hxx +++ b/src/AudioFormat.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/AudioParser.cxx b/src/AudioParser.cxx index a21dcafa7..74bb04abc 100644 --- a/src/AudioParser.cxx +++ b/src/AudioParser.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/AudioParser.hxx b/src/AudioParser.hxx index cb6eb3ca0..07ad7cb4a 100644 --- a/src/AudioParser.hxx +++ b/src/AudioParser.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/AvahiPoll.cxx b/src/AvahiPoll.cxx deleted file mode 100644 index 0d5a43dad..000000000 --- a/src/AvahiPoll.cxx +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "AvahiPoll.hxx" -#include "event/Loop.hxx" -#include "event/SocketMonitor.hxx" -#include "event/TimeoutMonitor.hxx" - -static unsigned -FromAvahiWatchEvent(AvahiWatchEvent e) -{ - return (e & AVAHI_WATCH_IN ? SocketMonitor::READ : 0) | - (e & AVAHI_WATCH_OUT ? SocketMonitor::WRITE : 0) | - (e & AVAHI_WATCH_ERR ? SocketMonitor::ERROR : 0) | - (e & AVAHI_WATCH_HUP ? SocketMonitor::HANGUP : 0); -} - -static AvahiWatchEvent -ToAvahiWatchEvent(unsigned e) -{ - return AvahiWatchEvent((e & SocketMonitor::READ ? AVAHI_WATCH_IN : 0) | - (e & SocketMonitor::WRITE ? AVAHI_WATCH_OUT : 0) | - (e & SocketMonitor::ERROR ? AVAHI_WATCH_ERR : 0) | - (e & SocketMonitor::HANGUP ? AVAHI_WATCH_HUP : 0)); -} - -struct AvahiWatch final : private SocketMonitor { -private: - const AvahiWatchCallback callback; - void *const userdata; - - AvahiWatchEvent received; - -public: - AvahiWatch(int _fd, AvahiWatchEvent _event, - AvahiWatchCallback _callback, void *_userdata, - EventLoop &_loop) - :SocketMonitor(_fd, _loop), - callback(_callback), userdata(_userdata), - received(AvahiWatchEvent(0)) { - Schedule(FromAvahiWatchEvent(_event)); - } - - ~AvahiWatch() { - Steal(); - } - - static void WatchUpdate(AvahiWatch *w, AvahiWatchEvent event) { - w->Schedule(FromAvahiWatchEvent(event)); - } - - static AvahiWatchEvent WatchGetEvents(AvahiWatch *w) { - return w->received; - } - - static void WatchFree(AvahiWatch *w) { - delete w; - } - -protected: - virtual bool OnSocketReady(unsigned flags) { - received = ToAvahiWatchEvent(flags); - callback(this, Get(), received, userdata); - received = AvahiWatchEvent(0); - return true; - } -}; - -static constexpr unsigned -TimevalToMS(const timeval &tv) -{ - return tv.tv_sec * 1000 + (tv.tv_usec + 500) / 1000; -} - -struct AvahiTimeout final : private TimeoutMonitor { -private: - const AvahiTimeoutCallback callback; - void *const userdata; - -public: - AvahiTimeout(const struct timeval *tv, - AvahiTimeoutCallback _callback, void *_userdata, - EventLoop &_loop) - :TimeoutMonitor(_loop), - callback(_callback), userdata(_userdata) { - if (tv != nullptr) - Schedule(TimevalToMS(*tv)); - } - - static void TimeoutUpdate(AvahiTimeout *t, const struct timeval *tv) { - if (tv != nullptr) - t->Schedule(TimevalToMS(*tv)); - else - t->Cancel(); - } - - static void TimeoutFree(AvahiTimeout *t) { - delete t; - } - -protected: - virtual void OnTimeout() { - callback(this, userdata); - } -}; - -MyAvahiPoll::MyAvahiPoll(EventLoop &_loop):event_loop(_loop) -{ - watch_new = WatchNew; - watch_update = AvahiWatch::WatchUpdate; - watch_get_events = AvahiWatch::WatchGetEvents; - watch_free = AvahiWatch::WatchFree; - timeout_new = TimeoutNew; - timeout_update = AvahiTimeout::TimeoutUpdate; - timeout_free = AvahiTimeout::TimeoutFree; -} - -AvahiWatch * -MyAvahiPoll::WatchNew(const AvahiPoll *api, int fd, AvahiWatchEvent event, - AvahiWatchCallback callback, void *userdata) { - const MyAvahiPoll &poll = *(const MyAvahiPoll *)api; - - return new AvahiWatch(fd, event, callback, userdata, - poll.event_loop); -} - -AvahiTimeout * -MyAvahiPoll::TimeoutNew(const AvahiPoll *api, const struct timeval *tv, - AvahiTimeoutCallback callback, void *userdata) { - const MyAvahiPoll &poll = *(const MyAvahiPoll *)api; - - return new AvahiTimeout(tv, callback, userdata, - poll.event_loop); -} diff --git a/src/AvahiPoll.hxx b/src/AvahiPoll.hxx deleted file mode 100644 index 850205c46..000000000 --- a/src/AvahiPoll.hxx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_AVAHI_POLL_HXX -#define MPD_AVAHI_POLL_HXX - -#include "check.h" -#include "Compiler.h" - -#include <avahi-common/watch.h> - -class EventLoop; - -class MyAvahiPoll final : public AvahiPoll { - EventLoop &event_loop; - -public: - MyAvahiPoll(EventLoop &_loop); - -private: - static AvahiWatch *WatchNew(const AvahiPoll *api, int fd, - AvahiWatchEvent event, - AvahiWatchCallback callback, - void *userdata); - - static AvahiTimeout *TimeoutNew(const AvahiPoll *api, - const struct timeval *tv, - AvahiTimeoutCallback callback, - void *userdata); -}; - -#endif diff --git a/src/CheckAudioFormat.cxx b/src/CheckAudioFormat.cxx index 1f3ef4925..03e67e07e 100644 --- a/src/CheckAudioFormat.cxx +++ b/src/CheckAudioFormat.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/CheckAudioFormat.hxx b/src/CheckAudioFormat.hxx index df952adc7..67bd88a61 100644 --- a/src/CheckAudioFormat.hxx +++ b/src/CheckAudioFormat.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/Client.cxx b/src/Client.cxx deleted file mode 100644 index 928747897..000000000 --- a/src/Client.cxx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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"); diff --git a/src/Client.hxx b/src/Client.hxx deleted file mode 100644 index f0bc6b0f7..000000000 --- a/src/Client.hxx +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "check.h" -#include "ClientMessage.hxx" -#include "command/CommandListBuilder.hxx" -#include "event/FullyBufferedSocket.hxx" -#include "event/TimeoutMonitor.hxx" -#include "Compiler.h" - -#include <set> -#include <string> -#include <list> - -#include <stddef.h> -#include <stdarg.h> - -struct sockaddr; -class EventLoop; -struct Partition; - -class Client final : private FullyBufferedSocket, TimeoutMonitor { -public: - Partition &partition; - struct playlist &playlist; - struct PlayerControl &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 IsExpired() const { - return !FullyBufferedSocket::IsDefined(); - } - - void Close(); - void SetExpired(); - - using FullyBufferedSocket::Write; - - /** - * returns the uid of the client process, or a negative value - * if the uid is unknown - */ - int GetUID() const { - return uid; - } - - /** - * Is this client running on the same machine, connected with - * a local (UNIX domain) socket? - */ - bool IsLocal() const { - return uid > 0; - } - - unsigned GetPermission() const { - return permission; - } - - void SetPermission(unsigned _permission) { - permission = _permission; - } - - /** - * Send "idle" response to this client. - */ - void IdleNotify(); - void IdleAdd(unsigned flags); - bool IdleWait(unsigned flags); - - enum class SubscribeResult { - /** success */ - OK, - - /** invalid channel name */ - INVALID, - - /** already subscribed to this channel */ - ALREADY, - - /** too many subscriptions */ - FULL, - }; - - gcc_pure - bool IsSubscribed(const char *channel_name) const { - return subscriptions.find(channel_name) != subscriptions.end(); - } - - SubscribeResult Subscribe(const char *channel); - bool Unsubscribe(const char *channel); - void UnsubscribeAll(); - bool PushMessage(const ClientMessage &msg); - -private: - /* virtual methods from class BufferedSocket */ - virtual InputResult OnSocketInput(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; -}; - -void client_manager_init(void); - -void -client_new(EventLoop &loop, Partition &partition, - int fd, const struct sockaddr *sa, size_t sa_length, int uid); - -/** - * 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_printf(2,3) -void -client_printf(Client &client, const char *fmt, ...); - -#endif diff --git a/src/ClientEvent.cxx b/src/ClientEvent.cxx deleted file mode 100644 index 6c86057a1..000000000 --- a/src/ClientEvent.cxx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index 5954bba8b..000000000 --- a/src/ClientExpire.cxx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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" - -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 deleted file mode 100644 index 382b76083..000000000 --- a/src/ClientFile.cxx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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, 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.GetUID(); - 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 deleted file mode 100644 index b06fbf212..000000000 --- a/src/ClientFile.hxx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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, Path path_fs, Error &error); - -#endif diff --git a/src/ClientGlobal.cxx b/src/ClientGlobal.cxx deleted file mode 100644 index e79f3430b..000000000 --- a/src/ClientGlobal.cxx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index f9778a645..000000000 --- a/src/ClientIdle.cxx +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index c64310093..000000000 --- a/src/ClientInternal.hxx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "command/CommandResult.hxx" - -static constexpr unsigned CLIENT_MAX_SUBSCRIPTIONS = 16; -static constexpr unsigned CLIENT_MAX_MESSAGES = 64; - -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; - -CommandResult -client_process_line(Client &client, char *line); - -#endif diff --git a/src/ClientList.cxx b/src/ClientList.cxx deleted file mode 100644 index 37e6f1289..000000000 --- a/src/ClientList.cxx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index 8c6b15755..000000000 --- a/src/ClientList.hxx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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) {} - ~ClientList() { - CloseAll(); - } - - 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 deleted file mode 100644 index 4b124c6ca..000000000 --- a/src/ClientMessage.cxx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License 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 "util/CharUtil.hxx" -#include "Compiler.h" - -gcc_const -static bool -valid_channel_char(const char ch) -{ - return IsAlphaNumericASCII(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 deleted file mode 100644 index 4579e523e..000000000 --- a/src/ClientMessage.hxx +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.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 deleted file mode 100644 index e84887072..000000000 --- a/src/ClientNew.cxx +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 <glib.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 - -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 deleted file mode 100644 index 485e687c9..000000000 --- a/src/ClientProcess.cxx +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "command/AllCommands.hxx" -#include "Log.hxx" - -#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 CommandResult -client_process_command_list(Client &client, bool list_ok, - std::list<std::string> &&list) -{ - CommandResult ret = CommandResult::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 != CommandResult::OK || client.IsExpired()) - break; - else if (list_ok) - client_puts(client, "list_OK\n"); - } - - return ret; -} - -CommandResult -client_process_line(Client &client, char *line) -{ - CommandResult 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 CommandResult::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 CommandResult::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 == CommandResult::CLOSE || - client.IsExpired()) - return CommandResult::CLOSE; - - if (ret == CommandResult::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 CommandResult::CLOSE; - } - - ret = CommandResult::OK; - } - } else { - if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) { - client.cmd_list.Begin(false); - ret = CommandResult::OK; - } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) { - client.cmd_list.Begin(true); - ret = CommandResult::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 == CommandResult::CLOSE || - client.IsExpired()) - return CommandResult::CLOSE; - - if (ret == CommandResult::OK) - command_success(client); - } - } - - return ret; -} diff --git a/src/ClientRead.cxx b/src/ClientRead.cxx deleted file mode 100644 index 22edefe60..000000000 --- a/src/ClientRead.cxx +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "util/CharUtil.hxx" - -#include <assert.h> -#include <string.h> - -BufferedSocket::InputResult -Client::OnSocketInput(void *data, size_t length) -{ - char *p = (char *)data; - char *newline = (char *)memchr(p, '\n', length); - if (newline == nullptr) - return InputResult::MORE; - - TimeoutMonitor::ScheduleSeconds(client_timeout); - - BufferedSocket::ConsumeInput(newline + 1 - p); - - /* skip whitespace at the end of the line */ - while (newline > p && IsWhitespaceOrNull(newline[-1])) - --newline; - - /* terminate the string at the end of the line */ - *newline = 0; - - CommandResult result = client_process_line(*this, p); - switch (result) { - case CommandResult::OK: - case CommandResult::IDLE: - case CommandResult::ERROR: - break; - - case CommandResult::KILL: - Close(); - main_loop->Break(); - return InputResult::CLOSED; - - case CommandResult::FINISH: - if (Flush()) - Close(); - return InputResult::CLOSED; - - case CommandResult::CLOSE: - Close(); - return InputResult::CLOSED; - } - - if (IsExpired()) { - Close(); - return InputResult::CLOSED; - } - - return InputResult::AGAIN; -} diff --git a/src/ClientSubscribe.cxx b/src/ClientSubscribe.cxx deleted file mode 100644 index 3a9f1b19c..000000000 --- a/src/ClientSubscribe.cxx +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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> -#include <string.h> - - bool Unsubscribe(const char *channel); - void UnsubscribeAll(); - bool PushMessage(const ClientMessage &msg); - -Client::SubscribeResult -Client::Subscribe(const char *channel) -{ - assert(channel != nullptr); - - if (!client_message_valid_channel_name(channel)) - return Client::SubscribeResult::INVALID; - - if (num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS) - return Client::SubscribeResult::FULL; - - auto r = subscriptions.insert(channel); - if (!r.second) - return Client::SubscribeResult::ALREADY; - - ++num_subscriptions; - - idle_add(IDLE_SUBSCRIPTION); - - return Client::SubscribeResult::OK; -} - -bool -Client::Unsubscribe(const char *channel) -{ - const auto i = subscriptions.find(channel); - if (i == subscriptions.end()) - return false; - - assert(num_subscriptions > 0); - - subscriptions.erase(i); - --num_subscriptions; - - idle_add(IDLE_SUBSCRIPTION); - - assert((num_subscriptions == 0) == - subscriptions.empty()); - - return true; -} - -void -Client::UnsubscribeAll() -{ - subscriptions.clear(); - num_subscriptions = 0; -} - -bool -Client::PushMessage(const ClientMessage &msg) -{ - if (messages.size() >= CLIENT_MAX_MESSAGES || - !IsSubscribed(msg.GetChannel())) - return false; - - if (messages.empty()) - IdleAdd(IDLE_MESSAGE); - - messages.push_back(msg); - return true; -} diff --git a/src/ClientWrite.cxx b/src/ClientWrite.cxx deleted file mode 100644 index e66197eb7..000000000 --- a/src/ClientWrite.cxx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/FormatString.hxx" - -#include <string.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) -{ - char *p = FormatNewV(fmt, args); - client_write(client, p, strlen(p)); - delete[] p; -} - -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/CommandLine.cxx b/src/CommandLine.cxx index 05f0a358c..19bc9d4bb 100644 --- a/src/CommandLine.cxx +++ b/src/CommandLine.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,57 +22,101 @@ #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 "config/ConfigGlobal.hxx" +#include "db/Registry.hxx" +#include "db/DatabasePlugin.hxx" +#include "decoder/DecoderList.hxx" +#include "decoder/DecoderPlugin.hxx" +#include "output/OutputList.hxx" +#include "output/OutputPlugin.hxx" +#include "input/Registry.hxx" +#include "input/InputPlugin.hxx" +#include "playlist/PlaylistRegistry.hxx" +#include "playlist/PlaylistPlugin.hxx" #include "fs/AllocatedPath.hxx" #include "fs/Traits.hxx" #include "fs/FileSystem.hxx" +#include "fs/StandardDirectory.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" -#include "system/FatalError.hxx" +#include "util/OptionDef.hxx" +#include "util/OptionParser.hxx" + +#ifdef ENABLE_NEIGHBOR_PLUGINS +#include "neighbor/Registry.hxx" +#include "neighbor/NeighborPlugin.hxx" +#endif #ifdef ENABLE_ENCODER -#include "EncoderList.hxx" -#include "EncoderPlugin.hxx" +#include "encoder/EncoderList.hxx" +#include "encoder/EncoderPlugin.hxx" #endif #ifdef ENABLE_ARCHIVE -#include "ArchiveList.hxx" -#include "ArchivePlugin.hxx" +#include "archive/ArchiveList.hxx" +#include "archive/ArchivePlugin.hxx" #endif -#include <glib.h> - #include <stdio.h> #include <stdlib.h> #ifdef WIN32 -#define CONFIG_FILE_LOCATION "\\mpd\\mpd.conf" +#define CONFIG_FILE_LOCATION "mpd\\mpd.conf" +#define APP_CONFIG_FILE_LOCATION "conf\\mpd.conf" #else #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 const OptionDef opt_kill( + "kill", "kill the currently running mpd session"); +static const OptionDef opt_no_config( + "no-config", "don't read from config"); +static const OptionDef opt_no_daemon( + "no-daemon", "don't detach from console"); +static const OptionDef opt_stdout( + "stdout", nullptr); // hidden, compatibility with old versions +static const OptionDef opt_stderr( + "stderr", "print messages to stderr"); +static const OptionDef opt_verbose( + "verbose", 'v', "verbose logging"); +static const OptionDef opt_version( + "version", 'V', "print version number"); +static const OptionDef opt_help( + "help", 'h', "show help options"); +static const OptionDef opt_help_alt( + nullptr, '?', nullptr); // hidden, standard alias for --help + static constexpr Domain cmdline_domain("cmdline"); gcc_noreturn static void version(void) { - puts("Music Player Daemon " VERSION "\n" + puts("Music Player Daemon " VERSION +#ifdef GIT_COMMIT + " (" GIT_COMMIT ")" +#endif + "\n" "\n" "Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n" - "Copyright (C) 2008-2013 Max Kellermann <max@duempel.org>\n" + "Copyright (C) 2008-2014 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" + "db/Database plugins:"); + + for (auto i = database_plugins; *i != nullptr; ++i) + printf(" %s", (*i)->name); + +#ifdef ENABLE_NEIGHBOR_PLUGINS + puts("\n\n" + "Neighbor plugins:"); + for (auto i = neighbor_plugins; *i != nullptr; ++i) + printf(" %s", (*i)->name); +#endif + + puts("\n\n" "Decoders plugins:"); decoder_plugins_for_each([](const DecoderPlugin &plugin){ @@ -132,122 +176,165 @@ static void version(void) exit(EXIT_SUCCESS); } -static const char *summary = - "Music Player Daemon - a daemon for playing music."; +static void PrintOption(const OptionDef &opt) +{ + if (opt.HasShortOption()) + printf(" -%c, --%-12s%s\n", + opt.GetShortOption(), + opt.GetLongOption(), + opt.GetDescription()); + else + printf(" --%-16s%s\n", + opt.GetLongOption(), + opt.GetDescription()); +} + +gcc_noreturn +static void help(void) +{ + puts("Usage:\n" + " mpd [OPTION...] [path/to/mpd.conf]\n" + "\n" + "Music Player Daemon - a daemon for playing music.\n" + "\n" + "Options:"); + + PrintOption(opt_help); + PrintOption(opt_kill); + PrintOption(opt_no_config); + PrintOption(opt_no_daemon); + PrintOption(opt_stderr); + PrintOption(opt_verbose); + PrintOption(opt_version); + + exit(EXIT_SUCCESS); +} -gcc_pure -static AllocatedPath -PathBuildChecked(const AllocatedPath &a, PathTraits::const_pointer b) +class ConfigLoader { - if (a.IsNull()) - return AllocatedPath::Null(); + Error &error; + bool result; +public: + ConfigLoader(Error &_error) : error(_error), result(false) { } - return AllocatedPath::Build(a, b); + bool GetResult() const { return result; } + + bool TryFile(const Path path); + bool TryFile(const AllocatedPath &base_path, + PathTraitsFS::const_pointer path); +}; + +bool ConfigLoader::TryFile(Path path) +{ + if (FileExists(path)) { + result = ReadConfigFile(path, error); + return true; + } + return false; +} + +bool ConfigLoader::TryFile(const AllocatedPath &base_path, + PathTraitsFS::const_pointer path) +{ + if (base_path.IsNull()) + return false; + auto full_path = AllocatedPath::Build(base_path, path); + return TryFile(full_path); } 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", nullptr }, - { "no-config", 0, 0, G_OPTION_ARG_NONE, &option_no_config, - "don't read from config", nullptr }, - { "no-daemon", 0, 0, G_OPTION_ARG_NONE, &option_no_daemon, - "don't detach from console", nullptr }, - { "stdout", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr, - nullptr, nullptr }, - { "stderr", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr, - "print messages to stderr", nullptr }, - { "verbose", 'v', 0, G_OPTION_ARG_NONE, &options->verbose, - "verbose logging", nullptr }, - { "version", 'V', 0, G_OPTION_ARG_NONE, &option_version, - "print version number", nullptr }, - { nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr } - }; - + bool use_config_file = true; 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, nullptr); - - 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); + // First pass: handle command line options + OptionParser parser(argc, argv); + while (parser.HasEntries()) { + if (!parser.ParseNext()) + continue; + if (parser.CheckOption(opt_kill)) { + options->kill = true; + continue; + } + if (parser.CheckOption(opt_no_config)) { + use_config_file = false; + continue; + } + if (parser.CheckOption(opt_no_daemon)) { + options->daemon = false; + continue; + } + if (parser.CheckOption(opt_stderr, opt_stdout)) { + options->log_stderr = true; + continue; + } + if (parser.CheckOption(opt_verbose)) { + options->verbose = true; + continue; + } + if (parser.CheckOption(opt_version)) + version(); + if (parser.CheckOption(opt_help, opt_help_alt)) + help(); - if (option_version) - version(); + error.Format(cmdline_domain, "invalid option: %s", + parser.GetOption()); + return false; + } /* 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) { + if (!use_config_file) { LogDebug(cmdline_domain, "Ignoring config, using daemon defaults"); return true; - } else if (argc <= 1) { - /* default configuration file path */ + } -#ifdef WIN32 - AllocatedPath path = PathBuildChecked(AllocatedPath::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(AllocatedPath::FromUTF8(system_config_dirs[i]), - CONFIG_FILE_LOCATION); - if (!path.IsNull() && FileExists(path)) - return ReadConfigFile(path, error); + // Second pass: find non-option parameters (i.e. config file) + const char *config_file = nullptr; + for (int i = 1; i < argc; ++i) { + if (OptionParser::IsOption(argv[i])) + continue; + if (config_file == nullptr) { + config_file = argv[i]; + continue; } + error.Set(cmdline_domain, "too many arguments"); + return false; + } + + if (config_file != nullptr) { + /* use specified configuration file */ + return ReadConfigFile(Path::FromFS(config_file), error); + } + + /* use default configuration file path */ + + ConfigLoader loader(error); + + bool found = +#ifdef WIN32 + loader.TryFile(GetUserConfigDir(), CONFIG_FILE_LOCATION) || + loader.TryFile(GetSystemConfigDir(), CONFIG_FILE_LOCATION) || + loader.TryFile(GetAppBaseDir(), APP_CONFIG_FILE_LOCATION); #else - AllocatedPath path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_user_config_dir()), - USER_CONFIG_FILE_LOCATION_XDG); - if (!path.IsNull() && FileExists(path)) - return ReadConfigFile(path, error); - - path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_home_dir()), - USER_CONFIG_FILE_LOCATION1); - if (!path.IsNull() && FileExists(path)) - return ReadConfigFile(path, error); - - path = PathBuildChecked(AllocatedPath::FromUTF8(g_get_home_dir()), - USER_CONFIG_FILE_LOCATION2); - if (!path.IsNull() && FileExists(path)) - return ReadConfigFile(path, error); - - path = AllocatedPath::FromUTF8(SYSTEM_CONFIG_FILE_LOCATION); - if (!path.IsNull() && FileExists(path)) - return ReadConfigFile(path, error); + loader.TryFile(GetUserConfigDir(), + USER_CONFIG_FILE_LOCATION_XDG) || + loader.TryFile(GetHomeDir(), USER_CONFIG_FILE_LOCATION1) || + loader.TryFile(GetHomeDir(), USER_CONFIG_FILE_LOCATION2) || + loader.TryFile(Path::FromFS(SYSTEM_CONFIG_FILE_LOCATION)); #endif - + if (!found) { 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; } + + return loader.GetResult(); } diff --git a/src/CommandLine.hxx b/src/CommandLine.hxx index 214150eae..d8dedb1fb 100644 --- a/src/CommandLine.hxx +++ b/src/CommandLine.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,15 +20,13 @@ #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 kill; + bool daemon; + bool log_stderr; + bool verbose; }; bool diff --git a/src/Compiler.h b/src/Compiler.h index 94abdcff3..44a87c7ba 100644 --- a/src/Compiler.h +++ b/src/Compiler.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/ConfigData.cxx b/src/ConfigData.cxx deleted file mode 100644 index c6cabee6e..000000000 --- a/src/ConfigData.cxx +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/AllocatedPath.hxx" -#include "system/FatalError.hxx" - -#include <assert.h> -#include <string.h> -#include <stdlib.h> - -int -block_param::GetIntValue() const -{ - char *endptr; - long value2 = strtol(value.c_str(), &endptr, 0); - if (*endptr != 0) - FormatFatalError("Not a valid number in line %i", line); - - return value2; -} - -unsigned -block_param::GetUnsignedValue() const -{ - char *endptr; - unsigned long value2 = strtoul(value.c_str(), &endptr, 0); - if (*endptr != 0) - FormatFatalError("Not a valid 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(_value), line(_line) {} - -config_param::~config_param() -{ - delete next; -} - -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(); -} - -AllocatedPath -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 AllocatedPath::Null(); - - s = default_value; - } - - AllocatedPath path = ParsePath(s, error); - if (gcc_unlikely(path.IsNull())) - error.FormatPrefix("Invalid path in \"%s\" at line %i: ", - name, line2); - - return path; -} - -AllocatedPath -config_param::GetBlockPath(const char *name, Error &error) const -{ - return GetBlockPath(name, nullptr, error); -} - -int -config_param::GetBlockValue(const char *name, int default_value) const -{ - const block_param *bp = GetBlockParam(name); - if (bp == nullptr) - return default_value; - - return bp->GetIntValue(); -} - -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 deleted file mode 100644 index b41b27420..000000000 --- a/src/ConfigData.hxx +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.h" - -#include <string> -#include <array> -#include <vector> - -class AllocatedPath; -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 - int GetIntValue() const; - - 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; - - std::string 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), 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; - - /** - * Same as config_dup_path(), but looks up the setting in the - * specified block. - */ - AllocatedPath GetBlockPath(const char *name, const char *default_value, - Error &error) const; - - AllocatedPath GetBlockPath(const char *name, Error &error) const; - - gcc_pure - int GetBlockValue(const char *name, int default_value) 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 deleted file mode 100644 index 9cfe61582..000000000 --- a/src/ConfigDefaults.hxx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index bd529e670..000000000 --- a/src/ConfigError.cxx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License 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 deleted file mode 100644 index a8917d7d1..000000000 --- a/src/ConfigError.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 90859a89a..000000000 --- a/src/ConfigFile.cxx +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/Limits.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 == nullptr) { - assert(!tokenizer.IsEnd()); - return false; - } - - const char *value = tokenizer.NextString(error); - if (value == nullptr) { - 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 != nullptr) { - 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 == nullptr) { - delete ret; - error.Set(config_file_domain, - "Expected '}' before end-of-file"); - return nullptr; - } - - (*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 nullptr; - } - } -} - -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 == nullptr) { - 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 == nullptr) { - return false; - } - } else { - /* a string value */ - - value = tokenizer.NextString(error); - if (value == nullptr) { - 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, 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 deleted file mode 100644 index 8c4ddbbd3..000000000 --- a/src/ConfigFile.hxx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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, Path path, Error &error); - -#endif diff --git a/src/ConfigGlobal.cxx b/src/ConfigGlobal.cxx deleted file mode 100644 index 49b9c08fb..000000000 --- a/src/ConfigGlobal.cxx +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "fs/AllocatedPath.hxx" -#include "util/Error.hxx" -#include "system/FatalError.hxx" -#include "Log.hxx" - -#include <assert.h> -#include <string.h> -#include <stdlib.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(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.c_str(); -} - -AllocatedPath -config_get_path(ConfigOption option, Error &error) -{ - const struct config_param *param = config_get_param(option); - if (param == nullptr) - return AllocatedPath::Null(); - - return config_parse_path(param, error); -} - -AllocatedPath -config_parse_path(const struct config_param *param, Error & error) -{ - AllocatedPath path = ParsePath(param->value.c_str(), 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.c_str(), &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.c_str(), &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.c_str(), &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 deleted file mode 100644 index 978db3145..000000000 --- a/src/ConfigGlobal.hxx +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.h" - -class Error; -class Path; -class AllocatedPath; - -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(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 AllocatedPath::Null() if the value is not present. If the path - * could not be parsed, returns AllocatedPath::Null() and sets the error. - */ -AllocatedPath -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 AllocatedPath::Null() and sets the error. - */ -AllocatedPath -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 deleted file mode 100644 index e8a2a86c8..000000000 --- a/src/ConfigOption.hxx +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.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 deleted file mode 100644 index 73381d8a0..000000000 --- a/src/ConfigParser.cxx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License 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 deleted file mode 100644 index 00fd42fe3..000000000 --- a/src/ConfigParser.hxx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index b88de3934..000000000 --- a/src/ConfigPath.cxx +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/AllocatedPath.hxx" -#include "fs/Traits.hxx" -#include "fs/Domain.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 AllocatedPath -GetHome(const char *user, Error &error) -{ - passwd *pw = getpwnam(user); - if (pw == nullptr) { - error.Format(path_domain, - "no such user: %s", user); - return AllocatedPath::Null(); - } - - return AllocatedPath::FromFS(pw->pw_dir); -} - -/** - * Determine the current user's home directory. - */ -static AllocatedPath -GetHome(Error &error) -{ - const char *home = g_get_home_dir(); - if (home == nullptr) { - error.Set(path_domain, - "problems getting home for current user"); - return AllocatedPath::Null(); - } - - return AllocatedPath::FromUTF8(home, error); -} - -/** - * Determine the configured user's home directory. - */ -static AllocatedPath -GetConfiguredHome(Error &error) -{ - const char *user = config_get_string(CONF_USER, nullptr); - return user != nullptr - ? GetHome(user, error) - : GetHome(error); -} - -#endif - -AllocatedPath -ParsePath(const char *path, Error &error) -{ - assert(path != nullptr); - -#ifndef WIN32 - if (path[0] == '~') { - ++path; - - if (*path == '\0') - return GetConfiguredHome(error); - - AllocatedPath home = AllocatedPath::Null(); - - if (*path == '/') { - home = GetConfiguredHome(error); - - ++path; - } else { - const char *slash = strchr(path, '/'); - const char *end = slash == nullptr - ? path + strlen(path) - : slash; - const std::string user(path, end); - home = GetHome(user.c_str(), error); - - if (slash == nullptr) - return home; - - path = slash + 1; - } - - if (home.IsNull()) - return AllocatedPath::Null(); - - AllocatedPath path2 = AllocatedPath::FromUTF8(path, error); - if (path2.IsNull()) - return AllocatedPath::Null(); - - return AllocatedPath::Build(home, path2); - } else if (!PathTraits::IsAbsoluteUTF8(path)) { - error.Format(path_domain, - "not an absolute path: %s", path); - return AllocatedPath::Null(); - } else { -#endif - return AllocatedPath::FromUTF8(path, error); -#ifndef WIN32 - } -#endif -} diff --git a/src/ConfigPath.hxx b/src/ConfigPath.hxx deleted file mode 100644 index 99abd85b7..000000000 --- a/src/ConfigPath.hxx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 AllocatedPath; -class Error; - -AllocatedPath -ParsePath(const char *path, Error &error); - -#endif diff --git a/src/ConfigTemplates.cxx b/src/ConfigTemplates.cxx deleted file mode 100644 index 6c6bf1689..000000000 --- a/src/ConfigTemplates.cxx +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License 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 deleted file mode 100644 index 4f5460460..000000000 --- a/src/ConfigTemplates.hxx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 index 601d74dc2..bb585db5a 100644 --- a/src/CrossFade.cxx +++ b/src/CrossFade.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,8 +26,6 @@ #include "Log.hxx" #include <assert.h> -#include <string.h> -#include <stdlib.h> static constexpr Domain cross_fade_domain("cross_fade"); diff --git a/src/CrossFade.hxx b/src/CrossFade.hxx index c47db84e1..b385ea316 100644 --- a/src/CrossFade.hxx +++ b/src/CrossFade.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/Daemon.cxx b/src/Daemon.cxx deleted file mode 100644 index 557c47777..000000000 --- a/src/Daemon.cxx +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/AllocatedPath.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 = (gid_t)-1; - -/** the absolute path of the pidfile */ -static AllocatedPath pidfile = AllocatedPath::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() && - setgid(user_gid) == -1) { - FormatFatalSystemError("Failed to set group %d", - (int)user_gid); - } - -#ifdef _BSD_SOURCE - /* init supplementary groups - * (must be done before we change our uid) - */ - if (!had_group && - /* no need to set the new user's supplementary groups if - we are already this user */ - user_uid != getuid() && - 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, AllocatedPath &&_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 = AllocatedPath::Null(); - } - - g_free(user_name); -} - -#endif diff --git a/src/Daemon.hxx b/src/Daemon.hxx deleted file mode 100644 index ecd090d5b..000000000 --- a/src/Daemon.hxx +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 AllocatedPath; - -#ifndef WIN32 -void -daemonize_init(const char *user, const char *group, AllocatedPath &&pidfile); -#else -static inline void -daemonize_init(const char *user, const char *group, AllocatedPath &&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/DatabaseError.cxx b/src/DatabaseError.cxx deleted file mode 100644 index 800442b9f..000000000 --- a/src/DatabaseError.cxx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index c133ee357..000000000 --- a/src/DatabaseError.hxx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 013a3e329..000000000 --- a/src/DatabaseGlue.cxx +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 <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 == nullptr); - 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 == nullptr) { - error.Format(db_domain, - "No such database plugin: %s", plugin_name); - return false; - } - - db = plugin->create(param, error); - return db != nullptr; -} - -void -DatabaseGlobalDeinit(void) -{ - if (db_is_open) - db->Close(); - - if (db != nullptr) - delete db; -} - -const Database * -GetDatabase() -{ - assert(db == nullptr || 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 == nullptr || db_is_open); - - return is_simple; -} - -Directory * -db_get_root(void) -{ - assert(db != nullptr); - assert(db_is_simple()); - - return ((SimpleDatabase *)db)->GetRoot(); -} - -Directory * -db_get_directory(const char *name) -{ - if (db == nullptr) - return nullptr; - - Directory *music_root = db_get_root(); - if (name == nullptr) - return music_root; - - return music_root->LookupDirectory(name); -} - -bool -db_save(Error &error) -{ - assert(db != nullptr); - assert(db_is_open); - assert(db_is_simple()); - - return ((SimpleDatabase *)db)->Save(error); -} - -bool -DatabaseGlobalOpen(Error &error) -{ - assert(db != nullptr); - assert(!db_is_open); - - if (!db->Open(error)) - return false; - - db_is_open = true; - - stats_update(); - - return true; -} - -bool -db_exists() -{ - assert(db != nullptr); - assert(db_is_open); - assert(db_is_simple()); - - return ((SimpleDatabase *)db)->GetUpdateStamp() > 0; -} diff --git a/src/DatabaseGlue.hxx b/src/DatabaseGlue.hxx deleted file mode 100644 index bc05942e0..000000000 --- a/src/DatabaseGlue.hxx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.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 nullptr if this MPD - * configuration has no database (no music_directory was configured). - */ -gcc_pure -const Database * -GetDatabase(); - -/** - * Returns the global #Database instance. May return nullptr 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 deleted file mode 100644 index 4202b1247..000000000 --- a/src/DatabaseHelpers.cxx +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License 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, TagType 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, - TagType 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 deleted file mode 100644 index d8806bc69..000000000 --- a/src/DatabaseHelpers.hxx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.h" - -class Error; -class Database; -struct DatabaseSelection; -struct DatabaseStats; - -bool -VisitUniqueTags(const Database &db, const DatabaseSelection &selection, - TagType 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 deleted file mode 100644 index d85f72d3b..000000000 --- a/src/DatabaseLock.cxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "Compiler.h" - -Mutex db_mutex; - -#ifndef NDEBUG -ThreadId db_mutex_holder; -#endif diff --git a/src/DatabaseLock.hxx b/src/DatabaseLock.hxx deleted file mode 100644 index 1bd5cbe61..000000000 --- a/src/DatabaseLock.hxx +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 "Compiler.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 deleted file mode 100644 index b0cb19589..000000000 --- a/src/DatabasePlaylist.cxx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index d5f1648d5..000000000 --- a/src/DatabasePlaylist.hxx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.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 deleted file mode 100644 index ccf899389..000000000 --- a/src/DatabasePlugin.hxx +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 "Compiler.h" - -#include <time.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, - TagType tag_type, - VisitString visit_string, - Error &error) const = 0; - - virtual bool GetStats(const DatabaseSelection &selection, - DatabaseStats &stats, - Error &error) const = 0; - - /** - * Returns the time stamp of the last database update. - * Returns 0 if that is not not known/available. - */ - gcc_pure - virtual time_t GetUpdateStamp() 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 deleted file mode 100644 index 3732e98f3..000000000 --- a/src/DatabasePrint.cxx +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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, const Song &song) -{ - assert(song.parent != nullptr); - - song_print_uri(client, song); - - if (song.tag != nullptr && 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, const Song &song) -{ - assert(song.parent != nullptr); - - song_print_info(client, song); - - if (song.tag != nullptr && 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, - std::ref(client), _1) - : VisitDirectory(); - const auto s = std::bind(full ? PrintSongFull : PrintSongBrief, - std::ref(client), _1); - const auto p = selection.filter == nullptr - ? std::bind(full ? PrintPlaylistFull : PrintPlaylistBrief, - std::ref(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, TagType 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, - std::ref(client), _1); - return db->Visit(selection, f, error); - } else { - using namespace std::placeholders; - const auto f = std::bind(PrintUniqueTag, std::ref(client), - (TagType)type, _1); - return db->VisitUniqueTags(selection, (TagType)type, - f, error); - } -} diff --git a/src/DatabasePrint.hxx b/src/DatabasePrint.hxx deleted file mode 100644 index 36a68d87b..000000000 --- a/src/DatabasePrint.hxx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.h" - -class SongFilter; -struct DatabaseSelection; -struct db_visitor; -class Client; -class Error; - -bool -db_selection_print(Client &client, const DatabaseSelection &selection, - bool full, Error &error); - -gcc_nonnull(2) -bool -printAllIn(Client &client, const char *uri_utf8, Error &error); - -gcc_nonnull(2) -bool -printInfoForAllIn(Client &client, const char *uri_utf8, - Error &error); - -gcc_nonnull(2) -bool -searchStatsForSongsIn(Client &client, const char *name, - const SongFilter *filter, - Error &error); - -bool -listAllUniqueTags(Client &client, int type, - const SongFilter *filter, - Error &error); - -#endif diff --git a/src/DatabaseQueue.cxx b/src/DatabaseQueue.cxx deleted file mode 100644 index 98f80c5b6..000000000 --- a/src/DatabaseQueue.cxx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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) -{ - PlaylistResult result = - partition.playlist.AppendSong(partition.pc, &song, nullptr); - if (result != PlaylistResult::SUCCESS) { - error.Set(playlist_domain, int(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 deleted file mode 100644 index 4faa708c2..000000000 --- a/src/DatabaseQueue.hxx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 4ce4a62cb..000000000 --- a/src/DatabaseRegistry.cxx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 - nullptr -}; - -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 deleted file mode 100644 index d8c5dff12..000000000 --- a/src/DatabaseRegistry.hxx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.h" - -struct DatabasePlugin; - -/** - * nullptr 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 deleted file mode 100644 index abfd4a34f..000000000 --- a/src/DatabaseSave.cxx +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/Charset.hxx" -#include "util/Error.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.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: " - -static constexpr unsigned DB_FORMAT = 1; - -void -db_save_internal(FILE *fp, const Directory &music_root) -{ - 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, GetFSCharset()); - - 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; - unsigned format = 0; - bool found_charset = false, found_version = false; - bool success; - bool tags[TAG_NUM_OF_ITEM_TYPES]; - - /* get initial info */ - line = file.ReadLine(); - if (line == nullptr || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) { - error.Set(db_domain, "Database corrupted"); - return false; - } - - memset(tags, false, sizeof(tags)); - - while ((line = file.ReadLine()) != nullptr && - 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 char *const old_charset = GetFSCharset(); - if (*old_charset != 0 - && strcmp(new_charset, old_charset) != 0) { - error.Format(db_domain, - "Existing database has charset " - "\"%s\" instead of \"%s\"; " - "discarding database file", - new_charset, old_charset); - return false; - } - } else if (g_str_has_prefix(line, DB_TAG_PREFIX)) { - const char *name = line + sizeof(DB_TAG_PREFIX) - 1; - TagType 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 deleted file mode 100644 index 741189973..000000000 --- a/src/DatabaseSave.hxx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 96018cbae..000000000 --- a/src/DatabaseSelection.cxx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License 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" - -DatabaseSelection::DatabaseSelection(const char *_uri, bool _recursive, - const SongFilter *_filter) - :uri(_uri), recursive(_recursive), filter(_filter) -{ - /* optimization: if the caller didn't specify a base URI, pick - the one from SongFilter */ - if (uri.empty() && filter != nullptr) - uri = filter->GetBase(); -} - -bool -DatabaseSelection::Match(const Song &song) const -{ - return filter == nullptr || filter->Match(song); -} diff --git a/src/DatabaseSelection.hxx b/src/DatabaseSelection.hxx deleted file mode 100644 index 6c1a1dc8d..000000000 --- a/src/DatabaseSelection.hxx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.h" - -#include <string> - -class SongFilter; -struct Song; - -struct DatabaseSelection { - /** - * The base URI of the search (UTF-8). Must not begin or end - * with a slash. An empty string searches the whole database. - */ - std::string uri; - - /** - * Recursively search all sub directories? - */ - bool recursive; - - const SongFilter *filter; - - DatabaseSelection(const char *_uri, bool _recursive, - const SongFilter *_filter=nullptr); - - gcc_pure - bool Match(const Song &song) const; -}; - -#endif diff --git a/src/DatabaseSimple.hxx b/src/DatabaseSimple.hxx deleted file mode 100644 index 6d52ac0b3..000000000 --- a/src/DatabaseSimple.hxx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.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); - -/** - * Returns true if there is a valid database file on the disk. - * - * May only be used if db_is_simple() returns true. - */ -gcc_pure -bool -db_exists(); - -#endif diff --git a/src/DatabaseVisitor.hxx b/src/DatabaseVisitor.hxx deleted file mode 100644 index 256b08442..000000000 --- a/src/DatabaseVisitor.hxx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 4fea02bef..000000000 --- a/src/DecoderAPI.cxx +++ /dev/null @@ -1,547 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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> -#include <math.h> - -void -decoder_initialized(Decoder &decoder, - const AudioFormat audio_format, - bool seekable, float total_time) -{ - DecoderControl &dc = decoder.dc; - struct audio_format_string af_string; - - assert(dc.state == DecoderState::START); - assert(dc.pipe != nullptr); - assert(decoder.stream_tag == nullptr); - assert(decoder.decoder_tag == nullptr); - 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(Decoder &decoder) -{ - const DecoderControl &dc = decoder.dc; - assert(dc.pipe != nullptr); - - 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(Decoder &decoder) -{ - const DecoderControl &dc = decoder.dc; - assert(dc.pipe != nullptr); - - if (decoder_prepare_initial_seek(decoder)) - return DecoderCommand::SEEK; - - return dc.command; -} - -DecoderCommand -decoder_get_command(Decoder &decoder) -{ - return decoder_get_virtual_command(decoder); -} - -void -decoder_command_finished(Decoder &decoder) -{ - DecoderControl &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 != nullptr); - - if (decoder.initial_seek_running) { - assert(!decoder.seeking); - assert(decoder.chunk == nullptr); - 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 != nullptr) { - dc.buffer->Return(decoder.chunk); - decoder.chunk = nullptr; - } - - 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 Decoder & decoder) -{ - const DecoderControl &dc = decoder.dc; - - assert(dc.pipe != nullptr); - - 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(Decoder & decoder) -{ - DecoderControl &dc = decoder.dc; - - assert(dc.pipe != nullptr); - - 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 Decoder *decoder) -{ - if (decoder == nullptr) - return false; - - const DecoderControl &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(Decoder *decoder, - InputStream &is, - void *buffer, size_t length) -{ - /* XXX don't allow decoder==nullptr */ - - assert(decoder == nullptr || - decoder->dc.state == DecoderState::START || - decoder->dc.state == DecoderState::DECODE); - assert(buffer != nullptr); - - 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()); - - is.Unlock(); - - if (gcc_unlikely(nbytes == 0 && error.IsDefined())) - LogError(error); - - return nbytes; -} - -void -decoder_timestamp(Decoder &decoder, double t) -{ - 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(Decoder &decoder, const Tag &tag) -{ - struct music_chunk *chunk; - - if (decoder.chunk != nullptr) { - /* there is a partial chunk - flush it, we want the - tag in a new chunk */ - decoder_flush_chunk(decoder); - } - - assert(decoder.chunk == nullptr); - - chunk = decoder_get_chunk(decoder); - if (chunk == nullptr) { - assert(decoder.dc.command != DecoderCommand::NONE); - return decoder.dc.command; - } - - chunk->tag = new Tag(tag); - return DecoderCommand::NONE; -} - -static bool -update_stream_tag(Decoder &decoder, InputStream *is) -{ - Tag *tag; - - tag = is != nullptr - ? is->LockReadTag() - : nullptr; - if (tag == nullptr) { - tag = decoder.song_tag; - if (tag == nullptr) - return false; - - /* no stream tag present - submit the song tag - instead */ - decoder.song_tag = nullptr; - } - - delete decoder.stream_tag; - decoder.stream_tag = tag; - return true; -} - -DecoderCommand -decoder_data(Decoder &decoder, - InputStream *is, - const void *data, size_t length, - uint16_t kbit_rate) -{ - DecoderControl &dc = decoder.dc; - DecoderCommand cmd; - - assert(dc.state == DecoderState::DECODE); - assert(dc.pipe != nullptr); - 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 != nullptr) { - /* 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 == nullptr) { - /* 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; - bool full; - - chunk = decoder_get_chunk(decoder); - if (chunk == nullptr) { - assert(dc.command != DecoderCommand::NONE); - return dc.command; - } - - const auto dest = - chunk->Write(dc.out_audio_format, - decoder.timestamp - - dc.song->start_ms / 1000.0, - kbit_rate); - if (dest.IsNull()) { - /* the chunk is full, flush it */ - decoder_flush_chunk(decoder); - continue; - } - - size_t nbytes = dest.size; - assert(nbytes > 0); - - if (nbytes > length) - nbytes = length; - - /* copy the buffer */ - - memcpy(dest.data, 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); - } - - 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(Decoder &decoder, InputStream *is, - Tag &&tag) -{ - gcc_unused const DecoderControl &dc = decoder.dc; - DecoderCommand cmd; - - assert(dc.state == DecoderState::DECODE); - assert(dc.pipe != nullptr); - - /* 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 != nullptr) { - /* 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(Decoder &decoder, - const ReplayGainInfo *replay_gain_info) -{ - if (replay_gain_info != nullptr) { - static unsigned serial; - if (++serial == 0) - serial = 1; - - if (REPLAY_GAIN_OFF != replay_gain_mode) { - ReplayGainMode rgm = replay_gain_mode; - if (rgm != REPLAY_GAIN_ALBUM) - rgm = REPLAY_GAIN_TRACK; - - const auto &tuple = replay_gain_info->tuples[rgm]; - const auto scale = - tuple.CalculateScale(replay_gain_preamp, - replay_gain_missing_preamp, - replay_gain_limit); - decoder.dc.replay_gain_db = 20.0 * log10f(scale); - } - - decoder.replay_gain_info = *replay_gain_info; - decoder.replay_gain_serial = serial; - - if (decoder.chunk != nullptr) { - /* flush the current chunk because the new - replay gain values affect the following - samples */ - decoder_flush_chunk(decoder); - } - } else - decoder.replay_gain_serial = 0; -} - -void -decoder_mixramp(Decoder &decoder, MixRampInfo &&mix_ramp) -{ - DecoderControl &dc = decoder.dc; - - dc.SetMixRamp(std::move(mix_ramp)); -} diff --git a/src/DecoderAPI.hxx b/src/DecoderAPI.hxx deleted file mode 100644 index 2ee42483c..000000000 --- a/src/DecoderAPI.hxx +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 "MixRampInfo.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(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 - */ -gcc_pure -DecoderCommand -decoder_get_command(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(Decoder &decoder); - -/** - * Call this when you have received the DecoderCommand::SEEK command. - * - * @param decoder the decoder object - * @return the destination position for the week - */ -gcc_pure -double -decoder_seek_where(Decoder &decoder); - -/** - * Call this instead of decoder_command_finished() when seeking has - * failed. - * - * @param decoder the decoder object - */ -void -decoder_seek_error(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(Decoder *decoder, InputStream &is, - void *buffer, size_t length); - -static inline size_t -decoder_read(Decoder &decoder, InputStream &is, - void *buffer, size_t length) -{ - return decoder_read(&decoder, is, buffer, 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(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(Decoder &decoder, InputStream *is, - const void *data, size_t length, - uint16_t kbit_rate); - -static inline DecoderCommand -decoder_data(Decoder &decoder, InputStream &is, - const void *data, size_t length, - uint16_t kbit_rate) -{ - return decoder_data(decoder, &is, data, length, 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(Decoder &decoder, InputStream *is, Tag &&tag); - -static inline DecoderCommand -decoder_tag(Decoder &decoder, InputStream &is, Tag &&tag) -{ - return decoder_tag(decoder, &is, std::move(tag)); -} - -/** - * Set replay gain values for the following chunks. - * - * @param decoder the decoder object - * @param rgi the replay_gain_info object; may be nullptr to invalidate - * the previous replay gain values - */ -void -decoder_replay_gain(Decoder &decoder, - const ReplayGainInfo *replay_gain_info); - -/** - * Store MixRamp tags. - * - * @param decoder the decoder object - * @param mixramp_start the mixramp_start tag; may be nullptr to invalidate - * @param mixramp_end the mixramp_end tag; may be nullptr to invalidate - */ -void -decoder_mixramp(Decoder &decoder, MixRampInfo &&mix_ramp); - -#endif diff --git a/src/DecoderBuffer.cxx b/src/DecoderBuffer.cxx deleted file mode 100644 index 6aad53cb2..000000000 --- a/src/DecoderBuffer.cxx +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 { - Decoder *decoder; - InputStream *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(Decoder *decoder, InputStream &is, - size_t size) -{ - DecoderBuffer *buffer = (DecoderBuffer *) - g_malloc(sizeof(*buffer) - sizeof(buffer->data) + size); - - 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 deleted file mode 100644 index 92cc31aa4..000000000 --- a/src/DecoderBuffer.hxx +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 InputStream; - -/** - * Creates a new buffer. - * - * @param decoder the decoder object, used for decoder_read(), may be nullptr - * @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(Decoder *decoder, InputStream &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 nullptr 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 deleted file mode 100644 index 394f270c2..000000000 --- a/src/DecoderCommand.hxx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index ab460ced0..000000000 --- a/src/DecoderControl.cxx +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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> - -DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond) - :mutex(_mutex), client_cond(_client_cond), - state(DecoderState::STOP), - command(DecoderCommand::NONE), - client_is_waiting(false), - song(nullptr), - replay_gain_db(0), replay_gain_prev_db(0) {} - -DecoderControl::~DecoderControl() -{ - ClearError(); - - if (song != nullptr) - song->Free(); -} - -void -DecoderControl::WaitForDecoder() -{ - assert(!client_is_waiting); - client_is_waiting = true; - - client_cond.wait(mutex); - - assert(client_is_waiting); - client_is_waiting = false; -} - -bool -DecoderControl::IsCurrentSong(const Song &_song) const -{ - switch (state) { - case DecoderState::STOP: - case DecoderState::ERROR: - return false; - - case DecoderState::START: - case DecoderState::DECODE: - return SongEquals(*song, _song); - } - - assert(false); - gcc_unreachable(); -} - -void -DecoderControl::Start(Song *_song, - unsigned _start_ms, unsigned _end_ms, - MusicBuffer &_buffer, MusicPipe &_pipe) -{ - assert(_song != nullptr); - 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 -DecoderControl::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 -DecoderControl::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 -DecoderControl::Quit() -{ - assert(thread.IsDefined()); - - quit = true; - LockAsynchronousCommand(DecoderCommand::STOP); - - thread.Join(); -} - -void -DecoderControl::CycleMixRamp() -{ - previous_mix_ramp = std::move(mix_ramp); - mix_ramp.Clear(); -} diff --git a/src/DecoderControl.hxx b/src/DecoderControl.hxx deleted file mode 100644 index 863398dca..000000000 --- a/src/DecoderControl.hxx +++ /dev/null @@ -1,395 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "MixRampInfo.hxx" -#include "thread/Mutex.hxx" -#include "thread/Cond.hxx" -#include "thread/Thread.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; - -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 DecoderControl { - /** - * The handle of the decoder thread. - */ - Thread thread; - - /** - * This lock protects #state and #command. - * - * This is usually a reference to PlayerControl::mutex, so - * that both player thread and decoder thread share a mutex. - * This simplifies synchronization with #cond and - * #client_cond. - */ - 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. - * - * This is usually a reference to PlayerControl::cond. - */ - 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; - - /** - * Is the client currently waiting for the DecoderThread? If - * false, the DecoderThread may omit invoking Cond::signal(), - * reducing the number of system calls. - */ - bool client_is_waiting; - - 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; - - MixRampInfo mix_ramp, previous_mix_ramp; - - /** - * @param _mutex see #mutex - * @param _client_cond see #client_cond - */ - DecoderControl(Mutex &_mutex, Cond &_client_cond); - ~DecoderControl(); - - /** - * 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 #DecoderControl 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. - * - * Caller must hold the lock. - */ - void WaitForDecoder(); - - 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 #DecoderControl - * @param end_ms see #DecoderControl - * @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(); - - const char *GetMixRampStart() const { - return mix_ramp.GetStart(); - } - - const char *GetMixRampEnd() const { - return mix_ramp.GetEnd(); - } - - const char *GetMixRampPreviousEnd() const { - return previous_mix_ramp.GetEnd(); - } - - void SetMixRamp(MixRampInfo &&new_value) { - mix_ramp = std::move(new_value); - } - - /** - * Move mixramp_end to mixramp_prev_end and clear - * mixramp_start/mixramp_end. - */ - void CycleMixRamp(); -}; - -#endif diff --git a/src/DecoderError.cxx b/src/DecoderError.cxx deleted file mode 100644 index a8d3f548d..000000000 --- a/src/DecoderError.cxx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License 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 deleted file mode 100644 index 9ea74167f..000000000 --- a/src/DecoderError.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index d5f40ad48..000000000 --- a/src/DecoderInternal.cxx +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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(DecoderControl &dc) -{ - if (dc.command == DecoderCommand::NONE) - dc.Wait(); - - return dc.command; -} - -struct music_chunk * -decoder_get_chunk(Decoder &decoder) -{ - DecoderControl &dc = decoder.dc; - DecoderCommand cmd; - - if (decoder.chunk != nullptr) - return decoder.chunk; - - do { - decoder.chunk = dc.buffer->Allocate(); - if (decoder.chunk != nullptr) { - 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); - dc.Unlock(); - } while (cmd == DecoderCommand::NONE); - - return nullptr; -} - -void -decoder_flush_chunk(Decoder &decoder) -{ - DecoderControl &dc = decoder.dc; - - assert(decoder.chunk != nullptr); - - if (decoder.chunk->IsEmpty()) - dc.buffer->Return(decoder.chunk); - else - dc.pipe->Push(decoder.chunk); - - decoder.chunk = nullptr; - - dc.Lock(); - if (dc.client_is_waiting) - dc.client_cond.signal(); - dc.Unlock(); -} diff --git a/src/DecoderInternal.hxx b/src/DecoderInternal.hxx deleted file mode 100644 index 46069a561..000000000 --- a/src/DecoderInternal.hxx +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 DecoderControl; -struct InputStream; -struct Tag; - -struct Decoder { - DecoderControl &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; - - ReplayGainInfo replay_gain_info; - - /** - * A positive serial number for checking if replay gain info - * has changed since the last check. - */ - unsigned replay_gain_serial; - - Decoder(DecoderControl &_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(Decoder &decoder); - -/** - * Flushes the current chunk. - * - * Caller must not lock the #DecoderControl object. - */ -void -decoder_flush_chunk(Decoder &decoder); - -#endif diff --git a/src/DecoderList.cxx b/src/DecoderList.cxx deleted file mode 100644 index 834178260..000000000 --- a/src/DecoderList.cxx +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "decoder/SidplayDecoderPlugin.hxx" -#include "system/FatalError.hxx" -#include "util/Macros.hxx" - -#include <string.h> - -const struct DecoderPlugin *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, - nullptr -}; - -static constexpr unsigned num_decoder_plugins = - ARRAY_SIZE(decoder_plugins) - 1; - -/** which plugins have been initialized successfully? */ -bool decoder_plugins_enabled[num_decoder_plugins]; - -static unsigned -decoder_plugin_index(const struct DecoderPlugin *plugin) -{ - unsigned i = 0; - - while (decoder_plugins[i] != plugin) - ++i; - - return i; -} - -static unsigned -decoder_plugin_next_index(const struct DecoderPlugin *plugin) -{ - return plugin == 0 - ? 0 /* start with first plugin */ - : decoder_plugin_index(plugin) + 1; -} - -const struct DecoderPlugin * -decoder_plugin_from_suffix(const char *suffix, - const struct DecoderPlugin *plugin) -{ - if (suffix == nullptr) - return nullptr; - - for (unsigned i = decoder_plugin_next_index(plugin); - decoder_plugins[i] != nullptr; ++i) { - plugin = decoder_plugins[i]; - if (decoder_plugins_enabled[i] && - plugin->SupportsSuffix(suffix)) - return plugin; - } - - return nullptr; -} - -const struct DecoderPlugin * -decoder_plugin_from_mime_type(const char *mimeType, unsigned int next) -{ - static unsigned i = num_decoder_plugins; - - if (mimeType == nullptr) - return nullptr; - - if (!next) - i = 0; - for (; decoder_plugins[i] != nullptr; ++i) { - const struct DecoderPlugin *plugin = decoder_plugins[i]; - if (decoder_plugins_enabled[i] && - plugin->SupportsMimeType(mimeType)) { - ++i; - return plugin; - } - } - - return nullptr; -} - -const struct DecoderPlugin * -decoder_plugin_from_name(const char *name) -{ - return decoder_plugins_find([=](const DecoderPlugin &plugin){ - return strcmp(plugin.name, name) == 0; - }); -} - -/** - * Find the "decoder" configuration block for the specified plugin. - * - * @param plugin_name the name of the decoder plugin - * @return the configuration block, or nullptr if none was configured - */ -static const struct config_param * -decoder_plugin_config(const char *plugin_name) -{ - const struct config_param *param = nullptr; - - while ((param = config_get_next_param(CONF_DECODER, param)) != nullptr) { - const char *name = param->GetBlockValue("plugin"); - if (name == nullptr) - FormatFatalError("decoder configuration without 'plugin' name in line %d", - param->line); - - if (strcmp(name, plugin_name) == 0) - return param; - } - - return nullptr; -} - -void decoder_plugin_init_all(void) -{ - struct config_param empty; - - for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) { - const DecoderPlugin &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 (plugin.Init(*param)) - decoder_plugins_enabled[i] = true; - } -} - -void decoder_plugin_deinit_all(void) -{ - decoder_plugins_for_each_enabled([=](const DecoderPlugin &plugin){ - plugin.Finish(); - }); -} diff --git a/src/DecoderList.hxx b/src/DecoderList.hxx deleted file mode 100644 index fd4b22c63..000000000 --- a/src/DecoderList.hxx +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 DecoderPlugin; - -extern const struct DecoderPlugin *const decoder_plugins[]; -extern bool decoder_plugins_enabled[]; - -/* interface for using plugins */ - -/** - * Find the next enabled decoder plugin which supports the specified suffix. - * - * @param suffix the file name suffix - * @param plugin the previous plugin, or nullptr to find the first plugin - * @return a plugin, or nullptr if none matches - */ -const struct DecoderPlugin * -decoder_plugin_from_suffix(const char *suffix, - const struct DecoderPlugin *plugin); - -const struct DecoderPlugin * -decoder_plugin_from_mime_type(const char *mimeType, unsigned int next); - -const struct DecoderPlugin * -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); - -template<typename F> -static inline const DecoderPlugin * -decoder_plugins_find(F f) -{ - for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) - if (decoder_plugins_enabled[i] && f(*decoder_plugins[i])) - return decoder_plugins[i]; - - return nullptr; -} - -template<typename F> -static inline bool -decoder_plugins_try(F f) -{ - for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) - if (decoder_plugins_enabled[i] && f(*decoder_plugins[i])) - return true; - - return false; -} - -template<typename F> -static inline void -decoder_plugins_for_each(F f) -{ - for (auto i = decoder_plugins; *i != nullptr; ++i) - f(**i); -} - -template<typename F> -static inline void -decoder_plugins_for_each_enabled(F f) -{ - for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) - if (decoder_plugins_enabled[i]) - f(*decoder_plugins[i]); -} - -#endif diff --git a/src/DecoderPlugin.cxx b/src/DecoderPlugin.cxx deleted file mode 100644 index 77ed90882..000000000 --- a/src/DecoderPlugin.cxx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 -DecoderPlugin::SupportsSuffix(const char *suffix) const -{ - assert(suffix != nullptr); - - return suffixes != nullptr && string_array_contains(suffixes, suffix); - -} - -bool -DecoderPlugin::SupportsMimeType(const char *mime_type) const -{ - assert(mime_type != nullptr); - - return mime_types != nullptr && - string_array_contains(mime_types, mime_type); -} diff --git a/src/DecoderPlugin.hxx b/src/DecoderPlugin.hxx deleted file mode 100644 index 6b0340123..000000000 --- a/src/DecoderPlugin.hxx +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 - -#include "Compiler.h" - -struct config_param; -struct InputStream; -struct Tag; -struct tag_handler; - -/** - * Opaque handle which the decoder plugin passes to the functions in - * this header. - */ -struct Decoder; - -struct DecoderPlugin { - 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)(Decoder &decoder, InputStream &is); - - /** - * Decode a local file. - * - * Either implement this method or stream_decode(). - */ - void (*file_decode)(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)(InputStream &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 - */ - bool Init(const config_param ¶m) const { - return init != nullptr - ? init(param) - : true; - } - - /** - * Deinitialize a decoder plugin which was initialized successfully. - */ - void Finish() const { - if (finish != nullptr) - finish(); - } - - /** - * Decode a stream. - */ - void StreamDecode(Decoder &decoder, InputStream &is) const { - stream_decode(decoder, is); - } - - /** - * Decode a file. - */ - void FileDecode(Decoder &decoder, const char *path_fs) const { - file_decode(decoder, path_fs); - } - - /** - * Read the tag of a file. - */ - bool ScanFile(const char *path_fs, - const tag_handler &handler, void *handler_ctx) const { - return scan_file != nullptr - ? scan_file(path_fs, &handler, handler_ctx) - : false; - } - - /** - * Read the tag of a stream. - */ - bool ScanStream(InputStream &is, - const tag_handler &handler, void *handler_ctx) const { - return scan_stream != nullptr - ? scan_stream(is, &handler, handler_ctx) - : false; - } - - /** - * return "virtual" tracks in a container - */ - char *ContainerScan(const char *path, const unsigned int tnum) const { - return container_scan(path, tnum); - } - - /** - * Does the plugin announce the specified file name suffix? - */ - gcc_pure gcc_nonnull_all - bool SupportsSuffix(const char *suffix) const; - - /** - * Does the plugin announce the specified MIME type? - */ - gcc_pure gcc_nonnull_all - bool SupportsMimeType(const char *mime_type) const; -}; - -#endif diff --git a/src/DecoderPrint.cxx b/src/DecoderPrint.cxx deleted file mode 100644 index 2372272c2..000000000 --- a/src/DecoderPrint.cxx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 <functional> - -#include <assert.h> - -static void -decoder_plugin_print(Client &client, - const DecoderPlugin &plugin) -{ - const char *const*p; - - assert(plugin.name != nullptr); - - client_printf(client, "plugin: %s\n", plugin.name); - - if (plugin.suffixes != nullptr) - for (p = plugin.suffixes; *p != nullptr; ++p) - client_printf(client, "suffix: %s\n", *p); - - if (plugin.mime_types != nullptr) - for (p = plugin.mime_types; *p != nullptr; ++p) - client_printf(client, "mime_type: %s\n", *p); -} - -void -decoder_list_print(Client &client) -{ - using namespace std::placeholders; - const auto f = std::bind(decoder_plugin_print, std::ref(client), _1); - decoder_plugins_for_each_enabled(f); -} diff --git a/src/DecoderPrint.hxx b/src/DecoderPrint.hxx deleted file mode 100644 index 693608746..000000000 --- a/src/DecoderPrint.hxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 72fc3cfb4..000000000 --- a/src/DecoderThread.cxx +++ /dev/null @@ -1,450 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/Traits.hxx" -#include "fs/AllocatedPath.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 <functional> - -static constexpr Domain decoder_thread_domain("decoder_thread"); - -/** - * Marks the current decoder command as "finished" and notifies the - * player thread. - * - * @param dc the #DecoderControl object; must be locked - */ -static void -decoder_command_finished_locked(DecoderControl &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, nullptr on error - */ -static InputStream * -decoder_input_stream_open(DecoderControl &dc, const char *uri) -{ - Error error; - - InputStream *is = InputStream::Open(uri, dc.mutex, dc.cond, error); - if (is == nullptr) { - if (error.IsDefined()) - LogError(error); - - return nullptr; - } - - /* 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 nullptr; - } - - dc.Unlock(); - - return is; -} - -static bool -decoder_stream_decode(const DecoderPlugin &plugin, - Decoder &decoder, - InputStream &input_stream) -{ - assert(plugin.stream_decode != nullptr); - assert(decoder.stream_tag == nullptr); - assert(decoder.decoder_tag == nullptr); - 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.Rewind(IgnoreError()); - - decoder.dc.Unlock(); - - plugin.StreamDecode(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 DecoderPlugin &plugin, - Decoder &decoder, const char *path) -{ - assert(plugin.file_decode != nullptr); - assert(decoder.stream_tag == nullptr); - assert(decoder.decoder_tag == nullptr); - assert(path != nullptr); - assert(PathTraits::IsAbsoluteFS(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(); - - plugin.FileDecode(decoder, path); - - decoder.dc.Lock(); - - assert(decoder.dc.state == DecoderState::START || - decoder.dc.state == DecoderState::DECODE); - - return decoder.dc.state != DecoderState::START; -} - -gcc_pure -static bool -decoder_check_plugin_mime(const DecoderPlugin &plugin, const InputStream &is) -{ - assert(plugin.stream_decode != nullptr); - - return !is.mime.empty() && plugin.SupportsMimeType(is.mime.c_str()); -} - -gcc_pure -static bool -decoder_check_plugin_suffix(const DecoderPlugin &plugin, const char *suffix) -{ - assert(plugin.stream_decode != nullptr); - - return suffix != nullptr && plugin.SupportsSuffix(suffix); -} - -gcc_pure -static bool -decoder_check_plugin(const DecoderPlugin &plugin, const InputStream &is, - const char *suffix) -{ - return plugin.stream_decode != nullptr && - (decoder_check_plugin_mime(plugin, is) || - decoder_check_plugin_suffix(plugin, suffix)); -} - -static bool -decoder_run_stream_plugin(Decoder &decoder, InputStream &is, - const char *suffix, - const DecoderPlugin &plugin, - bool &tried_r) -{ - if (!decoder_check_plugin(plugin, is, suffix)) - return false; - - tried_r = true; - return decoder_stream_decode(plugin, decoder, is); -} - -static bool -decoder_run_stream_locked(Decoder &decoder, InputStream &is, - const char *uri, bool &tried_r) -{ - const char *const suffix = uri_get_suffix(uri); - - using namespace std::placeholders; - const auto f = std::bind(decoder_run_stream_plugin, - std::ref(decoder), std::ref(is), suffix, - _1, std::ref(tried_r)); - return decoder_plugins_try(f); -} - -/** - * Try decoding a stream, using the fallback plugin. - */ -static bool -decoder_run_stream_fallback(Decoder &decoder, InputStream &is) -{ - const struct DecoderPlugin *plugin; - - plugin = decoder_plugin_from_name("mad"); - return plugin != nullptr && plugin->stream_decode != nullptr && - decoder_stream_decode(*plugin, decoder, is); -} - -/** - * Try decoding a stream. - */ -static bool -decoder_run_stream(Decoder &decoder, const char *uri) -{ - DecoderControl &dc = decoder.dc; - InputStream *input_stream; - bool success; - - dc.Unlock(); - - input_stream = decoder_input_stream_open(dc, uri); - if (input_stream == nullptr) { - dc.Lock(); - return false; - } - - dc.Lock(); - - bool tried = false; - success = dc.command == DecoderCommand::STOP || - decoder_run_stream_locked(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 && - decoder_run_stream_fallback(decoder, *input_stream)); - - 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(Decoder &decoder, const char *path_fs) -{ - ReplayGainInfo info; - if (replay_gain_ape_read(Path::FromFS(path_fs), info)) - decoder_replay_gain(decoder, &info); -} - -/** - * Try decoding a file. - */ -static bool -decoder_run_file(Decoder &decoder, const char *path_fs) -{ - DecoderControl &dc = decoder.dc; - const char *suffix = uri_get_suffix(path_fs); - const struct DecoderPlugin *plugin = nullptr; - - if (suffix == nullptr) - return false; - - dc.Unlock(); - - decoder_load_replay_gain(decoder, path_fs); - - while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != nullptr) { - if (plugin->file_decode != nullptr) { - dc.Lock(); - - if (decoder_file_decode(*plugin, decoder, path_fs)) - return true; - - dc.Unlock(); - } else if (plugin->stream_decode != nullptr) { - InputStream *input_stream; - bool success; - - input_stream = decoder_input_stream_open(dc, path_fs); - if (input_stream == nullptr) - 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(DecoderControl &dc, - const Song *song, const char *uri) -{ - Decoder decoder(dc, dc.start_ms > 0, - song->tag != nullptr && 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 != nullptr) - decoder_flush_chunk(decoder); - - dc.Lock(); - - if (ret) - dc.state = DecoderState::STOP; - else { - dc.state = DecoderState::ERROR; - - const char *error_uri = song->uri; - const std::string allocated = uri_remove_auth(error_uri); - if (!allocated.empty()) - error_uri = allocated.c_str(); - - dc.error.Format(decoder_domain, - "Failed to decode %s", error_uri); - } - - dc.client_cond.signal(); -} - -static void -decoder_run(DecoderControl &dc) -{ - dc.ClearError(); - - const Song *song = dc.song; - assert(song != nullptr); - - const std::string uri = song->IsFile() - ? std::string(map_song_fs(*song).c_str()) - : song->GetURI(); - - if (uri.empty()) { - 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.c_str()); - -} - -static void -decoder_task(void *arg) -{ - DecoderControl &dc = *(DecoderControl *)arg; - - dc.Lock(); - - do { - assert(dc.state == DecoderState::STOP || - dc.state == DecoderState::ERROR); - - switch (dc.command) { - case DecoderCommand::START: - dc.CycleMixRamp(); - 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(); -} - -void -decoder_thread_start(DecoderControl &dc) -{ - assert(!dc.thread.IsDefined()); - - dc.quit = false; - - Error error; - if (!dc.thread.Start(decoder_task, &dc, error)) - FatalError(error); -} diff --git a/src/DecoderThread.hxx b/src/DecoderThread.hxx deleted file mode 100644 index a2d533f93..000000000 --- a/src/DecoderThread.hxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 DecoderControl; - -void -decoder_thread_start(DecoderControl &dc); - -#endif diff --git a/src/DespotifyUtils.cxx b/src/DespotifyUtils.cxx deleted file mode 100644 index e91587a7f..000000000 --- a/src/DespotifyUtils.cxx +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License 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> -} - -#include <stdio.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] = nullptr; - } - } -} - - -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, nullptr); - passwd = config_get_string(CONF_DESPOTIFY_PASSWORD, nullptr); - high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true); - - if (user == nullptr || passwd == nullptr) { - 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, nullptr, - 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 deleted file mode 100644 index c0d4af47c..000000000 --- a/src/DespotifyUtils.hxx +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 nullptr 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/DetachedSong.cxx b/src/DetachedSong.cxx new file mode 100644 index 000000000..2fff9b70f --- /dev/null +++ b/src/DetachedSong.cxx @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DetachedSong.hxx" +#include "db/LightSong.hxx" +#include "util/UriUtil.hxx" +#include "fs/Traits.hxx" + +DetachedSong::DetachedSong(const LightSong &other) + :uri(other.GetURI().c_str()), + real_uri(other.real_uri), + tag(*other.tag), + mtime(other.mtime), + start_ms(other.start_ms), end_ms(other.end_ms) {} + +DetachedSong::~DetachedSong() +{ + /* this destructor exists here just so it won't get inlined */ +} + +bool +DetachedSong::IsRemote() const +{ + return uri_has_scheme(GetRealURI()); +} + +bool +DetachedSong::IsAbsoluteFile() const +{ + return PathTraitsUTF8::IsAbsolute(GetRealURI()); +} + +bool +DetachedSong::IsInDatabase() const +{ + /* here, we use GetURI() and not GetRealURI() because + GetRealURI() is never relative */ + + const char *_uri = GetURI(); + return !uri_has_scheme(_uri) && !PathTraitsUTF8::IsAbsolute(_uri); +} + +double +DetachedSong::GetDuration() const +{ + if (end_ms > 0) + return (end_ms - start_ms) / 1000.0; + + return tag.time - start_ms / 1000.0; +} diff --git a/src/DetachedSong.hxx b/src/DetachedSong.hxx new file mode 100644 index 000000000..c01f32ea5 --- /dev/null +++ b/src/DetachedSong.hxx @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DETACHED_SONG_HXX +#define MPD_DETACHED_SONG_HXX + +#include "check.h" +#include "tag/Tag.hxx" +#include "Compiler.h" + +#include <string> +#include <utility> + +#include <time.h> + +struct LightSong; + +class DetachedSong { + friend DetachedSong map_song_detach(const LightSong &song); + + /** + * An UTF-8-encoded URI referring to the song file. This can + * be one of: + * + * - an absolute URL with a scheme + * (e.g. "http://example.com/foo.mp3") + * + * - an absolute file name + * + * - a file name relative to the music directory + */ + std::string uri; + + /** + * The "real" URI, the one to be used for opening the + * resource. If this attribute is empty, then #uri shall be + * used. + * + * This attribute is used for songs from the database which + * have a relative URI. + */ + std::string real_uri; + + Tag tag; + + 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; + + explicit DetachedSong(const LightSong &other); + +public: + explicit DetachedSong(const DetachedSong &) = default; + + explicit DetachedSong(const char *_uri) + :uri(_uri), + mtime(0), start_ms(0), end_ms(0) {} + + explicit DetachedSong(const std::string &_uri) + :uri(_uri), + mtime(0), start_ms(0), end_ms(0) {} + + explicit DetachedSong(std::string &&_uri) + :uri(std::move(_uri)), + mtime(0), start_ms(0), end_ms(0) {} + + template<typename U> + DetachedSong(U &&_uri, Tag &&_tag) + :uri(std::forward<U>(_uri)), + tag(std::move(_tag)), + mtime(0), start_ms(0), end_ms(0) {} + + DetachedSong(DetachedSong &&) = default; + + ~DetachedSong(); + + gcc_pure + const char *GetURI() const { + return uri.c_str(); + } + + template<typename T> + void SetURI(T &&_uri) { + uri = std::forward<T>(_uri); + } + + /** + * Does this object have a "real" URI different from the + * displayed URI? + */ + gcc_pure + bool HasRealURI() const { + return !real_uri.empty(); + } + + /** + * Returns "real" URI (#real_uri) and falls back to just + * GetURI(). + */ + gcc_pure + const char *GetRealURI() const { + return (HasRealURI() ? real_uri : uri).c_str(); + } + + template<typename T> + void SetRealURI(T &&_uri) { + real_uri = std::forward<T>(_uri); + } + + /** + * Returns true if both objects refer to the same physical + * song. + */ + gcc_pure + bool IsSame(const DetachedSong &other) const { + return uri == other.uri; + } + + gcc_pure gcc_nonnull_all + bool IsURI(const char *other_uri) const { + return uri == other_uri; + } + + gcc_pure + bool IsRemote() const; + + gcc_pure + bool IsFile() const { + return !IsRemote(); + } + + gcc_pure + bool IsAbsoluteFile() const; + + gcc_pure + bool IsInDatabase() const; + + const Tag &GetTag() const { + return tag; + } + + Tag &WritableTag() { + return tag; + } + + void SetTag(const Tag &_tag) { + tag = Tag(_tag); + } + + void SetTag(Tag &&_tag) { + tag = std::move(_tag); + } + + void MoveTagFrom(DetachedSong &&other) { + tag = std::move(other.tag); + } + + time_t GetLastModified() const { + return mtime; + } + + void SetLastModified(time_t _value) { + mtime = _value; + } + + unsigned GetStartMS() const { + return start_ms; + } + + void SetStartMS(unsigned _value) { + start_ms = _value; + } + + unsigned GetEndMS() const { + return end_ms; + } + + void SetEndMS(unsigned _value) { + end_ms = _value; + } + + gcc_pure + double GetDuration() const; + + /** + * Update the #tag and #mtime. + * + * @return true on success + */ + bool Update(); +}; + +#endif diff --git a/src/Directory.cxx b/src/Directory.cxx deleted file mode 100644 index b2942588e..000000000 --- a/src/Directory.cxx +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "fs/Traits.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 != nullptr); - - 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 != nullptr); - assert((*path == 0) == (parent == nullptr)); - - 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()); - - return PathTraits::GetBaseUTF8(path); -} - -Directory * -Directory::CreateChild(const char *name_utf8) -{ - assert(holding_db_lock()); - assert(name_utf8 != nullptr); - assert(*name_utf8 != 0); - - char *allocated; - const char *path_utf8; - if (IsRoot()) { - allocated = nullptr; - path_utf8 = name_utf8; - } else { - allocated = g_strconcat(GetPath(), - "/", name_utf8, nullptr); - 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 nullptr; -} - -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 != nullptr); - - 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 = nullptr; - break; - } - - if (slash != nullptr) - *slash = '\0'; - - d = d->FindChild(name); - if (d == nullptr || slash == nullptr) - break; - - name = slash + 1; - } - - g_free(duplicated); - - return d; -} - -void -Directory::AddSong(Song *song) -{ - assert(holding_db_lock()); - assert(song != nullptr); - assert(song->parent == this); - - list_add_tail(&song->siblings, &songs); -} - -void -Directory::RemoveSong(Song *song) -{ - assert(holding_db_lock()); - assert(song != nullptr); - assert(song->parent == this); - - list_del(&song->siblings); -} - -const Song * -Directory::FindSong(const char *name_utf8) const -{ - assert(holding_db_lock()); - assert(name_utf8 != nullptr); - - Song *song; - directory_for_each_song(song, *this) { - assert(song->parent == this); - - if (strcmp(song->uri, name_utf8) == 0) - return song; - } - - return nullptr; -} - -Song * -Directory::LookupSong(const char *uri) -{ - char *duplicated, *base; - - assert(holding_db_lock()); - assert(uri != nullptr); - - duplicated = g_strdup(uri); - base = strrchr(duplicated, '/'); - - Directory *d = this; - if (base != nullptr) { - *base++ = 0; - d = d->LookupDirectory(duplicated); - if (d == nullptr) { - g_free(duplicated); - return nullptr; - } - } else - base = duplicated; - - Song *song = d->FindSong(base); - assert(song == nullptr || 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(nullptr, &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 deleted file mode 100644 index 380a6b790..000000000 --- a/src/Directory.hxx +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.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 nullptr 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 == nullptr; - } - - /** - * 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 nullptr 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 deleted file mode 100644 index fa330d126..000000000 --- a/src/DirectorySave.cxx +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/NumberParser.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) { - fprintf(fp, DIRECTORY_DIR "%s\n", cur->GetName()); - - 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 nullptr; - } - - Directory *directory = parent.CreateChild(name); - - const char *line = file.ReadLine(); - if (line == nullptr) { - error.Set(directory_domain, "Unexpected end of file"); - directory->Delete(); - return nullptr; - } - - if (g_str_has_prefix(line, DIRECTORY_MTIME)) { - directory->mtime = - ParseUint64(line + sizeof(DIRECTORY_MTIME) - 1); - - line = file.ReadLine(); - if (line == nullptr) { - error.Set(directory_domain, "Unexpected end of file"); - directory->Delete(); - return nullptr; - } - } - - if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) { - error.Format(directory_domain, "Malformed line: %s", line); - directory->Delete(); - return nullptr; - } - - success = directory_load(file, *directory, error); - if (!success) { - directory->Delete(); - return nullptr; - } - - return directory; -} - -bool -directory_load(TextFile &file, Directory &directory, Error &error) -{ - const char *line; - - while ((line = file.ReadLine()) != nullptr && - !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 == nullptr) - 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 == nullptr) - 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 deleted file mode 100644 index 02814490a..000000000 --- a/src/DirectorySave.hxx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index b3397f25c..000000000 --- a/src/EncoderAPI.hxx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 deleted file mode 100644 index 7760a9582..000000000 --- a/src/EncoderList.cxx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 - nullptr -}; - -const EncoderPlugin * -encoder_plugin_get(const char *name) -{ - encoder_plugins_for_each(plugin) - if (strcmp(plugin->name, name) == 0) - return plugin; - - return nullptr; -} diff --git a/src/EncoderList.hxx b/src/EncoderList.hxx deleted file mode 100644 index 8cbb389a4..000000000 --- a/src/EncoderList.hxx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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) != nullptr; \ - ++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 nullptr if none - * was found - */ -const EncoderPlugin * -encoder_plugin_get(const char *name); - -#endif diff --git a/src/EncoderPlugin.hxx b/src/EncoderPlugin.hxx deleted file mode 100644 index 8ad5ea0c6..000000000 --- a/src/EncoderPlugin.hxx +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 nullptr to ignore errors. - * @return an encoder object on success, nullptr 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 - * @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 != nullptr) - 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 - * @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 != nullptr - ? 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 - * @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 != nullptr - ? 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 - * @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 != nullptr - ? 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 - * @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 != nullptr - ? 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 - * @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, nullptr on failure - */ -static inline const char * -encoder_get_mime_type(Encoder *encoder) -{ - /* this method is optional */ - return encoder->plugin.get_mime_type != nullptr - ? encoder->plugin.get_mime_type(encoder) - : nullptr; -} - -#endif diff --git a/src/ExcludeList.cxx b/src/ExcludeList.cxx index d34afd897..7b691b36b 100644 --- a/src/ExcludeList.cxx +++ b/src/ExcludeList.cxx @@ -1,6 +1,6 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/ExcludeList.hxx b/src/ExcludeList.hxx index e15f902f9..8870664b0 100644 --- a/src/ExcludeList.hxx +++ b/src/ExcludeList.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/FilterConfig.cxx b/src/FilterConfig.cxx deleted file mode 100644 index cfac1c756..000000000 --- a/src/FilterConfig.cxx +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 <algorithm> - -#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 nullptr if none was configured - */ -static const struct config_param * -filter_plugin_config(const char *filter_template_name, Error &error) -{ - const struct config_param *param = nullptr; - - while ((param = config_get_next_param(CONF_AUDIO_FILTER, param)) != nullptr) { - const char *name = param->GetBlockValue("name"); - if (name == nullptr) { - error.Format(config_domain, - "filter configuration without 'name' name in line %d", - param->line); - return nullptr; - } - - if (strcmp(name, filter_template_name) == 0) - return param; - } - - error.Format(config_domain, - "filter template not found: %s", - filter_template_name); - return nullptr; -} - -static bool -filter_chain_append_new(Filter &chain, const char *template_name, Error &error) -{ - const struct config_param *cfg = - filter_plugin_config(template_name, error); - if (cfg == nullptr) - // The error has already been set, just stop. - return false; - - // Instantiate one of those filter plugins with the template name as a hint - Filter *f = filter_configured_new(*cfg, error); - if (f == nullptr) - // The error has already been set, just stop. - return false; - - const char *plugin_name = cfg->GetBlockValue("plugin", - "unknown"); - filter_chain_append(chain, plugin_name, f); - - return true; -} - -bool -filter_chain_parse(Filter &chain, const char *spec, Error &error) -{ - const char *const end = spec + strlen(spec); - - while (true) { - const char *comma = std::find(spec, end, ','); - if (comma > spec) { - const std::string name(spec, comma); - if (!filter_chain_append_new(chain, name.c_str(), - error)) - return false; - } - - if (comma == end) - break; - - spec = comma + 1; - } - - return true; -} diff --git a/src/FilterConfig.hxx b/src/FilterConfig.hxx deleted file mode 100644 index 6b074b930..000000000 --- a/src/FilterConfig.hxx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 true on success - */ -bool -filter_chain_parse(Filter &chain, const char *spec, Error &error); - -#endif diff --git a/src/FilterInternal.hxx b/src/FilterInternal.hxx deleted file mode 100644 index e2884e82f..000000000 --- a/src/FilterInternal.hxx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 nullptr - * 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 nullptr - * to ignore errors. - * @return the destination buffer on success (will be - * invalidated by filter_close() or filter_filter()), nullptr 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 deleted file mode 100644 index 608542f92..000000000 --- a/src/FilterPlugin.cxx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 != nullptr); - 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 == nullptr) { - error.Set(config_domain, "No filter plugin specified"); - return nullptr; - } - - const filter_plugin *plugin = filter_plugin_by_name(plugin_name); - if (plugin == nullptr) { - error.Format(config_domain, - "No such filter plugin: %s", plugin_name); - return nullptr; - } - - return filter_new(plugin, param, error); -} diff --git a/src/FilterPlugin.hxx b/src/FilterPlugin.hxx deleted file mode 100644 index 5ff4637b3..000000000 --- a/src/FilterPlugin.hxx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 nullptr to - * ignore errors. - * @return a new filter object, or nullptr 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 nullptr to - * ignore errors. - * @return a new filter object, or nullptr on error - */ -Filter * -filter_configured_new(const config_param ¶m, Error &error); - -#endif diff --git a/src/FilterRegistry.cxx b/src/FilterRegistry.cxx deleted file mode 100644 index b3b08505e..000000000 --- a/src/FilterRegistry.cxx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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, - nullptr, -}; - -const struct filter_plugin * -filter_plugin_by_name(const char *name) -{ - for (unsigned i = 0; filter_plugins[i] != nullptr; ++i) - if (strcmp(filter_plugins[i]->name, name) == 0) - return filter_plugins[i]; - - return nullptr; -} diff --git a/src/FilterRegistry.hxx b/src/FilterRegistry.hxx deleted file mode 100644 index 286420f92..000000000 --- a/src/FilterRegistry.hxx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 - -#include "Compiler.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; - -gcc_pure -const struct filter_plugin * -filter_plugin_by_name(const char *name); - -#endif diff --git a/src/GlobalEvents.cxx b/src/GlobalEvents.cxx index 86bfb3e2a..9c60f6357 100644 --- a/src/GlobalEvents.cxx +++ b/src/GlobalEvents.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,7 +21,6 @@ #include "GlobalEvents.hxx" #include "util/Manual.hxx" #include "event/DeferredMonitor.hxx" -#include "Compiler.h" #include <atomic> diff --git a/src/GlobalEvents.hxx b/src/GlobalEvents.hxx index 47ec6d070..106eeb8f8 100644 --- a/src/GlobalEvents.hxx +++ b/src/GlobalEvents.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/IOThread.cxx b/src/IOThread.cxx index a14f12eb1..e21ede4f3 100644 --- a/src/IOThread.cxx +++ b/src/IOThread.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,6 +22,7 @@ #include "thread/Mutex.hxx" #include "thread/Cond.hxx" #include "thread/Thread.hxx" +#include "thread/Name.hxx" #include "event/Loop.hxx" #include "system/FatalError.hxx" #include "util/Error.hxx" @@ -48,6 +49,8 @@ io_thread_run(void) static void io_thread_func(gcc_unused void *arg) { + SetThreadName("io"); + /* lock+unlock to synchronize with io_thread_start(), to be sure that io.thread is set */ io.mutex.lock(); diff --git a/src/IOThread.hxx b/src/IOThread.hxx index 3384a09e0..cf78e66cf 100644 --- a/src/IOThread.hxx +++ b/src/IOThread.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/IcyMetaDataParser.cxx b/src/IcyMetaDataParser.cxx index bfa2e8558..febdb385a 100644 --- a/src/IcyMetaDataParser.cxx +++ b/src/IcyMetaDataParser.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ #include "config.h" #include "IcyMetaDataParser.hxx" #include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" #include "util/Domain.hxx" #include "Log.hxx" @@ -37,7 +38,7 @@ IcyMetaDataParser::Reset() return; if (data_rest == 0 && meta_size > 0) - g_free(meta_data); + delete[] meta_data; delete tag; @@ -66,7 +67,7 @@ IcyMetaDataParser::Data(size_t length) } static void -icy_add_item(Tag &tag, TagType type, const char *value) +icy_add_item(TagBuilder &tag, TagType type, const char *value) { size_t length = strlen(value); @@ -81,7 +82,7 @@ icy_add_item(Tag &tag, TagType type, const char *value) } static void -icy_parse_tag_item(Tag &tag, const char *name, const char *value) +icy_parse_tag_item(TagBuilder &tag, const char *name, const char *value) { if (strcmp(name, "StreamTitle") == 0) icy_add_item(tag, TAG_TITLE, value); @@ -122,7 +123,7 @@ icy_parse_tag(char *p, char *const end) assert(end != nullptr); assert(p <= end); - Tag *tag = new Tag(); + TagBuilder tag; while (p != end) { const char *const name = p; @@ -153,7 +154,7 @@ icy_parse_tag(char *p, char *const end) *quote = 0; p = quote + 1; - icy_parse_tag_item(*tag, name, value); + icy_parse_tag_item(tag, name, value); char *semicolon = std::find(p, end, ';'); if (semicolon == end) @@ -161,7 +162,7 @@ icy_parse_tag(char *p, char *const end) p = semicolon + 1; } - return tag; + return tag.CommitNew(); } size_t @@ -190,7 +191,7 @@ IcyMetaDataParser::Meta(const void *data, size_t length) /* initialize metadata reader, allocate enough memory (+1 for the null terminator) */ meta_position = 0; - meta_data = (char *)g_malloc(meta_size + 1); + meta_data = new char[meta_size + 1]; } assert(meta_position < meta_size); @@ -211,7 +212,7 @@ IcyMetaDataParser::Meta(const void *data, size_t length) delete tag; tag = icy_parse_tag(meta_data, meta_data + meta_size); - g_free(meta_data); + delete[] meta_data; /* change back to normal data mode */ diff --git a/src/IcyMetaDataParser.hxx b/src/IcyMetaDataParser.hxx index 6bcb09668..4956a9904 100644 --- a/src/IcyMetaDataParser.hxx +++ b/src/IcyMetaDataParser.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/IcyMetaDataServer.cxx b/src/IcyMetaDataServer.cxx index f5e981c2f..146df23d1 100644 --- a/src/IcyMetaDataServer.cxx +++ b/src/IcyMetaDataServer.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,7 +25,6 @@ #include <glib.h> -#include <assert.h> #include <string.h> char* diff --git a/src/IcyMetaDataServer.hxx b/src/IcyMetaDataServer.hxx index 20964ce74..773b46641 100644 --- a/src/IcyMetaDataServer.hxx +++ b/src/IcyMetaDataServer.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/IdTable.hxx b/src/IdTable.hxx deleted file mode 100644 index ab021e48f..000000000 --- a/src/IdTable.hxx +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.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 index 840a5d4e5..ed16bbecb 100644 --- a/src/Idle.cxx +++ b/src/Idle.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -44,6 +44,7 @@ static const char *const idle_names[] = { "update", "subscription", "message", + "neighbor", nullptr }; diff --git a/src/Idle.hxx b/src/Idle.hxx index e5a39f403..7fc79d10a 100644 --- a/src/Idle.hxx +++ b/src/Idle.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -59,6 +59,9 @@ static constexpr unsigned IDLE_SUBSCRIPTION = 0x200; /** a message on the subscribed channel was received */ static constexpr unsigned IDLE_MESSAGE = 0x400; +/** a neighbor was found or lost */ +static constexpr unsigned IDLE_NEIGHBOR = 0x800; + /** * Adds idle flag (with bitwise "or") and queues notifications to all * clients. diff --git a/src/InotifyDomain.cxx b/src/InotifyDomain.cxx deleted file mode 100644 index 1b8e62d38..000000000 --- a/src/InotifyDomain.cxx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License 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 deleted file mode 100644 index 005487804..000000000 --- a/src/InotifyDomain.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index c24816241..000000000 --- a/src/InotifyQueue.cxx +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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> - -/** - * Wait this long after the last change before calling - * update_enqueue(). This increases the probability that updates can - * be bundled. - */ -static constexpr unsigned 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 deleted file mode 100644 index ce79748a0..000000000 --- a/src/InotifyQueue.hxx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.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 deleted file mode 100644 index ccf5d3e14..000000000 --- a/src/InotifySource.cxx +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/Error.hxx" -#include "system/fd_util.h" -#include "system/FatalError.hxx" -#include "Log.hxx" - -#include <sys/inotify.h> -#include <unistd.h> -#include <errno.h> - -bool -InotifySource::OnSocketReady(gcc_unused unsigned flags) -{ - const auto dest = buffer.Write(); - if (dest.IsEmpty()) - FatalError("buffer full"); - - ssize_t nbytes = read(Get(), dest.data, dest.size); - if (nbytes < 0) - FatalSystemError("Failed to read from inotify"); - if (nbytes == 0) - FatalError("end of file from inotify"); - - buffer.Append(nbytes); - - while (true) { - const char *name; - - auto range = buffer.Read(); - const struct inotify_event *event = - (const struct inotify_event *) - range.data; - if (range.size < sizeof(*event) || - range.size < sizeof(*event) + event->len) - break; - - if (event->len > 0 && event->name[event->len - 1] == 0) - name = event->name; - else - name = nullptr; - - callback(event->wd, event->mask, name, callback_ctx); - buffer.Consume(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) -{ - 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 nullptr; - } - - return new InotifySource(loop, callback, callback_ctx, fd); -} - -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 deleted file mode 100644 index f6ddea966..000000000 --- a/src/InotifySource.hxx +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "util/FifoBuffer.hxx" -#include "Compiler.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; - - FifoBuffer<uint8_t, 4096> 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); - - /** - * 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 deleted file mode 100644 index 5a015508a..000000000 --- a/src/InotifyUpdate.cxx +++ /dev/null @@ -1,339 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with 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/AllocatedPath.hxx" -#include "fs/FileSystem.hxx" -#include "util/Error.hxx" -#include "Log.hxx" - -#include <string> -#include <map> -#include <forward_list> - -#include <assert.h> -#include <sys/inotify.h> -#include <sys/stat.h> -#include <string.h> -#include <dirent.h> - -static constexpr unsigned IN_MASK = -#ifdef IN_ONLYDIR - IN_ONLYDIR| -#endif - IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF - |IN_MOVE|IN_MOVE_SELF; - -struct WatchDirectory { - WatchDirectory *parent; - - AllocatedPath name; - - int descriptor; - - std::forward_list<WatchDirectory> children; - - template<typename N> - WatchDirectory(WatchDirectory *_parent, N &&_name, - int _descriptor) - :parent(_parent), name(std::forward<N>(_name)), - descriptor(_descriptor) {} - - WatchDirectory(const WatchDirectory &) = delete; - WatchDirectory &operator=(const WatchDirectory &) = delete; -}; - -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 != nullptr); - - if (directory->parent == nullptr) { - 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 AllocatedPath -watch_directory_get_uri_fs(const WatchDirectory *directory) -{ - if (directory->parent == nullptr) - return AllocatedPath::Null(); - - const auto uri = watch_directory_get_uri_fs(directory->parent); - if (uri.IsNull()) - return directory->name; - - return AllocatedPath::Build(uri, directory->name); -} - -/* we don't look at "." / ".." nor files with newlines in their name */ -static bool skip_path(const char *path) -{ - return (path[0] == '.' && path[1] == 0) || - (path[0] == '.' && path[1] == '.' && path[2] == 0) || - strchr(path, '\n') != nullptr; -} - -static void -recursive_watch_subdirectories(WatchDirectory *directory, - const AllocatedPath &path_fs, unsigned depth) -{ - Error error; - DIR *dir; - struct dirent *ent; - - assert(directory != nullptr); - assert(depth <= inotify_max_depth); - assert(!path_fs.IsNull()); - - ++depth; - - if (depth > inotify_max_depth) - return; - - dir = opendir(path_fs.c_str()); - if (dir == nullptr) { - FormatErrno(inotify_domain, - "Failed to open directory %s", path_fs.c_str()); - return; - } - - while ((ent = readdir(dir))) { - struct stat st; - int ret; - - if (skip_path(ent->d_name)) - continue; - - const auto child_path_fs = - AllocatedPath::Build(path_fs, ent->d_name); - ret = StatFile(child_path_fs, st); - if (ret < 0) { - FormatErrno(inotify_domain, - "Failed to stat %s", - child_path_fs.c_str()); - continue; - } - - if (!S_ISDIR(st.st_mode)) - continue; - - ret = inotify_source->Add(child_path_fs.c_str(), IN_MASK, - error); - if (ret < 0) { - FormatError(error, - "Failed to register %s", - child_path_fs.c_str()); - error.Clear(); - continue; - } - - WatchDirectory *child = tree_find_watch_directory(ret); - if (child != nullptr) - /* already being watched */ - continue; - - directory->children.emplace_front(directory, - AllocatedPath::FromFS(ent->d_name), - ret); - child = &directory->children.front(); - - tree_add_watch_directory(child); - - recursive_watch_subdirectories(child, child_path_fs, depth); - } - - closedir(dir); -} - -gcc_pure -static unsigned -watch_directory_depth(const WatchDirectory *d) -{ - assert(d != nullptr); - - unsigned depth = 0; - while ((d = d->parent) != nullptr) - ++depth; - - return depth; -} - -static void -mpd_inotify_callback(int wd, unsigned mask, - gcc_unused const char *name, gcc_unused void *ctx) -{ - WatchDirectory *directory; - - /*FormatDebug(inotify_domain, "wd=%d mask=0x%x name='%s'", wd, mask, name);*/ - - directory = tree_find_watch_directory(wd); - if (directory == nullptr) - return; - - const auto uri_fs = watch_directory_get_uri_fs(directory); - - if ((mask & (IN_DELETE_SELF|IN_MOVE_SELF)) != 0) { - 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 auto &root = mapper_get_music_directory_fs(); - - const auto path_fs = uri_fs.IsNull() - ? root - : AllocatedPath::Build(root, uri_fs.c_str()); - - recursive_watch_subdirectories(directory, path_fs, - watch_directory_depth(directory)); - } - - 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.IsNull()) { - const std::string uri_utf8 = uri_fs.ToUTF8(); - if (!uri_utf8.empty()) - inotify_queue->Enqueue(uri_utf8.c_str()); - } - else - inotify_queue->Enqueue(""); - } -} - -void -mpd_inotify_init(unsigned max_depth) -{ - LogDebug(inotify_domain, "initializing inotify"); - - const auto &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 == nullptr) { - 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 = nullptr; - return; - } - - inotify_root = new WatchDirectory(nullptr, path, descriptor); - - tree_add_watch_directory(inotify_root); - - recursive_watch_subdirectories(inotify_root, path, 0); - - inotify_queue = new InotifyQueue(*main_loop); - - LogDebug(inotify_domain, "watching music directory"); -} - -void -mpd_inotify_finish(void) -{ - if (inotify_source == nullptr) - return; - - delete inotify_queue; - delete inotify_source; - delete inotify_root; - inotify_directories.clear(); -} diff --git a/src/InotifyUpdate.hxx b/src/InotifyUpdate.hxx deleted file mode 100644 index 44d0b10b7..000000000 --- a/src/InotifyUpdate.hxx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 3af1e1686..000000000 --- a/src/InputInit.cxx +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 nullptr if none was configured - */ -static const struct config_param * -input_plugin_config(const char *plugin_name, Error &error) -{ - const struct config_param *param = nullptr; - - while ((param = config_get_next_param(CONF_INPUT, param)) != nullptr) { - const char *name = param->GetBlockValue("plugin"); - if (name == nullptr) { - error.Format(input_domain, - "input configuration without 'plugin' name in line %d", - param->line); - return nullptr; - } - - if (strcmp(name, plugin_name) == 0) - return param; - } - - return nullptr; -} - -bool -input_stream_global_init(Error &error) -{ - const config_param empty; - - for (unsigned i = 0; input_plugins[i] != nullptr; ++i) { - const InputPlugin *plugin = input_plugins[i]; - - assert(plugin->name != nullptr); - assert(*plugin->name != 0); - assert(plugin->open != nullptr); - - 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 == nullptr || 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 != nullptr) - plugin->finish(); -} diff --git a/src/InputInit.hxx b/src/InputInit.hxx deleted file mode 100644 index 6afea1351..000000000 --- a/src/InputInit.hxx +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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. - */ -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/InputPlugin.hxx b/src/InputPlugin.hxx deleted file mode 100644 index 00226d195..000000000 --- a/src/InputPlugin.hxx +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 <stddef.h> -#include <stdint.h> - -struct config_param; -struct InputStream; -class Error; -struct Tag; - -struct InputPlugin { - typedef int64_t offset_type; - - const char *name; - - /** - * Global initialization. This method is called when MPD starts. - * - * @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); - - InputStream *(*open)(const char *uri, - Mutex &mutex, Cond &cond, - Error &error); - void (*close)(InputStream *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)(InputStream *is, Error &error); - - /** - * Update the public attributes. Call before access. Can be - * nullptr if the plugin always keeps its attributes up to date. - */ - void (*update)(InputStream *is); - - Tag *(*tag)(InputStream *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)(InputStream *is); - - size_t (*read)(InputStream *is, void *ptr, size_t size, - Error &error); - bool (*eof)(InputStream *is); - bool (*seek)(InputStream *is, offset_type offset, int whence, - Error &error); -}; - -#endif diff --git a/src/InputRegistry.cxx b/src/InputRegistry.cxx deleted file mode 100644 index aa6c06ed1..000000000 --- a/src/InputRegistry.cxx +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "util/Macros.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 - -const InputPlugin *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 - nullptr -}; - -bool input_plugins_enabled[ARRAY_SIZE(input_plugins) - 1]; diff --git a/src/InputRegistry.hxx b/src/InputRegistry.hxx deleted file mode 100644 index 8bc927a7a..000000000 --- a/src/InputRegistry.hxx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 InputPlugin *const input_plugins[]; - -extern bool input_plugins_enabled[]; - -#define input_plugins_for_each(plugin) \ - for (const InputPlugin *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 deleted file mode 100644 index 28a0aad1a..000000000 --- a/src/InputStream.cxx +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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> -#include <stdio.h> /* for SEEK_SET */ - -static constexpr Domain input_domain("input"); - -InputStream * -InputStream::Open(const char *url, - Mutex &mutex, Cond &cond, - Error &error) -{ - input_plugins_for_each_enabled(plugin) { - InputStream *is; - - is = plugin->open(url, mutex, cond, error); - if (is != nullptr) { - assert(is->plugin.close != nullptr); - assert(is->plugin.read != nullptr); - assert(is->plugin.eof != nullptr); - assert(!is->seekable || is->plugin.seek != nullptr); - - is = input_rewind_open(is); - - return is; - } else if (error.IsDefined()) - return nullptr; - } - - error.Set(input_domain, "Unrecognized URI"); - return nullptr; -} - -bool -InputStream::Check(Error &error) -{ - return plugin.check == nullptr || plugin.check(this, error); -} - -void -InputStream::Update() -{ - if (plugin.update != nullptr) - plugin.update(this); -} - -void -InputStream::WaitReady() -{ - while (true) { - Update(); - if (ready) - break; - - cond.wait(mutex); - } -} - -void -InputStream::LockWaitReady() -{ - const ScopeLock protect(mutex); - WaitReady(); -} - -bool -InputStream::CheapSeeking() const -{ - return IsSeekable() && !uri_has_scheme(uri.c_str()); -} - -bool -InputStream::Seek(offset_type _offset, int whence, Error &error) -{ - if (plugin.seek == nullptr) - return false; - - return plugin.seek(this, _offset, whence, error); -} - -bool -InputStream::LockSeek(offset_type _offset, int whence, Error &error) -{ - if (plugin.seek == nullptr) - return false; - - const ScopeLock protect(mutex); - return Seek(_offset, whence, error); -} - -bool -InputStream::Rewind(Error &error) -{ - return Seek(0, SEEK_SET, error); -} - -bool -InputStream::LockRewind(Error &error) -{ - return LockSeek(0, SEEK_SET, error); -} - -Tag * -InputStream::ReadTag() -{ - return plugin.tag != nullptr - ? plugin.tag(this) - : nullptr; -} - -Tag * -InputStream::LockReadTag() -{ - if (plugin.tag == nullptr) - return nullptr; - - const ScopeLock protect(mutex); - return ReadTag(); -} - -bool -InputStream::IsAvailable() -{ - return plugin.available != nullptr - ? plugin.available(this) - : true; -} - -size_t -InputStream::Read(void *ptr, size_t _size, Error &error) -{ - assert(ptr != nullptr); - assert(_size > 0); - - return plugin.read(this, ptr, _size, error); -} - -size_t -InputStream::LockRead(void *ptr, size_t _size, Error &error) -{ - assert(ptr != nullptr); - assert(_size > 0); - - const ScopeLock protect(mutex); - return Read(ptr, _size, error); -} - -void -InputStream::Close() -{ - plugin.close(this); -} - -bool -InputStream::IsEOF() -{ - return plugin.eof(this); -} - -bool -InputStream::LockIsEOF() -{ - const ScopeLock protect(mutex); - return IsEOF(); -} - diff --git a/src/InputStream.hxx b/src/InputStream.hxx deleted file mode 100644 index b1bc9c4ab..000000000 --- a/src/InputStream.hxx +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.h" - -#include <string> - -#include <assert.h> -#include <stdint.h> - -class Cond; -class Error; -struct Tag; -struct InputPlugin; - -struct InputStream { - typedef int64_t offset_type; - - /** - * the plugin which implements this input stream - */ - const InputPlugin &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 nullptr. - * - * 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 - */ - offset_type size; - - /** - * the current offset within the stream - */ - offset_type offset; - - /** - * the MIME content type of the resource, or empty if unknown. - */ - std::string mime; - - InputStream(const InputPlugin &_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 != nullptr); - } - - /** - * 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 nullptr if the caller doesn't want to get - * notifications - * @return an #InputStream object on success, nullptr on error - */ - gcc_nonnull_all - gcc_malloc - static InputStream *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 - offset_type GetSize() const { - assert(ready); - - return size; - } - - gcc_pure - offset_type 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(offset_type offset, int whence, Error &error); - - /** - * Wrapper for Seek() which locks and unlocks the mutex; the - * caller must not be holding it already. - */ - bool LockSeek(offset_type offset, int whence, Error &error); - - /** - * Rewind to the beginning of the stream. This is a wrapper - * for Seek(0, SEEK_SET, error). - */ - bool Rewind(Error &error); - bool LockRewind(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 InputStream 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 index daad94212..16000afb3 100644 --- a/src/Instance.cxx +++ b/src/Instance.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,16 +21,18 @@ #include "Instance.hxx" #include "Partition.hxx" #include "Idle.hxx" +#include "Stats.hxx" void -Instance::DeleteSong(const Song &song) +Instance::DeleteSong(const char *uri) { - partition->DeleteSong(song); + partition->DeleteSong(uri); } void Instance::DatabaseModified() { + stats_invalidate(); partition->DatabaseModified(); idle_add(IDLE_DATABASE); } @@ -46,3 +48,25 @@ Instance::SyncWithPlayer() { partition->SyncWithPlayer(); } + +void +Instance::OnDatabaseModified() +{ + DatabaseModified(); +} + +#ifdef ENABLE_NEIGHBOR_PLUGINS + +void +Instance::FoundNeighbor(gcc_unused const NeighborInfo &info) +{ + idle_add(IDLE_NEIGHBOR); +} + +void +Instance::LostNeighbor(gcc_unused const NeighborInfo &info) +{ + idle_add(IDLE_NEIGHBOR); +} + +#endif diff --git a/src/Instance.hxx b/src/Instance.hxx index a0dfd1b94..a14719839 100644 --- a/src/Instance.hxx +++ b/src/Instance.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,17 +21,32 @@ #define MPD_INSTANCE_HXX #include "check.h" +#include "db/DatabaseListener.hxx" +#include "Compiler.h" + +#ifdef ENABLE_NEIGHBOR_PLUGINS +#include "neighbor/Listener.hxx" +class NeighborGlue; +#endif class ClientList; struct Partition; -struct Song; -struct Instance { +struct Instance final + : public DatabaseListener +#ifdef ENABLE_NEIGHBOR_PLUGINS + , public NeighborListener +#endif +{ +#ifdef ENABLE_NEIGHBOR_PLUGINS + NeighborGlue *neighbors; +#endif + ClientList *client_list; Partition *partition; - void DeleteSong(const Song &song); + void DeleteSong(const char *uri); /** * The database has been modified. Propagate the change to @@ -49,6 +64,15 @@ struct Instance { * Synchronize the player with the play queue. */ void SyncWithPlayer(); + +private: + virtual void OnDatabaseModified(); + +#ifdef ENABLE_NEIGHBOR_PLUGINS + /* virtual methods from class NeighborListener */ + virtual void FoundNeighbor(const NeighborInfo &info) override; + virtual void LostNeighbor(const NeighborInfo &info) override; +#endif }; #endif diff --git a/src/Listen.cxx b/src/Listen.cxx index d5c132545..faa3e0db1 100644 --- a/src/Listen.cxx +++ b/src/Listen.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,10 +21,10 @@ #include "Listen.hxx" #include "Main.hxx" #include "Instance.hxx" -#include "Client.hxx" -#include "ConfigData.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" +#include "client/Client.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" #include "event/ServerSocket.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" diff --git a/src/Listen.hxx b/src/Listen.hxx index a6fdb2f1c..f14ec665c 100644 --- a/src/Listen.hxx +++ b/src/Listen.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/Log.cxx b/src/Log.cxx index a46c0ced8..ba691581b 100644 --- a/src/Log.cxx +++ b/src/Log.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,62 +19,19 @@ #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 <string.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_INFO; - - case LogLevel::DEFAULT: - 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); + char msg[1024]; + vsnprintf(msg, sizeof(msg), fmt, ap); + Log(domain, level, msg); } void @@ -159,7 +116,7 @@ FormatError(const Error &error, const char *fmt, ...) void LogErrno(const Domain &domain, int e, const char *msg) { - LogFormat(domain, LogLevel::ERROR, "%s: %s", msg, g_strerror(e)); + LogFormat(domain, LogLevel::ERROR, "%s: %s", msg, strerror(e)); } void diff --git a/src/Log.hxx b/src/Log.hxx index 920a8d8a3..15077e374 100644 --- a/src/Log.hxx +++ b/src/Log.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,47 +20,12 @@ #ifndef MPD_LOG_HXX #define MPD_LOG_HXX +#include "LogLevel.hxx" #include "Compiler.h" -#ifdef WIN32 -#include <windows.h> -/* damn you, windows.h! */ -#ifdef ERROR -#undef ERROR -#endif -#endif - class Error; class Domain; -enum class LogLevel { - /** - * Debug message for developers. - */ - DEBUG, - - /** - * Unimportant informational message. - */ - INFO, - - /** - * Interesting informational message. - */ - DEFAULT, - - /** - * Warning: something may be wrong. - */ - WARNING, - - /** - * An error has occurred, an operation could not finish - * successfully. - */ - ERROR, -}; - void Log(const Domain &domain, LogLevel level, const char *msg); diff --git a/src/LogBackend.cxx b/src/LogBackend.cxx new file mode 100644 index 000000000..ba37c1dbb --- /dev/null +++ b/src/LogBackend.cxx @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "LogBackend.hxx" +#include "Log.hxx" +#include "util/Domain.hxx" +#include "util/CharUtil.hxx" + +#ifdef HAVE_GLIB +#include <glib.h> +#endif + +#include <assert.h> +#include <stdio.h> +#include <string.h> +#include <time.h> + +#ifdef HAVE_SYSLOG +#include <syslog.h> +#endif + +static LogLevel log_threshold = LogLevel::INFO; + +#ifdef HAVE_GLIB +static const char *log_charset; +#endif + +static bool enable_timestamp; + +#ifdef HAVE_SYSLOG +static bool enable_syslog; +#endif + +void +SetLogThreshold(LogLevel _threshold) +{ + log_threshold = _threshold; +} + +#ifdef HAVE_GLIB + +void +SetLogCharset(const char *_charset) +{ + log_charset = _charset; +} + +#endif + +void +EnableLogTimestamp() +{ +#ifdef HAVE_SYSLOG + assert(!enable_syslog); +#endif + assert(!enable_timestamp); + + enable_timestamp = true; +} + +static const char *log_date(void) +{ + static constexpr size_t LOG_DATE_BUF_SIZE = 16; + static char buf[LOG_DATE_BUF_SIZE]; + time_t t = time(nullptr); + 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 && IsWhitespaceOrNull(p[length - 1])) + --length; + + return (int)length; +} + +#ifdef HAVE_SYSLOG + +static int +ToSysLogLevel(LogLevel log_level) +{ + switch (log_level) { + case LogLevel::DEBUG: + return LOG_DEBUG; + + case LogLevel::INFO: + return LOG_INFO; + + case LogLevel::DEFAULT: + return LOG_NOTICE; + + case LogLevel::WARNING: + return LOG_WARNING; + + case LogLevel::ERROR: + return LOG_ERR; + } + + assert(false); + gcc_unreachable(); +} + +static void +SysLog(const Domain &domain, LogLevel log_level, const char *message) +{ + syslog(ToSysLogLevel(log_level), "%s: %.*s", + domain.GetName(), + chomp_length(message), message); +} + +void +LogInitSysLog() +{ + openlog(PACKAGE, 0, LOG_DAEMON); + enable_syslog = true; +} + +void +LogFinishSysLog() +{ + if (enable_syslog) + closelog(); +} + +#endif + +static void +FileLog(const Domain &domain, const char *message) +{ +#ifdef HAVE_GLIB + char *converted; + + if (log_charset != nullptr) { + converted = g_convert_with_fallback(message, -1, + log_charset, "utf-8", + nullptr, nullptr, + nullptr, nullptr); + if (converted != nullptr) + message = converted; + } else + converted = nullptr; +#endif + + fprintf(stderr, "%s%s: %.*s\n", + enable_timestamp ? log_date() : "", + domain.GetName(), + chomp_length(message), message); + +#ifdef HAVE_GLIB + g_free(converted); +#endif +} + +void +Log(const Domain &domain, LogLevel level, const char *msg) +{ + if (level < log_threshold) + return; + +#ifdef HAVE_SYSLOG + if (enable_syslog) { + SysLog(domain, level, msg); + return; + } +#endif + + FileLog(domain, msg); +} diff --git a/src/LogBackend.hxx b/src/LogBackend.hxx new file mode 100644 index 000000000..75a692ede --- /dev/null +++ b/src/LogBackend.hxx @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_BACKEND_HXX +#define MPD_LOG_BACKEND_HXX + +#include "LogLevel.hxx" + +void +SetLogThreshold(LogLevel _threshold); + +void +SetLogCharset(const char *_charset); + +void +EnableLogTimestamp(); + +void +LogInitSysLog(); + +void +LogFinishSysLog(); + +#endif /* LOG_H */ diff --git a/src/LogInit.cxx b/src/LogInit.cxx index 41d13a5e8..2a5d4d7c5 100644 --- a/src/LogInit.cxx +++ b/src/LogInit.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,48 +19,36 @@ #include "config.h" #include "LogInit.hxx" +#include "LogBackend.hxx" #include "Log.hxx" -#include "ConfigData.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" -#include "system/fd_util.h" +#include "config/ConfigData.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" #include "system/FatalError.hxx" #include "fs/AllocatedPath.hxx" #include "fs/FileSystem.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" -#include "util/CharUtil.hxx" #include "system/FatalError.hxx" +#ifdef HAVE_GLIB +#include <glib.h> +#endif + #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_LEVEL_SECURE LogLevel::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 AllocatedPath out_path = AllocatedPath::Null(); @@ -73,66 +61,6 @@ static void redirect_logs(int fd) FatalSystemError("Failed to dup2 stderr"); } -static const char *log_date(void) -{ - static char buf[LOG_DATE_BUF_SIZE]; - time_t t = time(nullptr); - 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 && IsWhitespaceOrNull(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 != nullptr) { - converted = g_convert_with_fallback(message, -1, - log_charset, "utf-8", - nullptr, nullptr, - nullptr, nullptr); - if (converted != nullptr) - message = converted; - } else - converted = nullptr; - - 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, nullptr); -} - static int open_log_file(void) { @@ -154,85 +82,22 @@ log_init_file(unsigned line, Error &error) return false; } - g_log_set_default_handler(file_log_func, nullptr); + EnableLogTimestamp(); 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, nullptr); -} - -#endif - -static inline GLogLevelFlags +static inline LogLevel parse_log_level(const char *value, unsigned line) { if (0 == strcmp(value, "default")) - return G_LOG_LEVEL_MESSAGE; + return LogLevel::DEFAULT; if (0 == strcmp(value, "secure")) return LOG_LEVEL_SECURE; else if (0 == strcmp(value, "verbose")) - return G_LOG_LEVEL_DEBUG; + return LogLevel::DEBUG; else { FormatFatalError("unknown log level \"%s\" at line %u", value, line); - return G_LOG_LEVEL_MESSAGE; } } @@ -240,9 +105,7 @@ void log_early_init(bool verbose) { if (verbose) - log_threshold = G_LOG_LEVEL_DEBUG; - - log_init_stdout(); + SetLogThreshold(LogLevel::DEBUG); } bool @@ -250,16 +113,19 @@ log_init(bool verbose, bool use_stdout, Error &error) { const struct config_param *param; - g_get_charset(&log_charset); +#ifdef HAVE_GLIB + const char *charset; + g_get_charset(&charset); + SetLogCharset(charset); +#endif if (verbose) - log_threshold = G_LOG_LEVEL_DEBUG; + SetLogThreshold(LogLevel::DEBUG); else if ((param = config_get_param(CONF_LOG_LEVEL)) != nullptr) - log_threshold = parse_log_level(param->value.c_str(), - param->line); + SetLogThreshold(parse_log_level(param->value.c_str(), + param->line)); if (use_stdout) { - log_init_stdout(); return true; } else { param = config_get_param(CONF_LOG_FILE); @@ -267,7 +133,7 @@ log_init(bool verbose, bool use_stdout, Error &error) #ifdef HAVE_SYSLOG /* no configuration: default to syslog (if available) */ - log_init_syslog(); + LogInitSysLog(); return true; #else error.Set(log_domain, @@ -276,7 +142,7 @@ log_init(bool verbose, bool use_stdout, Error &error) #endif #ifdef HAVE_SYSLOG } else if (strcmp(param->value.c_str(), "syslog") == 0) { - log_init_syslog(); + LogInitSysLog(); return true; #endif } else { @@ -290,12 +156,8 @@ log_init(bool verbose, bool use_stdout, Error &error) static void close_log_files(void) { - if (stdout_mode) - return; - #ifdef HAVE_SYSLOG - if (out_path.IsNull()) - closelog(); + LogFinishSysLog(); #endif } @@ -309,32 +171,35 @@ log_deinit(void) void setup_log_output(bool use_stdout) { + if (use_stdout) + return; + fflush(nullptr); - if (!use_stdout) { -#ifndef WIN32 - if (out_path.IsNull()) - out_fd = open("/dev/null", O_WRONLY); + + if (out_fd < 0) { +#ifdef WIN32 + return; +#else + out_fd = open("/dev/null", O_WRONLY); + if (out_fd < 0) + return; #endif + } - if (out_fd >= 0) { - redirect_logs(out_fd); - close(out_fd); - } + redirect_logs(out_fd); + close(out_fd); + out_fd = -1; - stdout_mode = false; - log_charset = nullptr; - } + SetLogCharset(nullptr); } int cycle_log_files(void) { int fd; - if (stdout_mode || out_path.IsNull()) + if (out_path.IsNull()) return 0; - assert(!out_path.IsNull()); - FormatDebug(log_domain, "Cycling log files"); close_log_files(); diff --git a/src/LogInit.hxx b/src/LogInit.hxx index 2369fcdca..d0bcb40f2 100644 --- a/src/LogInit.hxx +++ b/src/LogInit.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/LogLevel.hxx b/src/LogLevel.hxx new file mode 100644 index 000000000..2614a67d1 --- /dev/null +++ b/src/LogLevel.hxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_LEVEL_HXX +#define MPD_LOG_LEVEL_HXX + +#ifdef WIN32 +#include <windows.h> +/* damn you, windows.h! */ +#ifdef ERROR +#undef ERROR +#endif +#endif + +enum class LogLevel { + /** + * Debug message for developers. + */ + DEBUG, + + /** + * Unimportant informational message. + */ + INFO, + + /** + * Interesting informational message. + */ + DEFAULT, + + /** + * Warning: something may be wrong. + */ + WARNING, + + /** + * An error has occurred, an operation could not finish + * successfully. + */ + ERROR, +}; + +#endif diff --git a/src/LogV.hxx b/src/LogV.hxx index 4bd4f801d..6b16f82b4 100644 --- a/src/LogV.hxx +++ b/src/LogV.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,7 @@ #ifndef MPD_LOGV_HXX #define MPD_LOGV_HXX -#include "Log.hxx" +#include "Log.hxx" // IWYU pragma: export #include <stdarg.h> diff --git a/src/Main.cxx b/src/Main.cxx index b45e2c3ae..f790ec574 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,67 +23,71 @@ #include "CommandLine.hxx" #include "PlaylistFile.hxx" #include "PlaylistGlobal.hxx" -#include "UpdateGlue.hxx" +#include "db/update/UpdateGlue.hxx" #include "MusicChunk.hxx" #include "StateFile.hxx" #include "PlayerThread.hxx" #include "Mapper.hxx" -#include "DatabaseGlue.hxx" -#include "DatabaseSimple.hxx" +#include "db/DatabaseGlue.hxx" +#include "db/DatabaseSimple.hxx" #include "Permission.hxx" #include "Listen.hxx" -#include "Client.hxx" -#include "ClientList.hxx" +#include "client/Client.hxx" +#include "client/ClientList.hxx" #include "command/AllCommands.hxx" #include "Partition.hxx" -#include "Volume.hxx" -#include "OutputAll.hxx" +#include "mixer/Volume.hxx" +#include "output/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 "input/Init.hxx" #include "event/Loop.hxx" #include "IOThread.hxx" #include "fs/AllocatedPath.hxx" #include "fs/Config.hxx" -#include "PlaylistRegistry.hxx" -#include "ZeroconfGlue.hxx" -#include "DecoderList.hxx" +#include "fs/StandardDirectory.hxx" +#include "playlist/PlaylistRegistry.hxx" +#include "zeroconf/ZeroconfGlue.hxx" +#include "decoder/DecoderList.hxx" #include "AudioConfig.hxx" -#include "pcm/PcmResample.hxx" -#include "Daemon.hxx" +#include "pcm/PcmConvert.hxx" +#include "unix/SignalHandlers.hxx" +#include "unix/Daemon.hxx" #include "system/FatalError.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "thread/Id.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigData.hxx" -#include "ConfigDefaults.hxx" -#include "ConfigOption.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigDefaults.hxx" +#include "config/ConfigOption.hxx" #include "Stats.hxx" +#ifdef ENABLE_NEIGHBOR_PLUGINS +#include "neighbor/Glue.hxx" +#endif + #ifdef ENABLE_INOTIFY -#include "InotifyUpdate.hxx" +#include "db/update/InotifyUpdate.hxx" #endif #ifdef ENABLE_SQLITE -#include "StickerDatabase.hxx" +#include "sticker/StickerDatabase.hxx" #endif #ifdef ENABLE_ARCHIVE -#include "ArchiveList.hxx" +#include "archive/ArchiveList.hxx" #endif +#ifdef HAVE_GLIB #include <glib.h> +#endif -#include <unistd.h> #include <stdlib.h> -#include <errno.h> -#include <string.h> #ifdef HAVE_LOCALE_H #include <locale.h> @@ -94,6 +98,8 @@ #include <ws2tcpip.h> #endif +#include <limits.h> + static constexpr unsigned DEFAULT_BUFFER_SIZE = 4096; static constexpr unsigned DEFAULT_BUFFER_BEFORE_PLAY = 10; @@ -135,13 +141,9 @@ glue_mapper_init(Error &error) return false; if (music_dir.IsNull()) { - const char *path = - g_get_user_special_dir(G_USER_DIRECTORY_MUSIC); - if (path != nullptr) { - music_dir = AllocatedPath::FromUTF8(path, error); - if (music_dir.IsNull()) - return false; - } + music_dir = GetUserMusicDir(); + if (music_dir.IsNull()) + return true; } mapper_init(std::move(music_dir), std::move(playlist_dir)); @@ -188,7 +190,7 @@ glue_db_init_and_load(void) return true; Error error; - if (!DatabaseGlobalInit(*param, error)) + if (!DatabaseGlobalInit(*main_loop, *instance, *param, error)) FatalError(error); delete allocated; @@ -240,9 +242,8 @@ static void winsock_init(void) { #ifdef WIN32 WSADATA sockinfo; - int retval; - retval = WSAStartup(MAKEWORD(2, 2), &sockinfo); + int retval = WSAStartup(MAKEWORD(2, 2), &sockinfo); if(retval != 0) FormatFatalError("Attempt to open Winsock2 failed; error code %d", retval); @@ -260,14 +261,11 @@ 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; + size_t buffer_size; param = config_get_param(CONF_AUDIO_BUFFER_SIZE); if (param != nullptr) { + char *test; long tmp = strtol(param->value.c_str(), &test, 10); if (*test != '\0' || tmp <= 0 || tmp == LONG_MAX) FormatFatalError("buffer size \"%s\" is not a " @@ -279,14 +277,16 @@ initialize_decoder_and_player(void) buffer_size *= 1024; - buffered_chunks = buffer_size / CHUNK_SIZE; + const unsigned buffered_chunks = buffer_size / CHUNK_SIZE; if (buffered_chunks >= 1 << 15) FormatFatalError("buffer size \"%lu\" is too big", (unsigned long)buffer_size); + float perc; param = config_get_param(CONF_BUFFER_BEFORE_PLAY); if (param != nullptr) { + char *test; perc = strtod(param->value.c_str(), &test); if (*test != '%' || perc < 0 || perc > 100) { FormatFatalError("buffered before play \"%s\" is not " @@ -297,7 +297,7 @@ initialize_decoder_and_player(void) } else perc = DEFAULT_BUFFER_BEFORE_PLAY; - buffered_before_play = (perc / 100) * buffered_chunks; + unsigned buffered_before_play = (perc / 100) * buffered_chunks; if (buffered_before_play > buffered_chunks) buffered_before_play = buffered_chunks; @@ -353,10 +353,7 @@ int main(int argc, char *argv[]) int mpd_main(int argc, char *argv[]) { struct options options; - clock_t start; - bool create_db; Error error; - bool success; daemonize_close_stdin(); @@ -365,19 +362,20 @@ int mpd_main(int argc, char *argv[]) setlocale(LC_CTYPE,""); #endif +#ifdef HAVE_GLIB 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 +#endif - io_thread_init(); winsock_init(); + io_thread_init(); config_global_init(); - success = parse_cmdline(argc, argv, &options, error); - if (!success) { + if (!parse_cmdline(argc, argv, &options, error)) { LogError(error); return EXIT_FAILURE; } @@ -400,16 +398,29 @@ int mpd_main(int argc, char *argv[]) instance = new Instance(); +#ifdef ENABLE_NEIGHBOR_PLUGINS + instance->neighbors = new NeighborGlue(); + if (!instance->neighbors->Init(io_thread_get(), *instance, error)) { + LogError(error); + return EXIT_FAILURE; + } + + if (instance->neighbors->IsEmpty()) { + delete instance->neighbors; + instance->neighbors = nullptr; + } +#endif + 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) { + if (!listen_global_init(error)) { LogError(error); return EXIT_FAILURE; } daemonize_set_user(); + daemonize_begin(options.daemon); GlobalEvents::Initialize(*main_loop); GlobalEvents::Register(GlobalEvents::IDLE, idle_event_emitted); @@ -431,7 +442,7 @@ int mpd_main(int argc, char *argv[]) archive_plugin_init_all(); #endif - if (!pcm_resample_global_init(error)) { + if (!pcm_convert_global_init(error)) { LogError(error); return EXIT_FAILURE; } @@ -439,7 +450,7 @@ int mpd_main(int argc, char *argv[]) decoder_plugin_init_all(); update_global_init(); - create_db = !glue_db_init_and_load(); + const bool create_db = !glue_db_init_and_load(); glue_sticker_init(); @@ -458,7 +469,7 @@ int mpd_main(int argc, char *argv[]) playlist_list_global_init(); - daemonize(options.daemon); + daemonize_commit(); setup_log_output(options.log_stderr); @@ -466,6 +477,12 @@ int mpd_main(int argc, char *argv[]) io_thread_start(); +#ifdef ENABLE_NEIGHBOR_PLUGINS + if (instance->neighbors != nullptr && + !instance->neighbors->Open(error)) + FatalError(error); +#endif + ZeroconfInit(*main_loop); player_create(instance->partition->pc); @@ -479,22 +496,22 @@ int mpd_main(int argc, char *argv[]) } if (!glue_state_file_init(error)) { - g_printerr("%s\n", error.GetMessage()); + LogError(error); 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); + if (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)); + if (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(); @@ -529,7 +546,14 @@ int mpd_main(int argc, char *argv[]) listen_global_finish(); delete instance->client_list; - start = clock(); +#ifdef ENABLE_NEIGHBOR_PLUGINS + if (instance->neighbors != nullptr) { + instance->neighbors->Close(); + delete instance->neighbors; + } +#endif + + const clock_t start = clock(); DatabaseGlobalDeinit(); FormatDebug(main_domain, "db_finish took %f seconds", @@ -544,7 +568,6 @@ int mpd_main(int argc, char *argv[]) playlist_list_global_finish(); input_stream_global_finish(); audio_output_all_finish(); - volume_finish(); mapper_finish(); delete instance->partition; command_finish(); @@ -554,7 +577,6 @@ int mpd_main(int argc, char *argv[]) archive_plugin_deinit_all(); #endif config_global_finish(); - stats_global_finish(); io_thread_deinit(); SignalHandlersFinish(); delete instance; diff --git a/src/Main.hxx b/src/Main.hxx index a8ec184c8..454519f46 100644 --- a/src/Main.hxx +++ b/src/Main.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/Mapper.cxx b/src/Mapper.cxx index cbe45daa0..ebcab91bf 100644 --- a/src/Mapper.cxx +++ b/src/Mapper.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,8 +23,10 @@ #include "config.h" #include "Mapper.hxx" -#include "Directory.hxx" -#include "Song.hxx" +#include "DetachedSong.hxx" +#include "db/Directory.hxx" +#include "db/Song.hxx" +#include "db/LightSong.hxx" #include "fs/AllocatedPath.hxx" #include "fs/Traits.hxx" #include "fs/Charset.hxx" @@ -36,9 +38,7 @@ #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"); @@ -150,7 +150,7 @@ map_to_relative_path(const char *path_utf8) return !music_dir_utf8.empty() && memcmp(path_utf8, music_dir_utf8.c_str(), music_dir_utf8_length) == 0 && - PathTraits::IsSeparatorUTF8(path_utf8[music_dir_utf8_length]) + PathTraitsUTF8::IsSeparator(path_utf8[music_dir_utf8_length]) ? path_utf8 + music_dir_utf8_length + 1 : path_utf8; } @@ -218,23 +218,41 @@ map_detached_song_fs(const char *uri_utf8) return AllocatedPath::Build(music_dir_fs, uri_fs); } +DetachedSong +map_song_detach(const LightSong &song) +{ + DetachedSong detached(song); + + if (detached.IsInDatabase() && !detached.HasRealURI()) { + const auto uri = song.GetURI(); + detached.SetRealURI(PathTraitsUTF8::Build(music_dir_utf8.c_str(), + uri.c_str())); + } + + return detached; +} + AllocatedPath map_song_fs(const Song &song) { - assert(song.IsFile()); + return song.parent == nullptr + ? map_detached_song_fs(song.uri) + : map_directory_child_fs(*song.parent, song.uri); +} - if (song.IsInDatabase()) - return song.IsDetached() - ? map_detached_song_fs(song.uri) - : map_directory_child_fs(*song.parent, song.uri); +AllocatedPath +map_song_fs(const DetachedSong &song) +{ + if (song.IsAbsoluteFile()) + return AllocatedPath::FromUTF8(song.GetRealURI()); else - return AllocatedPath::FromUTF8(song.uri); + return map_uri_fs(song.GetURI()); } std::string map_fs_to_utf8(const char *path_fs) { - if (PathTraits::IsSeparatorFS(path_fs[0])) { + if (PathTraitsFS::IsSeparator(path_fs[0])) { path_fs = music_dir_fs.RelativeFS(path_fs); if (path_fs == nullptr || *path_fs == 0) return std::string(); diff --git a/src/Mapper.hxx b/src/Mapper.hxx index 947fd2822..5c01a9aff 100644 --- a/src/Mapper.hxx +++ b/src/Mapper.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -30,10 +30,11 @@ #define PLAYLIST_FILE_SUFFIX ".m3u" -class Path; class AllocatedPath; struct Directory; struct Song; +struct LightSong; +class DetachedSong; void mapper_init(AllocatedPath &&music_dir, AllocatedPath &&playlist_dir); @@ -107,6 +108,14 @@ AllocatedPath map_directory_child_fs(const Directory &directory, const char *name); /** + * "Detach" the #Song object, i.e. convert it to a #DetachedSong + * instance. + */ +gcc_pure +DetachedSong +map_song_detach(const LightSong &song); + +/** * Determines the file system path of a song. This must not be a * remote song. * @@ -117,6 +126,10 @@ gcc_pure AllocatedPath map_song_fs(const Song &song); +gcc_pure +AllocatedPath +map_song_fs(const DetachedSong &song); + /** * Maps a file system path (relative to the music directory or * absolute) to a relative path in UTF-8 encoding. @@ -138,8 +151,7 @@ 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(). + * path. * * @return the path in file system encoding, or nullptr if mapping failed */ diff --git a/src/MemorySongEnumerator.cxx b/src/MemorySongEnumerator.cxx deleted file mode 100644 index 7c9d05daa..000000000 --- a/src/MemorySongEnumerator.cxx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index 46086a064..000000000 --- a/src/MemorySongEnumerator.hxx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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/MixRampInfo.hxx b/src/MixRampInfo.hxx index 9af41b77d..90c2c984a 100644 --- a/src/MixRampInfo.hxx +++ b/src/MixRampInfo.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/MixerAll.cxx b/src/MixerAll.cxx deleted file mode 100644 index 37225fd25..000000000 --- a/src/MixerAll.cxx +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 == nullptr) - 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 == nullptr) - 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 == nullptr || !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 != nullptr && - output->mixer->plugin == &software_mixer_plugin) - mixer_set_volume(output->mixer, volume, IgnoreError()); - } -} diff --git a/src/MixerAll.hxx b/src/MixerAll.hxx deleted file mode 100644 index fa7c89801..000000000 --- a/src/MixerAll.hxx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 - -#include "Compiler.h" - -/** - * Returns the average volume of all available mixers (range 0..100). - * Returns -1 if no mixer can be queried. - */ -gcc_pure -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. - */ -gcc_pure -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 deleted file mode 100644 index dd4f03543..000000000 --- a/src/MixerControl.cxx +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 != nullptr); - - mixer = plugin->init(ao, param, error); - - assert(mixer == nullptr || mixer->IsPlugin(*plugin)); - - return mixer; -} - -void -mixer_free(Mixer *mixer) -{ - assert(mixer != nullptr); - assert(mixer->plugin != nullptr); - - /* 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 != nullptr); - assert(mixer->plugin != nullptr); - - const ScopeLock protect(mixer->mutex); - - if (mixer->open) - success = true; - else if (mixer->plugin->open == nullptr) - 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 != nullptr); - assert(mixer->plugin != nullptr); - assert(mixer->open); - - if (mixer->plugin->close != nullptr) - mixer->plugin->close(mixer); - - mixer->open = false; -} - -void -mixer_close(Mixer *mixer) -{ - assert(mixer != nullptr); - assert(mixer->plugin != nullptr); - - 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 != nullptr); - - 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 != nullptr); - 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 deleted file mode 100644 index c1ee01eec..000000000 --- a/src/MixerControl.hxx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 deleted file mode 100644 index e421a34b4..000000000 --- a/src/MixerInternal.hxx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 440f442ba..000000000 --- a/src/MixerList.hxx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 deleted file mode 100644 index db6fb2471..000000000 --- a/src/MixerPlugin.hxx +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 - * nullptr to ignore errors - * @return a mixer object, or nullptr 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 - * nullptr 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 - * nullptr 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 - * nullptr 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 deleted file mode 100644 index 435079790..000000000 --- a/src/MixerType.cxx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index 320a36c04..000000000 --- a/src/MixerType.hxx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 index c811d8627..26dc11591 100644 --- a/src/MusicBuffer.cxx +++ b/src/MusicBuffer.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/MusicBuffer.hxx b/src/MusicBuffer.hxx index d2b23d43a..84e2af1d1 100644 --- a/src/MusicBuffer.hxx +++ b/src/MusicBuffer.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/MusicChunk.cxx b/src/MusicChunk.cxx index 2d20ac7ac..899163485 100644 --- a/src/MusicChunk.cxx +++ b/src/MusicChunk.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/MusicChunk.hxx b/src/MusicChunk.hxx index ecd57090b..97fc860d9 100644 --- a/src/MusicChunk.hxx +++ b/src/MusicChunk.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/MusicPipe.cxx b/src/MusicPipe.cxx index a5bbe590e..d7e36f2a4 100644 --- a/src/MusicPipe.cxx +++ b/src/MusicPipe.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/MusicPipe.hxx b/src/MusicPipe.hxx index f2db33cc5..3af77e60f 100644 --- a/src/MusicPipe.hxx +++ b/src/MusicPipe.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/OutputAPI.hxx b/src/OutputAPI.hxx deleted file mode 100644 index e905fd9db..000000000 --- a/src/OutputAPI.hxx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 36d41184a..000000000 --- a/src/OutputAll.cxx +++ /dev/null @@ -1,594 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 <glib.h> - -#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] != nullptr); - - 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 nullptr; -} - -gcc_const -static unsigned -audio_output_config_count(void) -{ - unsigned int nr = 0; - const struct config_param *param = nullptr; - - 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(PlayerControl &pc) -{ - const struct config_param *param = nullptr; - 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 == nullptr) { - if (param != nullptr) - 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 = nullptr; - 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 != nullptr) { - g_timer_destroy(ao->fail_timer); - ao->fail_timer = nullptr; - } -} - -/** - * 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(ReplayGainMode 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 != nullptr); - assert(g_mp != nullptr); - assert(chunk != nullptr); - 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 == nullptr || g_music_buffer == &buffer); - assert((g_mp == nullptr) == (g_music_buffer == nullptr)); - - g_music_buffer = &buffer; - - /* the audio format must be the same as existing chunks in the - pipe */ - assert(g_mp == nullptr || g_mp->CheckFormat(audio_format)); - - if (g_mp == nullptr) - 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 == nullptr) - return false; - - assert(chunk == ao->chunk || g_mp->Contains(ao->chunk)); - - if (chunk != ao->chunk) { - assert(chunk->next != nullptr); - return true; - } - - return ao->chunk_finished && chunk->next == nullptr; -} - -/** - * 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 == nullptr); - 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 = nullptr; - } -} - -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 != nullptr); - assert(g_mp != nullptr); - - 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 == nullptr; - 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(PlayerControl &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 != nullptr) - 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 != nullptr) { - assert(g_music_buffer != nullptr); - - g_mp->Clear(*g_music_buffer); - delete g_mp; - g_mp = nullptr; - } - - g_music_buffer = nullptr; - - 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 != nullptr) { - assert(g_music_buffer != nullptr); - - g_mp->Clear(*g_music_buffer); - delete g_mp; - g_mp = nullptr; - } - - g_music_buffer = nullptr; - - 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 deleted file mode 100644 index 98061c345..000000000 --- a/src/OutputAll.hxx +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 "Compiler.h" - -struct AudioFormat; -class MusicBuffer; -struct music_chunk; -struct PlayerControl; -class Error; - -/** - * Global initialization: load audio outputs from the configuration - * file and initialize them. - */ -void -audio_output_all_init(PlayerControl &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(ReplayGainMode 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(PlayerControl &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 deleted file mode 100644 index 10b5bb322..000000000 --- a/src/OutputCommand.cxx +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 != nullptr) { - 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 deleted file mode 100644 index 46fab92c5..000000000 --- a/src/OutputCommand.hxx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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/OutputControl.cxx b/src/OutputControl.cxx deleted file mode 100644 index 27f280231..000000000 --- a/src/OutputControl.cxx +++ /dev/null @@ -1,336 +0,0 @@ - -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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> - -/** after a failure, wait this number of seconds before - automatically reopening the device */ -static constexpr unsigned 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, - ReplayGainMode mode) -{ - if (ao->replay_gain_filter != nullptr) - replay_gain_filter_set_mode(ao->replay_gain_filter, mode); - if (ao->other_replay_gain_filter != nullptr) - replay_gain_filter_set_mode(ao->other_replay_gain_filter, mode); -} - -void -audio_output_enable(struct audio_output *ao) -{ - if (!ao->thread.IsDefined()) { - if (ao->plugin->enable == nullptr) { - /* 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.IsDefined()) { - if (ao->plugin->disable == nullptr) - 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 != nullptr); - assert(ao->allow_play); - assert(audio_format.IsValid()); - - if (ao->fail_timer != nullptr) { - g_timer_destroy(ao->fail_timer); - ao->fail_timer = nullptr; - } - - if (ao->open && audio_format == ao->in_audio_format) { - assert(ao->pipe == &mp || - (ao->always_on && ao->pause)); - - if (ao->pause) { - ao->chunk = nullptr; - 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 = nullptr; - - ao->pipe = ∓ - - if (!ao->thread.IsDefined()) - audio_output_thread_start(ao); - - ao_command(ao, ao->open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN); - open = ao->open; - - if (open && ao->mixer != nullptr) { - 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 != nullptr); - assert(ao->allow_play); - - if (ao->mixer != nullptr) - mixer_auto_close(ao->mixer); - - assert(!ao->open || ao->fail_timer == nullptr); - - if (ao->open) - ao_command(ao, AO_COMMAND_CLOSE); - else if (ao->fail_timer != nullptr) { - g_timer_destroy(ao->fail_timer); - ao->fail_timer = nullptr; - } -} - -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 == nullptr || - g_timer_elapsed(ao->fail_timer, nullptr) > 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->in_playback_loop && - !ao->woken_for_play) { - ao->woken_for_play = true; - ao->cond.signal(); - } -} - -void audio_output_pause(struct audio_output *ao) -{ - if (ao->mixer != nullptr && ao->plugin->pause == nullptr) - /* 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 != nullptr); - assert(!ao->open || ao->fail_timer == nullptr); - - 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 == nullptr); - - if (ao->thread.IsDefined()) { - assert(ao->allow_play); - ao_lock_command(ao, AO_COMMAND_KILL); - ao->thread.Join(); - } - - audio_output_free(ao); -} diff --git a/src/OutputControl.hxx b/src/OutputControl.hxx deleted file mode 100644 index d8f0b432d..000000000 --- a/src/OutputControl.hxx +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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; - -void -audio_output_set_replay_gain_mode(struct audio_output *ao, - ReplayGainMode 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 deleted file mode 100644 index a18bfff23..000000000 --- a/src/OutputError.cxx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License 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 deleted file mode 100644 index 22a11fdbd..000000000 --- a/src/OutputError.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index db6599b53..000000000 --- a/src/OutputFinish.cxx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 == nullptr); - assert(!ao->thread.IsDefined()); - - if (ao->mixer != nullptr) - 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 == nullptr); - assert(!ao->thread.IsDefined()); - - ao_plugin_finish(ao); -} diff --git a/src/OutputInit.cxx b/src/OutputInit.cxx deleted file mode 100644 index 28eba1ab2..000000000 --- a/src/OutputInit.cxx +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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) -{ - LogDefault(output_domain, "Attempt to detect audio output device"); - - audio_output_plugins_for_each(plugin) { - if (plugin->test_default_device == nullptr) - continue; - - FormatDefault(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 nullptr; -} - -/** - * 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 != nullptr) - 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 nullptr; - - case MIXER_TYPE_HARDWARE: - if (plugin == nullptr) - return nullptr; - - return mixer_new(plugin, ao, param, error); - - case MIXER_TYPE_SOFTWARE: - mixer = mixer_new(&software_mixer_plugin, nullptr, - config_param(), - IgnoreError()); - assert(mixer != nullptr); - - 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 != nullptr); - assert(plugin != nullptr); - assert(plugin->finish != nullptr); - assert(plugin->open != nullptr); - assert(plugin->close != nullptr); - assert(plugin->play != nullptr); - - if (!param.IsNull()) { - ao->name = param.GetBlockValue(AUDIO_OUTPUT_NAME); - if (ao->name == nullptr) { - error.Set(config_domain, - "Missing \"name\" configuration"); - return false; - } - - const char *p = param.GetBlockValue(AUDIO_OUTPUT_FORMAT); - if (p != nullptr) { - 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->in_playback_loop = false; - ao->woken_for_play = false; - ao->fail_timer = nullptr; - - /* set up the filter chain */ - - ao->filter = filter_chain_new(); - assert(ao->filter != nullptr); - - /* 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 != nullptr); - - 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->command = AO_COMMAND_NONE; - - ao->mixer = nullptr; - ao->replay_gain_filter = nullptr; - ao->other_replay_gain_filter = nullptr; - - /* 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 != nullptr); - - ao->replay_gain_serial = 0; - - ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin, - param, - IgnoreError()); - assert(ao->other_replay_gain_filter != nullptr); - - ao->other_replay_gain_serial = 0; - } else { - ao->replay_gain_filter = nullptr; - ao->other_replay_gain_filter = nullptr; - } - - /* 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 == nullptr && 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 != nullptr) - 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 != nullptr) { - 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 != nullptr); - - filter_chain_append(*ao->filter, "convert", ao->convert_filter); - - return true; -} - -struct audio_output * -audio_output_new(const config_param ¶m, - PlayerControl &pc, - Error &error) -{ - const struct audio_output_plugin *plugin; - - if (!param.IsNull()) { - const char *p; - - p = param.GetBlockValue(AUDIO_OUTPUT_TYPE); - if (p == nullptr) { - error.Set(config_domain, - "Missing \"type\" configuration"); - return nullptr; - } - - plugin = audio_output_plugin_get(p); - if (plugin == nullptr) { - 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 == nullptr) - return nullptr; - - FormatDefault(output_domain, - "Successfully detected a %s audio device", - plugin->name); - } - - struct audio_output *ao = ao_plugin_init(plugin, param, error); - if (ao == nullptr) - return nullptr; - - if (!audio_output_setup(ao, param, error)) { - ao_plugin_finish(ao); - return nullptr; - } - - ao->player_control = &pc; - return ao; -} diff --git a/src/OutputInternal.hxx b/src/OutputInternal.hxx deleted file mode 100644 index c07cdf856..000000000 --- a/src/OutputInternal.hxx +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "thread/Thread.hxx" - -#include <time.h> - -class Error; -class Filter; -class MusicPipe; -struct config_param; -struct PlayerControl; -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 nullptr 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; - - /** - * True while the OutputThread is inside ao_play(). This - * means the PlayerThread does not need to wake up the - * OutputThread when new chunks are added to the MusicPipe, - * because the OutputThread is already watching that. - */ - bool in_playback_loop; - - /** - * Has the OutputThread been woken up to play more chunks? - * This is set by audio_output_play() and reset by ao_play() - * to reduce the number of duplicate wakeups. - */ - bool woken_for_play; - - /** - * If not nullptr, 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 nullptr if the output thread isn't - * running. - */ - Thread 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 PlayerControl object which "owns" this output. This - * object is needed to signal command completion. - */ - PlayerControl *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, - PlayerControl &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 deleted file mode 100644 index b569408dc..000000000 --- a/src/OutputList.cxx +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 - nullptr -}; - -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 nullptr; -} diff --git a/src/OutputList.hxx b/src/OutputList.hxx deleted file mode 100644 index 756cf22a4..000000000 --- a/src/OutputList.hxx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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) != nullptr; ++output_plugin_iterator) - -#endif diff --git a/src/OutputPlugin.cxx b/src/OutputPlugin.cxx deleted file mode 100644 index 31d9cce96..000000000 --- a/src/OutputPlugin.cxx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 != nullptr); - assert(plugin->init != nullptr); - - 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 != nullptr - ? ao->plugin->enable(ao, error_r) - : true; -} - -void -ao_plugin_disable(struct audio_output *ao) -{ - if (ao->plugin->disable != nullptr) - 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 != nullptr - ? ao->plugin->delay(ao) - : 0; -} - -void -ao_plugin_send_tag(struct audio_output *ao, const Tag *tag) -{ - if (ao->plugin->send_tag != nullptr) - 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 != nullptr) - ao->plugin->drain(ao); -} - -void -ao_plugin_cancel(struct audio_output *ao) -{ - if (ao->plugin->cancel != nullptr) - ao->plugin->cancel(ao); -} - -bool -ao_plugin_pause(struct audio_output *ao) -{ - return ao->plugin->pause != nullptr && ao->plugin->pause(ao); -} diff --git a/src/OutputPlugin.hxx b/src/OutputPlugin.hxx deleted file mode 100644 index b9f4b7dfb..000000000 --- a/src/OutputPlugin.hxx +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.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 nullptr if there is - * no configuration - * @return nullptr 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. - * - * @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 - */ - 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. - * - * @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 nullptr 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 != nullptr - ? 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 deleted file mode 100644 index 30f2c6732..000000000 --- a/src/OutputPrint.cxx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 deleted file mode 100644 index 5d446d702..000000000 --- a/src/OutputPrint.hxx +++ /dev/null @@ -1,34 +0,0 @@ - -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 deleted file mode 100644 index a3650413c..000000000 --- a/src/OutputState.cxx +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 deleted file mode 100644 index 5ab765ba8..000000000 --- a/src/OutputState.hxx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 deleted file mode 100644 index 30d3ba30f..000000000 --- a/src/OutputThread.cxx +++ /dev/null @@ -1,679 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "Compiler.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 != nullptr) - ao->replay_gain_filter->Open(format, error_r); - if (ao->other_replay_gain_filter != nullptr) - 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 != nullptr) - ao->replay_gain_filter->Close(); - if (ao->other_replay_gain_filter != nullptr) - ao->other_replay_gain_filter->Close(); - } - - return af; -} - -static void -ao_filter_close(struct audio_output *ao) -{ - if (ao->replay_gain_filter != nullptr) - ao->replay_gain_filter->Close(); - if (ao->other_replay_gain_filter != nullptr) - 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 != nullptr); - assert(ao->chunk == nullptr); - assert(ao->in_audio_format.IsValid()); - - if (ao->fail_timer != nullptr) { - /* 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 = nullptr; - } - - /* 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 = nullptr; - - ao->chunk = nullptr; - 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 = nullptr; - - ao->chunk = nullptr; - 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 != nullptr); - 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 != nullptr) { - 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 - : nullptr); - *replay_gain_serial_p = chunk->replay_gain_serial; - } - - Error error; - data = replay_gain_filter->FilterPCM(data, length, - &length, error); - if (data == nullptr) { - FormatError(error, "\"%s\" [%s] failed to filter", - ao->name, ao->plugin->name); - return nullptr; - } - } - - *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 == nullptr) - return nullptr; - - if (length == 0) { - /* empty chunk, nothing to do */ - *length_r = 0; - return data; - } - - /* cross-fade */ - - if (chunk->other != nullptr) { - 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 == nullptr) - return nullptr; - - 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 nullptr; - } - - data = dest; - length = other_length; - } - - /* apply filter chain */ - - Error error; - data = ao->filter->FilterPCM(data, length, &length, error); - if (data == nullptr) { - FormatError(error, "\"%s\" [%s] failed to filter", - ao->name, ao->plugin->name); - return nullptr; - } - - *length_r = length; - return data; -} - -static bool -ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) -{ - assert(ao != nullptr); - assert(ao->filter != nullptr); - - if (ao->tags && gcc_unlikely(chunk->tag != nullptr)) { - 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 == nullptr) { - 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 == nullptr); - 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 != nullptr - /* 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 != nullptr); - - chunk = ao_next_chunk(ao); - if (chunk == nullptr) - /* no chunk available */ - return false; - - ao->chunk_finished = false; - - assert(!ao->in_playback_loop); - ao->in_playback_loop = true; - - while (chunk != nullptr && ao->command == AO_COMMAND_NONE) { - assert(!ao->chunk_finished); - - ao->chunk = chunk; - - success = ao_play_chunk(ao, chunk); - if (!success) { - assert(ao->chunk == nullptr); - break; - } - - assert(ao->chunk == chunk); - chunk = chunk->next; - } - - assert(ao->in_playback_loop); - ao->in_playback_loop = false; - - 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 void -audio_output_task(void *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 != nullptr); - - 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 == nullptr); - 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 = nullptr; - - if (ao->open) { - ao->mutex.unlock(); - ao_plugin_cancel(ao); - ao->mutex.lock(); - } - - ao_command_finished(ao); - continue; - - case AO_COMMAND_KILL: - ao->chunk = nullptr; - ao_command_finished(ao); - ao->mutex.unlock(); - return; - } - - 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->woken_for_play = false; - ao->cond.wait(ao->mutex); - } - } -} - -void audio_output_thread_start(struct audio_output *ao) -{ - assert(ao->command == AO_COMMAND_NONE); - - Error error; - if (!ao->thread.Start(audio_output_task, ao, error)) - FatalError(error); -} diff --git a/src/OutputThread.hxx b/src/OutputThread.hxx deleted file mode 100644 index 1a7932162..000000000 --- a/src/OutputThread.hxx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 index 91033a1ec..e22134bbc 100644 --- a/src/Page.cxx +++ b/src/Page.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,19 +19,19 @@ #include "config.h" #include "Page.hxx" - -#include <glib.h> +#include "util/Alloc.hxx" #include <new> #include <assert.h> #include <string.h> +#include <stdlib.h> Page * Page::Create(size_t size) { - void *p = g_malloc(sizeof(Page) + size - - sizeof(Page::data)); + void *p = xalloc(sizeof(Page) + size - + sizeof(Page::data)); return ::new(p) Page(size); } @@ -63,7 +63,7 @@ Page::Unref() if (unused) { this->Page::~Page(); - g_free(this); + free(this); } return unused; diff --git a/src/Page.hxx b/src/Page.hxx index 27c6092cc..95f35d06a 100644 --- a/src/Page.hxx +++ b/src/Page.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,8 +27,6 @@ #include "util/RefCount.hxx" -#include <algorithm> - #include <stddef.h> /** diff --git a/src/Partition.cxx b/src/Partition.cxx index 55750cfad..ea801f827 100644 --- a/src/Partition.cxx +++ b/src/Partition.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,7 +19,7 @@ #include "config.h" #include "Partition.hxx" -#include "Song.hxx" +#include "DetachedSong.hxx" void Partition::DatabaseModified() @@ -30,10 +30,10 @@ Partition::DatabaseModified() void Partition::TagModified() { - Song *song = pc.LockReadTaggedSong(); + DetachedSong *song = pc.LockReadTaggedSong(); if (song != nullptr) { playlist.TagModified(std::move(*song)); - song->Free(); + delete song; } } diff --git a/src/Partition.hxx b/src/Partition.hxx index 512ba3bca..4a5bcc6c7 100644 --- a/src/Partition.hxx +++ b/src/Partition.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -76,8 +76,8 @@ struct Partition { return playlist.DeleteRange(pc, start, end); } - void DeleteSong(const Song &song) { - playlist.DeleteSong(pc, song); + void DeleteSong(const char *uri) { + playlist.DeleteSong(pc, uri); } void Shuffle(unsigned start, unsigned end) { diff --git a/src/Permission.cxx b/src/Permission.cxx index a35c80e94..d2d9d4297 100644 --- a/src/Permission.cxx +++ b/src/Permission.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,9 +19,9 @@ #include "config.h" #include "Permission.hxx" -#include "ConfigData.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" #include "system/FatalError.hxx" #include <algorithm> diff --git a/src/Permission.hxx b/src/Permission.hxx index 228e9ee20..60761c696 100644 --- a/src/Permission.hxx +++ b/src/Permission.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/PlayerControl.cxx b/src/PlayerControl.cxx index f180874b0..7ba703ea0 100644 --- a/src/PlayerControl.cxx +++ b/src/PlayerControl.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,10 +20,9 @@ #include "config.h" #include "PlayerControl.hxx" #include "Idle.hxx" -#include "Song.hxx" -#include "DecoderControl.hxx" +#include "DetachedSong.hxx" -#include <cmath> +#include <algorithm> #include <assert.h> @@ -43,15 +42,12 @@ PlayerControl::PlayerControl(unsigned _buffer_chunks, PlayerControl::~PlayerControl() { - if (next_song != nullptr) - next_song->Free(); - - if (tagged_song != nullptr) - tagged_song->Free(); + delete next_song; + delete tagged_song; } void -PlayerControl::Play(Song *song) +PlayerControl::Play(DetachedSong *song) { assert(song != nullptr); @@ -196,26 +192,23 @@ PlayerControl::ClearError() } void -PlayerControl::LockSetTaggedSong(const Song &song) +PlayerControl::LockSetTaggedSong(const DetachedSong &song) { Lock(); - if (tagged_song != nullptr) - tagged_song->Free(); - tagged_song = song.DupDetached(); + delete tagged_song; + tagged_song = new DetachedSong(song); Unlock(); } void PlayerControl::ClearTaggedSong() { - if (tagged_song != nullptr) { - tagged_song->Free(); - tagged_song = nullptr; - } + delete tagged_song; + tagged_song = nullptr; } void -PlayerControl::EnqueueSong(Song *song) +PlayerControl::EnqueueSong(DetachedSong *song) { assert(song != nullptr); @@ -225,15 +218,13 @@ PlayerControl::EnqueueSong(Song *song) } bool -PlayerControl::Seek(Song *song, float seek_time) +PlayerControl::Seek(DetachedSong *song, float seek_time) { assert(song != nullptr); Lock(); - if (next_song != nullptr) - next_song->Free(); - + delete next_song; next_song = song; seek_where = seek_time; SynchronousCommand(PlayerCommand::SEEK); diff --git a/src/PlayerControl.hxx b/src/PlayerControl.hxx index 61bb408d2..4847958c7 100644 --- a/src/PlayerControl.hxx +++ b/src/PlayerControl.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -29,7 +29,7 @@ #include <stdint.h> -struct Song; +class DetachedSong; enum class PlayerState : uint8_t { STOP, @@ -131,16 +131,16 @@ struct PlayerControl { Error error; /** - * A copy of the current #Song after its tags have been - * updated by the decoder (for example, a radio stream that - * has sent a new tag after switching to the next song). This - * shall be used by the GlobalEvents::TAG handler to update - * the current #Song in the queue. + * A copy of the current #DetachedSong after its tags have + * been updated by the decoder (for example, a radio stream + * that has sent a new tag after switching to the next song). + * This shall be used by the GlobalEvents::TAG handler to + * update the current #DetachedSong in the queue. * * Protected by #mutex. Set by the PlayerThread and consumed * by the main thread. */ - Song *tagged_song; + DetachedSong *tagged_song; uint16_t bit_rate; AudioFormat audio_format; @@ -153,7 +153,7 @@ struct PlayerControl { * This is a duplicate, and must be freed when this attribute * is cleared. */ - Song *next_song; + DetachedSong *next_song; double seek_where; @@ -299,7 +299,7 @@ public: * @param song the song to be queued; the given instance will * be owned and freed by the player */ - void Play(Song *song); + void Play(DetachedSong *song); /** * see PlayerCommand::CANCEL @@ -371,9 +371,9 @@ public: /** * Set the #tagged_song attribute to a newly allocated copy of - * the given #Song. Locks and unlocks the object. + * the given #DetachedSong. Locks and unlocks the object. */ - void LockSetTaggedSong(const Song &song); + void LockSetTaggedSong(const DetachedSong &song); void ClearTaggedSong(); @@ -382,8 +382,8 @@ public: * * Caller must lock the object. */ - Song *ReadTaggedSong() { - Song *result = tagged_song; + DetachedSong *ReadTaggedSong() { + DetachedSong *result = tagged_song; tagged_song = nullptr; return result; } @@ -391,9 +391,9 @@ public: /** * Like ReadTaggedSong(), but locks and unlocks the object. */ - Song *LockReadTaggedSong() { + DetachedSong *LockReadTaggedSong() { Lock(); - Song *result = ReadTaggedSong(); + DetachedSong *result = ReadTaggedSong(); Unlock(); return result; } @@ -403,7 +403,7 @@ public: void UpdateAudio(); private: - void EnqueueSongLocked(Song *song) { + void EnqueueSongLocked(DetachedSong *song) { assert(song != nullptr); assert(next_song == nullptr); @@ -416,7 +416,7 @@ public: * @param song the song to be queued; the given instance will be owned * and freed by the player */ - void EnqueueSong(Song *song); + void EnqueueSong(DetachedSong *song); /** * Makes the player thread seek the specified song to a position. @@ -426,7 +426,7 @@ public: * @return true on success, false on failure (e.g. if MPD isn't * playing currently) */ - bool Seek(Song *song, float seek_time); + bool Seek(DetachedSong *song, float seek_time); void SetCrossFade(float cross_fade_seconds); diff --git a/src/PlayerThread.cxx b/src/PlayerThread.cxx index 356559e37..4fb045bda 100644 --- a/src/PlayerThread.cxx +++ b/src/PlayerThread.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,21 +19,21 @@ #include "config.h" #include "PlayerThread.hxx" -#include "DecoderThread.hxx" -#include "DecoderControl.hxx" +#include "decoder/DecoderThread.hxx" +#include "decoder/DecoderControl.hxx" #include "MusicPipe.hxx" #include "MusicBuffer.hxx" #include "MusicChunk.hxx" -#include "Song.hxx" -#include "Main.hxx" +#include "DetachedSong.hxx" #include "system/FatalError.hxx" #include "CrossFade.hxx" #include "PlayerControl.hxx" -#include "OutputAll.hxx" +#include "output/OutputAll.hxx" #include "tag/Tag.hxx" #include "Idle.hxx" #include "GlobalEvents.hxx" #include "util/Domain.hxx" +#include "thread/Name.hxx" #include "Log.hxx" #include <string.h> @@ -93,7 +93,7 @@ class Player { /** * the song currently being played */ - Song *song; + DetachedSong *song; /** * is cross fading enabled? @@ -291,12 +291,12 @@ Player::StartDecoder(MusicPipe &_pipe) assert(queued || pc.command == PlayerCommand::SEEK); assert(pc.next_song != nullptr); - unsigned start_ms = pc.next_song->start_ms; + unsigned start_ms = pc.next_song->GetStartMS(); 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, + dc.Start(new DetachedSong(*pc.next_song), + start_ms, pc.next_song->GetEndMS(), buffer, _pipe); } @@ -330,7 +330,7 @@ Player::WaitForDecoder() if (error.IsDefined()) { pc.SetError(PlayerError::DECODER, std::move(error)); - pc.next_song->Free(); + delete pc.next_song; pc.next_song = nullptr; pc.Unlock(); @@ -340,9 +340,7 @@ Player::WaitForDecoder() pc.ClearTaggedSong(); - if (song != nullptr) - song->Free(); - + delete song; song = pc.next_song; elapsed_time = 0.0; @@ -371,19 +369,20 @@ Player::WaitForDecoder() * indicated by the decoder plugin. */ static double -real_song_duration(const Song *song, double decoder_duration) +real_song_duration(const DetachedSong &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(); + return song.GetDuration(); - if (song->end_ms > 0 && song->end_ms / 1000.0 < decoder_duration) - return (song->end_ms - song->start_ms) / 1000.0; + const unsigned start_ms = song.GetStartMS(); + const unsigned end_ms = song.GetEndMS(); - return decoder_duration - song->start_ms / 1000.0; + if (end_ms > 0 && end_ms / 1000.0 < decoder_duration) + return (end_ms - start_ms) / 1000.0; + + return decoder_duration - start_ms / 1000.0; } bool @@ -451,7 +450,7 @@ Player::CheckDecoderStartup() return true; pc.Lock(); - pc.total_time = real_song_duration(dc.song, dc.total_time); + pc.total_time = real_song_duration(*dc.song, dc.total_time); pc.audio_format = dc.in_audio_format; pc.Unlock(); @@ -461,10 +460,10 @@ Player::CheckDecoderStartup() decoder_starting = false; if (!paused && !OpenOutput()) { - const auto uri = dc.song->GetURI(); FormatError(player_domain, "problems opening audio device " - "while playing \"%s\"", uri.c_str()); + "while playing \"%s\"", + dc.song->GetURI()); return true; } @@ -519,7 +518,7 @@ Player::SeekDecoder() { assert(pc.next_song != nullptr); - const unsigned start_ms = pc.next_song->start_ms; + const unsigned start_ms = pc.next_song->GetStartMS(); if (!dc.LockIsCurrentSong(*pc.next_song)) { /* the decoder is already decoding the "next" song - @@ -545,7 +544,7 @@ Player::SeekDecoder() ClearAndReplacePipe(dc.pipe); } - pc.next_song->Free(); + delete pc.next_song; pc.next_song = nullptr; queued = false; } @@ -661,7 +660,7 @@ Player::ProcessCommand() pc.Lock(); } - pc.next_song->Free(); + delete pc.next_song; pc.next_song = nullptr; queued = false; pc.CommandFinished(); @@ -684,19 +683,16 @@ Player::ProcessCommand() } static void -update_song_tag(PlayerControl &pc, Song *song, const Tag &new_tag) +update_song_tag(PlayerControl &pc, DetachedSong &song, const Tag &new_tag) { - if (song->IsFile()) + 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; + song.SetTag(new_tag); - pc.LockSetTaggedSong(*song); + pc.LockSetTaggedSong(song); /* the main thread will update the playlist version when he receives this event */ @@ -716,7 +712,7 @@ update_song_tag(PlayerControl &pc, Song *song, const Tag &new_tag) */ static bool play_chunk(PlayerControl &pc, - Song *song, struct music_chunk *chunk, + DetachedSong &song, struct music_chunk *chunk, MusicBuffer &buffer, const AudioFormat format, Error &error) @@ -839,7 +835,7 @@ Player::PlayNextChunk() /* play the current chunk */ Error error; - if (!play_chunk(pc, song, chunk, buffer, play_audio_format, error)) { + if (!play_chunk(pc, *song, chunk, buffer, play_audio_format, error)) { LogError(error); buffer.Return(chunk); @@ -883,10 +879,7 @@ Player::SongBorder() { xfade_state = CrossFadeState::UNKNOWN; - { - const auto uri = song->GetURI(); - FormatDefault(player_domain, "played \"%s\"", uri.c_str()); - } + FormatDefault(player_domain, "played \"%s\"", song->GetURI()); ReplacePipe(dc.pipe); @@ -1082,9 +1075,8 @@ Player::Run() delete cross_fade_tag; if (song != nullptr) { - const auto uri = song->GetURI(); - FormatDefault(player_domain, "played \"%s\"", uri.c_str()); - song->Free(); + FormatDefault(player_domain, "played \"%s\"", song->GetURI()); + delete song; } pc.Lock(); @@ -1093,7 +1085,7 @@ Player::Run() if (queued) { assert(pc.next_song != nullptr); - pc.next_song->Free(); + delete pc.next_song; pc.next_song = nullptr; } @@ -1115,6 +1107,8 @@ player_task(void *arg) { PlayerControl &pc = *(PlayerControl *)arg; + SetThreadName("player"); + DecoderControl dc(pc.mutex, pc.cond); decoder_thread_start(dc); @@ -1142,10 +1136,8 @@ player_task(void *arg) /* fall through */ case PlayerCommand::PAUSE: - if (pc.next_song != nullptr) { - pc.next_song->Free(); - pc.next_song = nullptr; - } + delete pc.next_song; + pc.next_song = nullptr; pc.CommandFinished(); break; @@ -1180,10 +1172,8 @@ player_task(void *arg) return; case PlayerCommand::CANCEL: - if (pc.next_song != nullptr) { - pc.next_song->Free(); - pc.next_song = nullptr; - } + delete pc.next_song; + pc.next_song = nullptr; pc.CommandFinished(); break; diff --git a/src/PlayerThread.hxx b/src/PlayerThread.hxx index efdcf05ca..7e4150252 100644 --- a/src/PlayerThread.hxx +++ b/src/PlayerThread.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/Playlist.cxx b/src/Playlist.cxx index 8d9ab24a3..abcb2ceaa 100644 --- a/src/Playlist.cxx +++ b/src/Playlist.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,24 +21,23 @@ #include "Playlist.hxx" #include "PlaylistError.hxx" #include "PlayerControl.hxx" -#include "Song.hxx" -#include "tag/Tag.hxx" +#include "DetachedSong.hxx" #include "Idle.hxx" #include "Log.hxx" #include <assert.h> void -playlist::TagModified(Song &&song) +playlist::TagModified(DetachedSong &&song) { - if (!playing || song.tag == nullptr) + if (!playing) return; assert(current >= 0); - Song ¤t_song = queue.GetOrder(current); - if (SongEquals(song, current_song)) - current_song.ReplaceTag(std::move(*song.tag)); + DetachedSong ¤t_song = queue.GetOrder(current); + if (song.IsSame(current_song)) + current_song.MoveTagFrom(std::move(song)); queue.ModifyAtOrder(current); queue.IncrementVersion(); @@ -56,15 +55,12 @@ playlist_queue_song_order(playlist &playlist, PlayerControl &pc, playlist.queued = order; - Song *song = playlist.queue.GetOrder(order).DupDetached(); + const DetachedSong &song = playlist.queue.GetOrder(order); - { - const auto uri = song->GetURI(); - FormatDebug(playlist_domain, "queue song %i:\"%s\"", - playlist.queued, uri.c_str()); - } + FormatDebug(playlist_domain, "queue song %i:\"%s\"", + playlist.queued, song.GetURI()); - pc.EnqueueSong(song); + pc.EnqueueSong(new DetachedSong(song)); } /** @@ -89,7 +85,7 @@ playlist_song_started(playlist &playlist, PlayerControl &pc) idle_add(IDLE_PLAYER); } -const Song * +const DetachedSong * playlist::GetQueuedSong() const { return playing && queued >= 0 @@ -98,7 +94,7 @@ playlist::GetQueuedSong() const } void -playlist::UpdateQueuedSong(PlayerControl &pc, const Song *prev) +playlist::UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev) { if (!playing) return; @@ -125,7 +121,7 @@ playlist::UpdateQueuedSong(PlayerControl &pc, const Song *prev) current = queue.PositionToOrder(current_position); } - const Song *const next_song = next_order >= 0 + const DetachedSong *const next_song = next_order >= 0 ? &queue.GetOrder(next_order) : nullptr; @@ -149,15 +145,11 @@ playlist::PlayOrder(PlayerControl &pc, int order) playing = true; queued = -1; - Song *song = queue.GetOrder(order).DupDetached(); + const DetachedSong &song = queue.GetOrder(order); - { - const auto uri = song->GetURI(); - FormatDebug(playlist_domain, "play %i:\"%s\"", - order, uri.c_str()); - } + FormatDebug(playlist_domain, "play %i:\"%s\"", order, song.GetURI()); - pc.Play(song); + pc.Play(new DetachedSong(song)); current = order; } @@ -174,7 +166,7 @@ playlist::SyncWithPlayer(PlayerControl &pc) pc.Lock(); const PlayerState pc_state = pc.GetState(); - const Song *pc_next_song = pc.next_song; + const DetachedSong *pc_next_song = pc.next_song; pc.Unlock(); if (pc_state == PlayerState::STOP) @@ -287,7 +279,7 @@ playlist::SetRandom(PlayerControl &pc, bool status) if (status == queue.random) return; - const Song *const queued_song = GetQueuedSong(); + const DetachedSong *const queued_song = GetQueuedSong(); queue.random = status; diff --git a/src/Playlist.hxx b/src/Playlist.hxx index 7d7e9b154..db7889227 100644 --- a/src/Playlist.hxx +++ b/src/Playlist.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,11 +20,13 @@ #ifndef MPD_PLAYLIST_HXX #define MPD_PLAYLIST_HXX -#include "Queue.hxx" +#include "queue/Queue.hxx" #include "PlaylistError.hxx" +enum TagType : uint8_t; struct PlayerControl; -struct Song; +class DetachedSong; +class Error; struct playlist { /** @@ -98,7 +100,7 @@ struct playlist { * none if there is none (yet?) or if MPD isn't playing. */ gcc_pure - const Song *GetQueuedSong() const; + const DetachedSong *GetQueuedSong() const; /** * This is the "PLAYLIST" event handler. It is invoked by the @@ -123,7 +125,7 @@ protected: * @param prev the song which was previously queued, as * determined by playlist_get_queued_song() */ - void UpdateQueuedSong(PlayerControl &pc, const Song *prev); + void UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev); public: void Clear(PlayerControl &pc); @@ -133,7 +135,7 @@ public: * thread. Apply the given song's tag to the current song if * the song matches. */ - void TagModified(Song &&song); + void TagModified(DetachedSong &&song); /** * The database has been modified. Pull all updates. @@ -141,7 +143,7 @@ public: void DatabaseModified(); PlaylistResult AppendSong(PlayerControl &pc, - Song *song, + DetachedSong &&song, unsigned *added_id=nullptr); /** @@ -160,7 +162,7 @@ public: protected: void DeleteInternal(PlayerControl &pc, - unsigned song, const Song **queued_p); + unsigned song, const DetachedSong **queued_p); public: PlaylistResult DeletePosition(PlayerControl &pc, @@ -182,7 +184,7 @@ public: PlaylistResult DeleteRange(PlayerControl &pc, unsigned start, unsigned end); - void DeleteSong(PlayerControl &pc, const Song &song); + void DeleteSong(PlayerControl &pc, const char *uri); void Shuffle(PlayerControl &pc, unsigned start, unsigned end); @@ -205,6 +207,10 @@ public: PlaylistResult SetPriorityId(PlayerControl &pc, unsigned song_id, uint8_t priority); + bool AddSongIdTag(unsigned id, TagType tag_type, const char *value, + Error &error); + bool ClearSongIdTag(unsigned id, TagType tag_type, Error &error); + void Stop(PlayerControl &pc); PlaylistResult PlayPosition(PlayerControl &pc, int position); diff --git a/src/PlaylistAny.cxx b/src/PlaylistAny.cxx deleted file mode 100644 index 52304700f..000000000 --- a/src/PlaylistAny.cxx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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, - InputStream **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; - InputStream *is = InputStream::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, - InputStream **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 deleted file mode 100644 index 743db2e39..000000000 --- a/src/PlaylistAny.hxx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 - -class Mutex; -class Cond; -class SongEnumerator; -struct InputStream; - -/** - * 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, - InputStream **is_r); - -#endif diff --git a/src/PlaylistControl.cxx b/src/PlaylistControl.cxx index 2dbd75d6e..9d75cc26d 100644 --- a/src/PlaylistControl.cxx +++ b/src/PlaylistControl.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,7 +26,7 @@ #include "Playlist.hxx" #include "PlaylistError.hxx" #include "PlayerControl.hxx" -#include "Song.hxx" +#include "DetachedSong.hxx" #include "Log.hxx" void @@ -195,7 +195,7 @@ playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time) if (!queue.IsValidPosition(song)) return PlaylistResult::BAD_RANGE; - const Song *queued_song = GetQueuedSong(); + const DetachedSong *queued_song = GetQueuedSong(); unsigned i = queue.random ? queue.PositionToOrder(song) @@ -215,8 +215,7 @@ playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time) queued_song = nullptr; } - Song *the_song = queue.GetOrder(i).DupDetached(); - if (!pc.Seek(the_song, seek_time)) { + if (!pc.Seek(new DetachedSong(queue.GetOrder(i)), seek_time)) { UpdateQueuedSong(pc, queued_song); return PlaylistResult::NOT_PLAYING; diff --git a/src/PlaylistDatabase.cxx b/src/PlaylistDatabase.cxx index a6d15e755..065aea320 100644 --- a/src/PlaylistDatabase.cxx +++ b/src/PlaylistDatabase.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,7 @@ #include "config.h" #include "PlaylistDatabase.hxx" #include "PlaylistVector.hxx" -#include "TextFile.hxx" +#include "fs/TextFile.hxx" #include "util/StringUtil.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" diff --git a/src/PlaylistDatabase.hxx b/src/PlaylistDatabase.hxx index 1481f621f..48de64efa 100644 --- a/src/PlaylistDatabase.hxx +++ b/src/PlaylistDatabase.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/PlaylistEdit.cxx b/src/PlaylistEdit.cxx index 668612a1a..87a64c5c9 100644 --- a/src/PlaylistEdit.cxx +++ b/src/PlaylistEdit.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -29,10 +29,10 @@ #include "PlayerControl.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" -#include "Song.hxx" +#include "DetachedSong.hxx" +#include "Mapper.hxx" #include "Idle.hxx" -#include "DatabaseGlue.hxx" -#include "DatabasePlugin.hxx" +#include "db/DatabaseSong.hxx" #include "Log.hxx" #include <stdlib.h> @@ -60,27 +60,25 @@ PlaylistResult playlist::AppendFile(PlayerControl &pc, const char *path_utf8, unsigned *added_id) { - Song *song = Song::LoadFile(path_utf8, nullptr); - if (song == nullptr) + DetachedSong song(path_utf8); + if (!song.Update()) return PlaylistResult::NO_SUCH_SONG; - const auto result = AppendSong(pc, song, added_id); - song->Free(); - return result; + return AppendSong(pc, std::move(song), added_id); } PlaylistResult playlist::AppendSong(PlayerControl &pc, - Song *song, unsigned *added_id) + DetachedSong &&song, unsigned *added_id) { unsigned id; if (queue.IsFull()) return PlaylistResult::TOO_LARGE; - const Song *const queued_song = GetQueuedSong(); + const DetachedSong *const queued_song = GetQueuedSong(); - id = queue.Append(song, 0); + id = queue.Append(std::move(song), 0); if (queue.random) { /* shuffle the new song into the list of remaining @@ -110,25 +108,17 @@ playlist::AppendURI(PlayerControl &pc, { FormatDebug(playlist_domain, "add to playlist: %s", uri); - const Database *db = nullptr; - Song *song; + DetachedSong *song; if (uri_has_scheme(uri)) { - song = Song::NewRemote(uri); + song = new DetachedSong(uri); } else { - db = GetDatabase(); - if (db == nullptr) - return PlaylistResult::NO_SUCH_SONG; - - song = db->GetSong(uri, IgnoreError()); + song = DatabaseDetachSong(uri, IgnoreError()); if (song == nullptr) return PlaylistResult::NO_SUCH_SONG; } - PlaylistResult result = AppendSong(pc, song, added_id); - if (db != nullptr) - db->ReturnSong(song); - else - song->Free(); + PlaylistResult result = AppendSong(pc, std::move(*song), added_id); + delete song; return result; } @@ -139,7 +129,7 @@ playlist::SwapPositions(PlayerControl &pc, unsigned song1, unsigned song2) if (!queue.IsValidPosition(song1) || !queue.IsValidPosition(song2)) return PlaylistResult::BAD_RANGE; - const Song *const queued_song = GetQueuedSong(); + const DetachedSong *const queued_song = GetQueuedSong(); queue.SwapPositions(song1, song2); @@ -193,7 +183,7 @@ playlist::SetPriorityRange(PlayerControl &pc, /* remember "current" and "queued" */ const int current_position = GetCurrentPosition(); - const Song *const queued_song = GetQueuedSong(); + const DetachedSong *const queued_song = GetQueuedSong(); /* apply the priority changes */ @@ -225,7 +215,7 @@ playlist::SetPriorityId(PlayerControl &pc, void playlist::DeleteInternal(PlayerControl &pc, - unsigned song, const Song **queued_p) + unsigned song, const DetachedSong **queued_p) { assert(song < GetLength()); @@ -275,7 +265,7 @@ playlist::DeletePosition(PlayerControl &pc, unsigned song) if (song >= queue.GetLength()) return PlaylistResult::BAD_RANGE; - const Song *queued_song = GetQueuedSong(); + const DetachedSong *queued_song = GetQueuedSong(); DeleteInternal(pc, song, &queued_song); @@ -297,7 +287,7 @@ playlist::DeleteRange(PlayerControl &pc, unsigned start, unsigned end) if (start >= end) return PlaylistResult::SUCCESS; - const Song *queued_song = GetQueuedSong(); + const DetachedSong *queued_song = GetQueuedSong(); do { DeleteInternal(pc, --end, &queued_song); @@ -320,10 +310,10 @@ playlist::DeleteId(PlayerControl &pc, unsigned id) } void -playlist::DeleteSong(PlayerControl &pc, const struct Song &song) +playlist::DeleteSong(PlayerControl &pc, const char *uri) { for (int i = queue.GetLength() - 1; i >= 0; --i) - if (SongEquals(song, queue.Get(i))) + if (queue.Get(i).IsURI(uri)) DeletePosition(pc, i); } @@ -341,7 +331,7 @@ playlist::MoveRange(PlayerControl &pc, unsigned start, unsigned end, int to) /* nothing happens */ return PlaylistResult::SUCCESS; - const Song *const queued_song = GetQueuedSong(); + const DetachedSong *const queued_song = GetQueuedSong(); /* * (to < 0) => move to offset from current song @@ -401,7 +391,7 @@ playlist::Shuffle(PlayerControl &pc, unsigned start, unsigned end) /* needs at least two entries. */ return; - const Song *const queued_song = GetQueuedSong(); + const DetachedSong *const queued_song = GetQueuedSong(); if (playing && current >= 0) { unsigned current_position = queue.OrderToPosition(current); diff --git a/src/PlaylistError.cxx b/src/PlaylistError.cxx index 91291f551..085246f15 100644 --- a/src/PlaylistError.cxx +++ b/src/PlaylistError.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/PlaylistError.hxx b/src/PlaylistError.hxx index 8ea65ca34..0f2424f41 100644 --- a/src/PlaylistError.hxx +++ b/src/PlaylistError.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/PlaylistFile.cxx b/src/PlaylistFile.cxx index e7dae6258..0b0f8d32d 100644 --- a/src/PlaylistFile.cxx +++ b/src/PlaylistFile.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,14 +22,13 @@ #include "PlaylistSave.hxx" #include "PlaylistInfo.hxx" #include "PlaylistVector.hxx" -#include "DatabasePlugin.hxx" -#include "DatabaseGlue.hxx" -#include "Song.hxx" +#include "db/DatabaseSong.hxx" +#include "DetachedSong.hxx" #include "Mapper.hxx" -#include "TextFile.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" -#include "ConfigDefaults.hxx" +#include "fs/TextFile.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" +#include "config/ConfigDefaults.hxx" #include "Idle.hxx" #include "fs/Limits.hxx" #include "fs/AllocatedPath.hxx" @@ -43,10 +42,7 @@ #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> @@ -159,10 +155,9 @@ LoadPlaylistFileInfo(PlaylistInfo &info, 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 = PathToUTF8(name); - g_free(name); + std::string name(name_fs_str, + name_length + 1 - sizeof(PLAYLIST_FILE_SUFFIX)); + std::string name_utf8 = PathToUTF8(name.c_str()); if (name_utf8.empty()) return false; @@ -250,7 +245,7 @@ LoadPlaylistFile(const char *utf8path, Error &error) if (!uri_has_scheme(s)) { uri_utf8 = map_fs_to_utf8(s); if (uri_utf8.empty()) { - if (PathTraits::IsAbsoluteFS(s)) { + if (PathTraitsFS::IsAbsolute(s)) { uri_utf8 = PathToUTF8(s); if (uri_utf8.empty()) continue; @@ -365,7 +360,7 @@ spl_remove_index(const char *utf8path, unsigned pos, Error &error) } bool -spl_append_song(const char *utf8path, const Song &song, Error &error) +spl_append_song(const char *utf8path, const DetachedSong &song, Error &error) { if (spl_map(error).IsNull()) return false; @@ -406,21 +401,15 @@ 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; + return spl_append_song(utf8file, DetachedSong(url), + error); } else { - const Database *db = GetDatabase(error); - if (db == nullptr) - return false; - - Song *song = db->GetSong(url, error); + DetachedSong *song = DatabaseDetachSong(url, error); if (song == nullptr) return false; bool success = spl_append_song(utf8file, *song, error); - db->ReturnSong(song); + delete song; return success; } } diff --git a/src/PlaylistFile.hxx b/src/PlaylistFile.hxx index f04530bcc..4d741bf8e 100644 --- a/src/PlaylistFile.hxx +++ b/src/PlaylistFile.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,8 +23,7 @@ #include <vector> #include <string> -struct Song; -struct PlaylistInfo; +class DetachedSong; class PlaylistVector; class Error; @@ -69,7 +68,7 @@ bool spl_remove_index(const char *utf8path, unsigned pos, Error &error); bool -spl_append_song(const char *utf8path, const Song &song, Error &error); +spl_append_song(const char *utf8path, const DetachedSong &song, Error &error); bool spl_append_uri(const char *file, const char *utf8file, Error &error); diff --git a/src/PlaylistGlobal.cxx b/src/PlaylistGlobal.cxx index 97902275b..dacfad0c7 100644 --- a/src/PlaylistGlobal.cxx +++ b/src/PlaylistGlobal.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,7 +24,6 @@ #include "config.h" #include "PlaylistGlobal.hxx" -#include "Playlist.hxx" #include "Main.hxx" #include "Instance.hxx" #include "GlobalEvents.hxx" diff --git a/src/PlaylistGlobal.hxx b/src/PlaylistGlobal.hxx index 4397292db..a2e3bb030 100644 --- a/src/PlaylistGlobal.hxx +++ b/src/PlaylistGlobal.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/PlaylistInfo.hxx b/src/PlaylistInfo.hxx index 2c5b9ae1a..baa6cc361 100644 --- a/src/PlaylistInfo.hxx +++ b/src/PlaylistInfo.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/PlaylistMapper.cxx b/src/PlaylistMapper.cxx deleted file mode 100644 index 68ac22438..000000000 --- a/src/PlaylistMapper.cxx +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/AllocatedPath.hxx" -#include "util/UriUtil.hxx" - -#include <assert.h> - -static SongEnumerator * -playlist_open_path(const char *path_fs, Mutex &mutex, Cond &cond, - InputStream **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, - InputStream **is_r) -{ - assert(spl_valid_name(uri)); - - const auto &playlist_directory_fs = map_spl_path(); - if (playlist_directory_fs.IsNull()) - return nullptr; - - const auto uri_fs = AllocatedPath::FromUTF8(uri); - if (uri_fs.IsNull()) - return nullptr; - - const auto path_fs = - AllocatedPath::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, - InputStream **is_r) -{ - assert(uri_safe_local(uri)); - - const auto 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, - InputStream **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 deleted file mode 100644 index 7132415f1..000000000 --- a/src/PlaylistMapper.hxx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 - -class Mutex; -class Cond; -class SongEnumerator; -struct InputStream; - -/** - * 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, - InputStream **is_r); - -#endif diff --git a/src/PlaylistPlugin.hxx b/src/PlaylistPlugin.hxx deleted file mode 100644 index 6eb0f4771..000000000 --- a/src/PlaylistPlugin.hxx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 - -struct config_param; -struct InputStream; -struct Tag; -class Mutex; -class Cond; -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)(InputStream &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, - InputStream &is) -{ - return plugin->open_stream(is); -} - -#endif diff --git a/src/PlaylistPrint.cxx b/src/PlaylistPrint.cxx index e3d500be3..faf373be7 100644 --- a/src/PlaylistPrint.cxx +++ b/src/PlaylistPrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,23 +20,21 @@ #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 "queue/QueuePrint.hxx" #include "SongPrint.hxx" -#include "DatabaseGlue.hxx" -#include "DatabasePlugin.hxx" -#include "Client.hxx" -#include "InputStream.hxx" -#include "Song.hxx" +#include "db/DatabaseGlue.hxx" +#include "db/DatabasePlugin.hxx" +#include "client/Client.hxx" +#include "input/InputStream.hxx" +#include "DetachedSong.hxx" #include "fs/Traits.hxx" #include "util/Error.hxx" #include "thread/Cond.hxx" +#define SONG_FILE "file: " +#define SONG_TIME "Time: " + void playlist_print_uris(Client &client, const playlist &playlist) { @@ -119,7 +117,7 @@ PrintSongDetails(Client &client, const char *uri_utf8) if (db == nullptr) return false; - Song *song = db->GetSong(uri_utf8, IgnoreError()); + auto *song = db->GetSong(uri_utf8, IgnoreError()); if (song == nullptr) return false; @@ -144,47 +142,3 @@ spl_print(Client &client, const char *name_utf8, bool detail, return true; } - -static void -playlist_provider_print(Client &client, const char *uri, - SongEnumerator &e, bool detail) -{ - const std::string base_uri = uri != nullptr - ? PathTraits::GetParentUTF8(uri) - : std::string("."); - - Song *song; - while ((song = e.NextSong()) != nullptr) { - song = playlist_check_translate_song(song, base_uri.c_str(), - false); - if (song == nullptr) - continue; - - if (detail) - song_print_info(client, *song); - else - song_print_uri(client, *song); - - song->Free(); - } -} - -bool -playlist_file_print(Client &client, const char *uri, bool detail) -{ - Mutex mutex; - Cond cond; - - InputStream *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 index 9cbc46325..38a4cc7cf 100644 --- a/src/PlaylistPrint.hxx +++ b/src/PlaylistPrint.hxx @@ -1,6 +1,5 @@ - /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -96,15 +95,4 @@ 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 deleted file mode 100644 index a8359d427..000000000 --- a/src/PlaylistQueue.cxx +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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" -#include "thread/Cond.hxx" -#include "fs/Traits.hxx" - -PlaylistResult -playlist_load_into_queue(const char *uri, SongEnumerator &e, - unsigned start_index, unsigned end_index, - playlist &dest, PlayerControl &pc, - bool secure) -{ - const std::string base_uri = uri != nullptr - ? PathTraits::GetParentUTF8(uri) - : std::string("."); - - Song *song; - 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.c_str(), - secure); - if (song == nullptr) - continue; - - PlaylistResult result = dest.AppendSong(pc, song); - song->Free(); - if (result != PlaylistResult::SUCCESS) - return result; - } - - return PlaylistResult::SUCCESS; -} - -PlaylistResult -playlist_open_into_queue(const char *uri, - unsigned start_index, unsigned end_index, - playlist &dest, PlayerControl &pc, - bool secure) -{ - Mutex mutex; - Cond cond; - - InputStream *is; - auto playlist = playlist_open_any(uri, mutex, cond, &is); - if (playlist == nullptr) - return PlaylistResult::NO_SUCH_LIST; - - PlaylistResult 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 deleted file mode 100644 index fc566ec78..000000000 --- a/src/PlaylistQueue.hxx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 PlayerControl; - -/** - * 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) - */ -PlaylistResult -playlist_load_into_queue(const char *uri, SongEnumerator &e, - unsigned start_index, unsigned end_index, - playlist &dest, PlayerControl &pc, - bool secure); - -/** - * Opens a playlist with a playlist plugin and append to the specified - * play queue. - */ -PlaylistResult -playlist_open_into_queue(const char *uri, - unsigned start_index, unsigned end_index, - playlist &dest, PlayerControl &pc, - bool secure); - -#endif - diff --git a/src/PlaylistRegistry.cxx b/src/PlaylistRegistry.cxx deleted file mode 100644 index 9afbe349d..000000000 --- a/src/PlaylistRegistry.cxx +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "util/Macros.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigData.hxx" -#include "system/FatalError.hxx" -#include "Log.hxx" - -#include <glib.h> - -#include <assert.h> -#include <string.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 -}; - -static constexpr unsigned n_playlist_plugins = - ARRAY_SIZE(playlist_plugins) - 1; - -/** which plugins have been initialized successfully? */ -static bool playlist_plugins_enabled[n_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[n_playlist_plugins]; - - 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(InputStream &is, const char *mime) -{ - 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.Rewind(IgnoreError()); - - auto playlist = playlist_plugin_open_stream(plugin, - is); - if (playlist != nullptr) - return playlist; - } - } - - return nullptr; -} - -static SongEnumerator * -playlist_list_open_stream_mime(InputStream &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*/ - const std::string mime(full_mime, semicolon); - return playlist_list_open_stream_mime2(is, mime.c_str()); -} - -static SongEnumerator * -playlist_list_open_stream_suffix(InputStream &is, const char *suffix) -{ - 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.Rewind(IgnoreError()); - - auto playlist = playlist_plugin_open_stream(plugin, is); - if (playlist != nullptr) - return playlist; - } - } - - return nullptr; -} - -SongEnumerator * -playlist_list_open_stream(InputStream &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, - InputStream **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; - InputStream *is = InputStream::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 deleted file mode 100644 index 2b747316d..000000000 --- a/src/PlaylistRegistry.hxx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 - -class Mutex; -class Cond; -class SongEnumerator; -struct InputStream; - -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(InputStream &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, - InputStream **is_r); - -#endif diff --git a/src/PlaylistSave.cxx b/src/PlaylistSave.cxx index 29bf193fd..8eeab0f62 100644 --- a/src/PlaylistSave.cxx +++ b/src/PlaylistSave.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,30 +22,30 @@ #include "PlaylistFile.hxx" #include "PlaylistError.hxx" #include "Playlist.hxx" -#include "Song.hxx" +#include "DetachedSong.hxx" #include "Mapper.hxx" #include "Idle.hxx" #include "fs/AllocatedPath.hxx" #include "fs/Traits.hxx" #include "fs/FileSystem.hxx" +#include "util/Alloc.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) +playlist_print_song(FILE *file, const DetachedSong &song) { - if (playlist_saveAbsolutePaths && song.IsInDatabase()) { + if (playlist_saveAbsolutePaths && + song.IsInDatabase() && song.IsFile()) { const auto path = map_song_fs(song); if (!path.IsNull()) fprintf(file, "%s\n", path.c_str()); } else { const auto uri_utf8 = song.GetURI(); - const auto uri_fs = AllocatedPath::FromUTF8(uri_utf8.c_str()); + const auto uri_fs = AllocatedPath::FromUTF8(uri_utf8); if (!uri_fs.IsNull()) fprintf(file, "%s\n", uri_fs.c_str()); @@ -56,7 +56,7 @@ void playlist_print_uri(FILE *file, const char *uri) { auto path = playlist_saveAbsolutePaths && !uri_has_scheme(uri) && - !PathTraits::IsAbsoluteUTF8(uri) + !PathTraitsUTF8::IsAbsolute(uri) ? map_uri_fs(uri) : AllocatedPath::FromUTF8(uri); @@ -127,7 +127,7 @@ playlist_load_spl(struct playlist &playlist, PlayerControl &pc, if ((playlist.AppendURI(pc, uri_utf8.c_str())) != PlaylistResult::SUCCESS) { /* for windows compatibility, convert slashes */ - char *temp2 = g_strdup(uri_utf8.c_str()); + char *temp2 = xstrdup(uri_utf8.c_str()); char *p = temp2; while (*p) { if (*p == '\\') @@ -139,7 +139,7 @@ playlist_load_spl(struct playlist &playlist, PlayerControl &pc, FormatError(playlist_domain, "can't add file \"%s\"", temp2); - g_free(temp2); + free(temp2); } } diff --git a/src/PlaylistSave.hxx b/src/PlaylistSave.hxx index 71e0a8189..3e58b8630 100644 --- a/src/PlaylistSave.hxx +++ b/src/PlaylistSave.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,14 +24,14 @@ #include <stdio.h> -struct Song; struct Queue; struct playlist; struct PlayerControl; +class DetachedSong; class Error; void -playlist_print_song(FILE *fp, const Song &song); +playlist_print_song(FILE *file, const DetachedSong &song); void playlist_print_uri(FILE *fp, const char *uri); diff --git a/src/PlaylistSong.cxx b/src/PlaylistSong.cxx deleted file mode 100644 index 60774dc36..000000000 --- a/src/PlaylistSong.cxx +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/AllocatedPath.hxx" -#include "fs/Traits.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 auto 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 (PathTraits::IsAbsoluteUTF8(uri) && secure) { - dest = Song::LoadFile(uri, nullptr); - if (dest == nullptr) - return nullptr; - } else { - const Database *db = GetDatabase(); - 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) - /* PathTraits::GetParentUTF8() 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 (PathTraits::IsAbsoluteUTF8(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 deleted file mode 100644 index d0db99868..000000000 --- a/src/PlaylistSong.hxx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 index ac2deebbf..4a4e37345 100644 --- a/src/PlaylistState.cxx +++ b/src/PlaylistState.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,17 +26,16 @@ #include "PlaylistState.hxx" #include "PlaylistError.hxx" #include "Playlist.hxx" -#include "QueueSave.hxx" -#include "TextFile.hxx" +#include "queue/QueueSave.hxx" +#include "fs/TextFile.hxx" #include "PlayerControl.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" #include "fs/Limits.hxx" #include "util/CharUtil.hxx" +#include "util/StringUtil.hxx" #include "Log.hxx" -#include <glib.h> - #include <string.h> #include <stdlib.h> @@ -112,7 +111,7 @@ playlist_state_load(TextFile &file, struct playlist &playlist) return; } - while (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) { + while (!StringStartsWith(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) { queue_load_song(file, line, playlist.queue); line = file.ReadLine(); @@ -135,7 +134,7 @@ playlist_state_restore(const char *line, TextFile &file, int seek_time = 0; bool random_mode = false; - if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE)) + if (!StringStartsWith(line, PLAYLIST_STATE_FILE_STATE)) return false; line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1; @@ -149,40 +148,40 @@ playlist_state_restore(const char *line, TextFile &file, state = PlayerState::STOP; while ((line = file.ReadLine()) != nullptr) { - if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_TIME)) { + if (StringStartsWith(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)) { + } else if (StringStartsWith(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)) { + } else if (StringStartsWith(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)) { + } else if (StringStartsWith(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)) { + } else if (StringStartsWith(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)) { + } else if (StringStartsWith(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)) { + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) { const char *p = line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY); /* this check discards "nan" which was used prior to MPD 0.18 */ if (IsDigitASCII(*p)) pc.SetMixRampDelay(atof(p)); - } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_RANDOM)) { + } else if (StringStartsWith(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)) { + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CURRENT)) { current = atoi(&(line [strlen (PLAYLIST_STATE_FILE_CURRENT)])); - } else if (g_str_has_prefix(line, + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) { playlist_state_load(file, playlist); } diff --git a/src/PlaylistState.hxx b/src/PlaylistState.hxx index 01cc94d03..bd48e4543 100644 --- a/src/PlaylistState.hxx +++ b/src/PlaylistState.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -32,12 +32,12 @@ struct PlayerControl; class TextFile; void -playlist_state_save(FILE *fp, const struct playlist &playlist, +playlist_state_save(FILE *fp, const playlist &playlist, PlayerControl &pc); bool playlist_state_restore(const char *line, TextFile &file, - struct playlist &playlist, PlayerControl &pc); + playlist &playlist, PlayerControl &pc); /** * Generates a hash number for the current state of the playlist and @@ -46,7 +46,7 @@ playlist_state_restore(const char *line, TextFile &file, * be saved. */ unsigned -playlist_state_get_hash(const struct playlist &playlist, +playlist_state_get_hash(const playlist &playlist, PlayerControl &c); #endif diff --git a/src/PlaylistTag.cxx b/src/PlaylistTag.cxx new file mode 100644 index 000000000..556e7f4e9 --- /dev/null +++ b/src/PlaylistTag.cxx @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "DetachedSong.hxx" +#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" +#include "util/Error.hxx" + +bool +playlist::AddSongIdTag(unsigned id, TagType tag_type, const char *value, + Error &error) +{ + const int position = queue.IdToPosition(id); + if (position < 0) { + error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG), + "No such song"); + return false; + } + + DetachedSong &song = queue.Get(position); + if (song.IsFile()) { + error.Set(playlist_domain, int(PlaylistResult::DENIED), + "Cannot edit tags of local file"); + return false; + } + + { + TagBuilder tag(std::move(song.WritableTag())); + tag.AddItem(tag_type, value); + song.SetTag(tag.Commit()); + } + + queue.ModifyAtPosition(position); + OnModified(); + return true; +} + +bool +playlist::ClearSongIdTag(unsigned id, TagType tag_type, + Error &error) +{ + const int position = queue.IdToPosition(id); + if (position < 0) { + error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG), + "No such song"); + return false; + } + + DetachedSong &song = queue.Get(position); + if (song.IsFile()) { + error.Set(playlist_domain, int(PlaylistResult::DENIED), + "Cannot edit tags of local file"); + return false; + } + + { + TagBuilder tag(std::move(song.WritableTag())); + if (tag_type == TAG_NUM_OF_ITEM_TYPES) + tag.RemoveAll(); + else + tag.RemoveType(tag_type); + song.SetTag(tag.Commit()); + } + + queue.ModifyAtPosition(position); + OnModified(); + return true; +} diff --git a/src/PlaylistUpdate.cxx b/src/PlaylistUpdate.cxx index 0e72ef671..114305960 100644 --- a/src/PlaylistUpdate.cxx +++ b/src/PlaylistUpdate.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,38 +19,37 @@ #include "config.h" #include "Playlist.hxx" -#include "DatabaseGlue.hxx" -#include "DatabasePlugin.hxx" -#include "Song.hxx" +#include "db/DatabaseGlue.hxx" +#include "db/DatabasePlugin.hxx" +#include "db/LightSong.hxx" +#include "DetachedSong.hxx" #include "tag/Tag.hxx" #include "Idle.hxx" #include "util/Error.hxx" static bool -UpdatePlaylistSong(const Database &db, Song &song) +UpdatePlaylistSong(const Database &db, DetachedSong &song) { - if (!song.IsInDatabase() || !song.IsDetached()) + if (!song.IsInDatabase() || !song.IsFile()) /* only update Songs instances that are "detached" from the Database */ return false; - Song *original = db.GetSong(song.uri, IgnoreError()); + const LightSong *original = db.GetSong(song.GetURI(), IgnoreError()); if (original == nullptr) /* not found - shouldn't happen, because the update thread should ensure that all stale Song instances have been purged */ return false; - if (original->mtime == song.mtime) { + if (original->mtime == song.GetLastModified()) { /* not modified */ db.ReturnSong(original); return false; } - song.mtime = original->mtime; - - if (original->tag != nullptr) - song.ReplaceTag(Tag(*original->tag)); + song.SetLastModified(original->mtime); + song.SetTag(*original->tag); db.ReturnSong(original); return true; diff --git a/src/PlaylistVector.cxx b/src/PlaylistVector.cxx index 5bb0cdf64..82a3519d9 100644 --- a/src/PlaylistVector.cxx +++ b/src/PlaylistVector.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,12 +19,11 @@ #include "config.h" #include "PlaylistVector.hxx" -#include "DatabaseLock.hxx" +#include "db/DatabaseLock.hxx" #include <algorithm> #include <assert.h> -#include <string.h> PlaylistVector::iterator PlaylistVector::find(const char *name) diff --git a/src/PlaylistVector.hxx b/src/PlaylistVector.hxx index 8ef8e44c7..8820ead5c 100644 --- a/src/PlaylistVector.hxx +++ b/src/PlaylistVector.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/Queue.cxx b/src/Queue.cxx deleted file mode 100644 index 92beefd0e..000000000 --- a/src/Queue.cxx +++ /dev/null @@ -1,490 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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]; - ModifyAtPosition(position); -} - -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(Queue *queue, unsigned start, unsigned end) -{ - assert(queue != nullptr); - 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 deleted file mode 100644 index 3c6001b4f..000000000 --- a/src/Queue.hxx +++ /dev/null @@ -1,378 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.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; - - explicit Queue(unsigned max_length); - - /** - * Deinitializes a queue object. It does not free the queue - * pointer itself. - */ - ~Queue(); - - Queue(const Queue &) = delete; - Queue &operator=(const Queue &) = 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". Call - * IncrementVersion() after all modifications have been made. - * number. - */ - void ModifyAtPosition(unsigned position) { - assert(position < length); - - items[position].version = version; - } - - /** - * Marks the specified song as "modified". Call - * IncrementVersion() after all modifications have been made. - * number. - */ - void ModifyAtOrder(unsigned order); - - /** - * 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/QueuePrint.cxx b/src/QueuePrint.cxx deleted file mode 100644 index 89f3c0ad3..000000000 --- a/src/QueuePrint.cxx +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 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 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 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 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 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 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 deleted file mode 100644 index bc91bb5de..000000000 --- a/src/QueuePrint.hxx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 Queue &queue, - unsigned start, unsigned end); - -void -queue_print_uris(Client &client, const Queue &queue, - unsigned start, unsigned end); - -void -queue_print_changes_info(Client &client, const Queue &queue, - uint32_t version); - -void -queue_print_changes_position(Client &client, const Queue &queue, - uint32_t version); - -void -queue_find(Client &client, const Queue &queue, - const SongFilter &filter); - -#endif diff --git a/src/QueueSave.cxx b/src/QueueSave.cxx deleted file mode 100644 index 2ab5f6280..000000000 --- a/src/QueueSave.cxx +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "fs/Traits.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) -{ - const auto uri = song.GetURI(); - fprintf(fp, "%i:%s\n", idx, uri.c_str()); -} - -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 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, nullptr, 10); - - line = file.ReadLine(); - if (line == nullptr) - 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) && !PathTraits::IsAbsoluteUTF8(uri)) - return; - - Error error; - song = song_load(file, nullptr, uri, error); - if (song == nullptr) { - 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(); - if (db == nullptr) - return; - - song = db->GetSong(uri, IgnoreError()); - if (song == nullptr) - return; - } - } - - queue.Append(song, priority); - - if (db != nullptr) - db->ReturnSong(song); - else - song->Free(); -} diff --git a/src/QueueSave.hxx b/src/QueueSave.hxx deleted file mode 100644 index 6c618c0dc..000000000 --- a/src/QueueSave.hxx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 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 index 8f9b0d3f7..c3bbcac3c 100644 --- a/src/ReplayGainConfig.cxx +++ b/src/ReplayGainConfig.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,14 +20,14 @@ #include "config.h" #include "ReplayGainConfig.hxx" #include "Idle.hxx" -#include "ConfigData.hxx" -#include "ConfigGlobal.hxx" -#include "Playlist.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigGlobal.hxx" #include "system/FatalError.hxx" #include <assert.h> #include <stdlib.h> #include <string.h> +#include <math.h> ReplayGainMode replay_gain_mode = REPLAY_GAIN_OFF; diff --git a/src/ReplayGainConfig.hxx b/src/ReplayGainConfig.hxx index 7777a859b..e498a56dd 100644 --- a/src/ReplayGainConfig.hxx +++ b/src/ReplayGainConfig.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/ReplayGainInfo.cxx b/src/ReplayGainInfo.cxx index cf6b0ba69..580773521 100644 --- a/src/ReplayGainInfo.cxx +++ b/src/ReplayGainInfo.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/ReplayGainInfo.hxx b/src/ReplayGainInfo.hxx index b8dfd16a1..37815c933 100644 --- a/src/ReplayGainInfo.hxx +++ b/src/ReplayGainInfo.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/SignalHandlers.cxx b/src/SignalHandlers.cxx deleted file mode 100644 index 542239083..000000000 --- a/src/SignalHandlers.cxx +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "event/Loop.hxx" -#include "system/FatalError.hxx" -#include "util/Domain.hxx" - -#include <signal.h> - -static constexpr Domain signal_handlers_domain("signal_handlers"); - -static void -HandleShutdownSignal() -{ - SignalMonitorGetEventLoop().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); - - 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 deleted file mode 100644 index 90dab6dec..000000000 --- a/src/SignalHandlers.hxx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 6213d5e66..000000000 --- a/src/Song.cxx +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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()) { - const auto new_uri = GetURI(); - song = NewDetached(new_uri.c_str()); - } 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); -} - -void -Song::ReplaceTag(Tag &&_tag) -{ - if (tag == nullptr) - tag = new Tag(); - *tag = std::move(_tag); -} - -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 -SongEquals(const Song &a, const Song &b) -{ - 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" */ - const auto au = a.GetURI(); - const auto bu = b.GetURI(); - return au == bu; - } - - return directory_is_same(a.parent, b.parent) && - strcmp(a.uri, b.uri) == 0; -} - -std::string -Song::GetURI() const -{ - assert(*uri); - - if (!IsInDatabase() || parent->IsRoot()) - return std::string(uri); - else { - const char *path = parent->GetPath(); - - std::string result; - result.reserve(strlen(path) + 1 + strlen(uri)); - result.assign(path); - result.push_back('/'); - result.append(uri); - return result; - } -} - -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 deleted file mode 100644 index b74690e77..000000000 --- a/src/Song.hxx +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.h" - -#include <string> - -#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); - - static Song *LoadFile(const char *path_utf8, Directory &parent) { - return LoadFile(path_utf8, &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; - } - - void ReplaceTag(Tag &&tag); - - bool UpdateFile(); - bool UpdateFileInArchive(); - - /** - * Returns the URI of the song in UTF-8 encoding, including its - * location within the music directory. - */ - gcc_pure - std::string GetURI() const; - - gcc_pure - double GetDuration() const; -}; - -/** - * Returns true if both objects refer to the same physical song. - */ -gcc_pure -bool -SongEquals(const Song &a, const Song &b); - -#endif diff --git a/src/SongEnumerator.hxx b/src/SongEnumerator.hxx deleted file mode 100644 index 0e268a31a..000000000 --- a/src/SongEnumerator.hxx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 index 49c966b6f..637150c37 100644 --- a/src/SongFilter.cxx +++ b/src/SongFilter.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,7 +19,9 @@ #include "config.h" #include "SongFilter.hxx" -#include "Song.hxx" +#include "db/Song.hxx" +#include "db/LightSong.hxx" +#include "DetachedSong.hxx" #include "tag/Tag.hxx" #include "util/ASCII.hxx" #include "util/UriUtil.hxx" @@ -136,7 +138,19 @@ SongFilter::Item::Match(const Tag &_tag) const } bool -SongFilter::Item::Match(const Song &song) const +SongFilter::Item::Match(const DetachedSong &song) const +{ + if (tag == LOCATE_TAG_BASE_TYPE) + return uri_is_child_or_same(value.c_str(), song.GetURI()); + + if (tag == LOCATE_TAG_FILE_TYPE) + return StringMatch(song.GetURI()); + + return Match(song.GetTag()); +} + +bool +SongFilter::Item::Match(const LightSong &song) const { if (tag == LOCATE_TAG_BASE_TYPE) { const auto uri = song.GetURI(); @@ -148,7 +162,7 @@ SongFilter::Item::Match(const Song &song) const return StringMatch(uri.c_str()); } - return song.tag != NULL && Match(*song.tag); + return Match(*song.tag); } SongFilter::SongFilter(unsigned tag, const char *value, bool fold_case) @@ -194,7 +208,17 @@ SongFilter::Parse(unsigned argc, char *argv[], bool fold_case) } bool -SongFilter::Match(const Song &song) const +SongFilter::Match(const DetachedSong &song) const +{ + for (const auto &i : items) + if (!i.Match(song)) + return false; + + return true; +} + +bool +SongFilter::Match(const LightSong &song) const { for (const auto &i : items) if (!i.Match(song)) diff --git a/src/SongFilter.hxx b/src/SongFilter.hxx index b15127c07..74d7187c9 100644 --- a/src/SongFilter.hxx +++ b/src/SongFilter.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -38,6 +38,8 @@ struct Tag; struct TagItem; struct Song; +struct LightSong; +class DetachedSong; class SongFilter { public: @@ -79,7 +81,10 @@ public: bool Match(const Tag &tag) const; gcc_pure - bool Match(const Song &song) const; + bool Match(const DetachedSong &song) const; + + gcc_pure + bool Match(const LightSong &song) const; }; private: @@ -103,7 +108,10 @@ public: bool Match(const Tag &tag) const; gcc_pure - bool Match(const Song &song) const; + bool Match(const DetachedSong &song) const; + + gcc_pure + bool Match(const LightSong &song) const; const std::list<Item> &GetItems() const { return items; diff --git a/src/SongPointer.hxx b/src/SongPointer.hxx deleted file mode 100644 index ded3b3e1d..000000000 --- a/src/SongPointer.hxx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 index ea164d02b..b0c9ed0a6 100644 --- a/src/SongPrint.cxx +++ b/src/SongPrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,33 +19,45 @@ #include "config.h" #include "SongPrint.hxx" -#include "Song.hxx" -#include "Directory.hxx" +#include "db/LightSong.hxx" +#include "DetachedSong.hxx" #include "TimePrint.hxx" #include "TagPrint.hxx" #include "Mapper.hxx" -#include "Client.hxx" +#include "client/Client.hxx" #include "util/UriUtil.hxx" +#define SONG_FILE "file: " + +static void +song_print_uri(Client &client, const char *uri) +{ + const std::string allocated = uri_remove_auth(uri); + if (!allocated.empty()) + uri = allocated.c_str(); + + client_printf(client, "%s%s\n", SONG_FILE, + map_to_relative_path(uri)); +} + void -song_print_uri(Client &client, const Song &song) +song_print_uri(Client &client, const LightSong &song) { - if (song.IsInDatabase() && !song.parent->IsRoot()) { + if (song.directory != nullptr) { client_printf(client, "%s%s/%s\n", SONG_FILE, - song.parent->GetPath(), song.uri); - } else { - const char *uri = song.uri; - const std::string allocated = uri_remove_auth(uri); - if (!allocated.empty()) - uri = allocated.c_str(); + song.directory, song.uri); + } else + song_print_uri(client, song.uri); +} - client_printf(client, "%s%s\n", SONG_FILE, - map_to_relative_path(uri)); - } +void +song_print_uri(Client &client, const DetachedSong &song) +{ + song_print_uri(client, song.GetURI()); } void -song_print_info(Client &client, const Song &song) +song_print_info(Client &client, const LightSong &song) { song_print_uri(client, song); @@ -63,6 +75,30 @@ song_print_info(Client &client, const Song &song) if (song.mtime > 0) time_print(client, "Last-Modified", song.mtime); - if (song.tag != nullptr) - tag_print(client, *song.tag); + tag_print(client, *song.tag); +} + +void +song_print_info(Client &client, const DetachedSong &song) +{ + song_print_uri(client, song); + + const unsigned start_ms = song.GetStartMS(); + const unsigned end_ms = song.GetEndMS(); + + if (end_ms > 0) + client_printf(client, "Range: %u.%03u-%u.%03u\n", + start_ms / 1000, + start_ms % 1000, + end_ms / 1000, + end_ms % 1000); + else if (start_ms > 0) + client_printf(client, "Range: %u.%03u-\n", + start_ms / 1000, + start_ms % 1000); + + if (song.GetLastModified() > 0) + time_print(client, "Last-Modified", song.GetLastModified()); + + tag_print(client, song.GetTag()); } diff --git a/src/SongPrint.hxx b/src/SongPrint.hxx index f8df89d38..16a9ee6ff 100644 --- a/src/SongPrint.hxx +++ b/src/SongPrint.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,13 +20,20 @@ #ifndef MPD_SONG_PRINT_HXX #define MPD_SONG_PRINT_HXX -struct Song; +struct LightSong; +class DetachedSong; class Client; void -song_print_info(Client &client, const Song &song); +song_print_info(Client &client, const DetachedSong &song); void -song_print_uri(Client &client, const Song &song); +song_print_info(Client &client, const LightSong &song); + +void +song_print_uri(Client &client, const LightSong &song); + +void +song_print_uri(Client &client, const DetachedSong &song); #endif diff --git a/src/SongSave.cxx b/src/SongSave.cxx index 63e279a16..d53e5bb62 100644 --- a/src/SongSave.cxx +++ b/src/SongSave.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,10 +19,10 @@ #include "config.h" #include "SongSave.hxx" -#include "Song.hxx" +#include "db/Song.hxx" +#include "DetachedSong.hxx" #include "TagSave.hxx" -#include "Directory.hxx" -#include "TextFile.hxx" +#include "fs/TextFile.hxx" #include "tag/Tag.hxx" #include "tag/TagBuilder.hxx" #include "util/StringUtil.hxx" @@ -37,41 +37,55 @@ static constexpr Domain song_save_domain("song_save"); +static void +range_save(FILE *file, unsigned start_ms, unsigned end_ms) +{ + if (end_ms > 0) + fprintf(file, "Range: %u-%u\n", start_ms, end_ms); + else if (start_ms > 0) + fprintf(file, "Range: %u-\n", start_ms); +} + 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); + range_save(fp, song.start_ms, song.end_ms); - if (song.tag != nullptr) - tag_save(fp, *song.tag); + 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, +void +song_save(FILE *fp, const DetachedSong &song) +{ + fprintf(fp, SONG_BEGIN "%s\n", song.GetURI()); + + range_save(fp, song.GetStartMS(), song.GetEndMS()); + + tag_save(fp, song.GetTag()); + + fprintf(fp, SONG_MTIME ": %li\n", (long)song.GetLastModified()); + fprintf(fp, SONG_END "\n"); +} + +DetachedSong * +song_load(TextFile &file, const char *uri, Error &error) { - Song *song = parent != nullptr - ? Song::NewFile(uri, parent) - : Song::NewRemote(uri); - char *line, *colon; - TagType type; - const char *value; + DetachedSong *song = new DetachedSong(uri); TagBuilder tag; + char *line; while ((line = file.ReadLine()) != nullptr && strcmp(line, SONG_END) != 0) { - colon = strchr(line, ':'); + char *colon = strchr(line, ':'); if (colon == nullptr || colon == line) { - song->Free(); + delete song; error.Format(song_save_domain, "unknown line in db: %s", line); @@ -79,8 +93,9 @@ song_load(TextFile &file, Directory *parent, const char *uri, } *colon++ = 0; - value = strchug_fast(colon); + const char *value = strchug_fast(colon); + TagType type; if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) { tag.AddItem(type, value); } else if (strcmp(line, "Time") == 0) { @@ -88,15 +103,19 @@ song_load(TextFile &file, Directory *parent, const char *uri, } else if (strcmp(line, "Playlist") == 0) { tag.SetHasPlaylist(strcmp(value, "yes") == 0); } else if (strcmp(line, SONG_MTIME) == 0) { - song->mtime = atoi(value); + song->SetLastModified(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, nullptr, 10); + unsigned start_ms = strtoul(value, &endptr, 10); + unsigned end_ms = *endptr == '-' + ? strtoul(endptr + 1, nullptr, 10) + : 0; + + song->SetStartMS(start_ms); + song->SetEndMS(end_ms); } else { - song->Free(); + delete song; error.Format(song_save_domain, "unknown line in db: %s", line); @@ -104,8 +123,6 @@ song_load(TextFile &file, Directory *parent, const char *uri, } } - if (tag.IsDefined()) - song->tag = tag.Commit(); - + song->SetTag(tag.Commit()); return song; } diff --git a/src/SongSave.hxx b/src/SongSave.hxx index 40fb4abf7..2a0edb49d 100644 --- a/src/SongSave.hxx +++ b/src/SongSave.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,12 +26,16 @@ struct Song; struct Directory; +class DetachedSong; class TextFile; class Error; void song_save(FILE *fp, const Song &song); +void +song_save(FILE *fp, const DetachedSong &song); + /** * Loads a song from the input file. Reading stops after the * "song_end" line. @@ -39,8 +43,8 @@ song_save(FILE *fp, const Song &song); * @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, +DetachedSong * +song_load(TextFile &file, const char *uri, Error &error); #endif diff --git a/src/SongSort.cxx b/src/SongSort.cxx deleted file mode 100644 index 4d422657a..000000000 --- a/src/SongSort.cxx +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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, TagType type) -{ - return tag != nullptr - ? tag->GetValue(type) - : nullptr; -} - -static int -compare_utf8_string(const char *a, const char *b) -{ - if (a == nullptr) - return b == nullptr ? 0 : -1; - - if (b == nullptr) - return 1; - - return g_utf8_collate(a, b); -} - -/** - * Compare two string tag values, ignoring case. Either one may be - * nullptr. - */ -static int -compare_string_tag_item(const Tag *a, const Tag *b, - TagType 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 nullptr. - */ -static int -compare_number_string(const char *a, const char *b) -{ - long ai = a == nullptr ? 0 : strtol(a, nullptr, 10); - long bi = b == nullptr ? 0 : strtol(b, nullptr, 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, TagType 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(nullptr, songs, song_cmp); -} diff --git a/src/SongSort.hxx b/src/SongSort.hxx deleted file mode 100644 index b3b67b0c0..000000000 --- a/src/SongSort.hxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index a0c4d3585..000000000 --- a/src/SongSticker.cxx +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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> - -std::string -sticker_song_get_value(const Song *song, const char *name) -{ - assert(song != nullptr); - assert(song->IsInDatabase()); - - const auto uri = song->GetURI(); - return sticker_load_value("song", uri.c_str(), name); -} - -bool -sticker_song_set_value(const Song *song, - const char *name, const char *value) -{ - assert(song != nullptr); - assert(song->IsInDatabase()); - - const auto uri = song->GetURI(); - return sticker_store_value("song", uri.c_str(), name, value); -} - -bool -sticker_song_delete(const Song *song) -{ - assert(song != nullptr); - assert(song->IsInDatabase()); - - const auto uri = song->GetURI(); - return sticker_delete("song", uri.c_str()); -} - -bool -sticker_song_delete_value(const Song *song, const char *name) -{ - assert(song != nullptr); - assert(song->IsInDatabase()); - - const auto uri = song->GetURI(); - return sticker_delete_value("song", uri.c_str(), name); -} - -struct sticker * -sticker_song_get(const Song *song) -{ - assert(song != nullptr); - assert(song->IsInDatabase()); - - const auto uri = song->GetURI(); - return sticker_load("song", uri.c_str()); -} - -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 != nullptr) - 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, "/", nullptr); - else - /* searching in root directory - no trailing slash */ - allocated = nullptr; - - 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 deleted file mode 100644 index 0923f0c3a..000000000 --- a/src/SongSticker.hxx +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 - -#include "Compiler.h" - -#include <string> - -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(). - */ -gcc_pure -std::string -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 index 1a873fedc..0f3e9b172 100644 --- a/src/SongUpdate.cxx +++ b/src/SongUpdate.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,45 +18,40 @@ */ #include "config.h" /* must be first for large file support */ -#include "Song.hxx" +#include "DetachedSong.hxx" +#include "db/Song.hxx" +#include "db/Directory.hxx" #include "util/UriUtil.hxx" -#include "util/Error.hxx" -#include "Directory.hxx" #include "Mapper.hxx" #include "fs/AllocatedPath.hxx" #include "fs/Traits.hxx" #include "fs/FileSystem.hxx" -#include "InputStream.hxx" -#include "DecoderPlugin.hxx" -#include "DecoderList.hxx" +#include "decoder/DecoderList.hxx" #include "tag/Tag.hxx" #include "tag/TagBuilder.hxx" #include "tag/TagHandler.hxx" #include "tag/TagId3.hxx" #include "tag/ApeTag.hxx" #include "TagFile.hxx" -#include "thread/Cond.hxx" +#include "TagStream.hxx" #include <assert.h> #include <string.h> -#include <sys/types.h> #include <sys/stat.h> -#include <stdio.h> Song * -Song::LoadFile(const char *path_utf8, Directory *parent) +Song::LoadFile(const char *path_utf8, Directory &parent) { Song *song; bool ret; - assert((parent == nullptr) == PathTraits::IsAbsoluteUTF8(path_utf8)); assert(!uri_has_scheme(path_utf8)); assert(strchr(path_utf8, '\n') == nullptr); song = NewFile(path_utf8, parent); //in archive ? - if (parent != nullptr && parent->device == DEVICE_INARCHIVE) { + if (parent.device == DEVICE_INARCHIVE) { ret = song->UpdateFileInArchive(); } else { ret = song->UpdateFile(); @@ -83,8 +78,6 @@ tag_scan_fallback(Path path, bool Song::UpdateFile() { - assert(IsFile()); - const auto path_fs = map_song_fs(*this); if (path_fs.IsNull()) return false; @@ -95,7 +88,7 @@ Song::UpdateFile() TagBuilder tag_builder; if (!tag_file_scan(path_fs, - &full_tag_handler, &tag_builder)) + full_tag_handler, &tag_builder)) return false; if (tag_builder.IsEmpty()) @@ -104,35 +97,66 @@ Song::UpdateFile() mtime = st.st_mtime; - delete tag; - tag = tag_builder.Commit(); + tag_builder.Commit(tag); return true; } bool Song::UpdateFileInArchive() { - const char *suffix; - const struct DecoderPlugin *plugin; - - assert(IsFile()); - /* check if there's a suffix and a plugin */ - suffix = uri_get_suffix(uri); + const char *suffix = uri_get_suffix(uri); if (suffix == nullptr) return false; - plugin = decoder_plugin_from_suffix(suffix, nullptr); - if (plugin == nullptr) + if (!decoder_plugins_supports_suffix(suffix)) return false; - delete tag; + const auto path_fs = map_song_fs(*this); + if (path_fs.IsNull()) + return false; - //accept every file that has music suffix - //because we don't support tag reading through - //input streams - tag = new Tag(); + TagBuilder tag_builder; + if (!tag_stream_scan(path_fs.c_str(), full_tag_handler, &tag_builder)) + return false; + tag_builder.Commit(tag); return true; } + +bool +DetachedSong::Update() +{ + if (IsAbsoluteFile()) { + const AllocatedPath path_fs = + AllocatedPath::FromUTF8(GetRealURI()); + + struct stat st; + if (!StatFile(path_fs, st) || !S_ISREG(st.st_mode)) + return false; + + TagBuilder tag_builder; + if (!tag_file_scan(path_fs, full_tag_handler, &tag_builder)) + return false; + + if (tag_builder.IsEmpty()) + tag_scan_fallback(path_fs, &full_tag_handler, + &tag_builder); + + mtime = st.st_mtime; + tag_builder.Commit(tag); + return true; + } else if (IsRemote()) { + TagBuilder tag_builder; + if (!tag_stream_scan(uri.c_str(), full_tag_handler, + &tag_builder)) + return false; + + mtime = 0; + tag_builder.Commit(tag); + return true; + } else + // TODO: implement + return false; +} diff --git a/src/StateFile.cxx b/src/StateFile.cxx index 75cef2c99..b28e8c617 100644 --- a/src/StateFile.cxx +++ b/src/StateFile.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,12 +19,12 @@ #include "config.h" #include "StateFile.hxx" -#include "OutputState.hxx" +#include "output/OutputState.hxx" #include "PlaylistState.hxx" -#include "TextFile.hxx" +#include "fs/TextFile.hxx" #include "Partition.hxx" -#include "Volume.hxx" -#include "event/Loop.hxx" +#include "mixer/Volume.hxx" + #include "fs/FileSystem.hxx" #include "util/Domain.hxx" #include "Log.hxx" diff --git a/src/StateFile.hxx b/src/StateFile.hxx index 4ec2c4be7..e35797b95 100644 --- a/src/StateFile.hxx +++ b/src/StateFile.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/Stats.cxx b/src/Stats.cxx index f224bdf49..940a984da 100644 --- a/src/Stats.cxx +++ b/src/Stats.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,44 +20,71 @@ #include "config.h" #include "Stats.hxx" #include "PlayerControl.hxx" -#include "Client.hxx" -#include "DatabaseSelection.hxx" -#include "DatabaseGlue.hxx" -#include "DatabasePlugin.hxx" -#include "DatabaseSimple.hxx" +#include "client/Client.hxx" +#include "db/Selection.hxx" +#include "db/DatabaseGlue.hxx" +#include "db/DatabasePlugin.hxx" +#include "db/DatabaseSimple.hxx" #include "util/Error.hxx" +#include "system/Clock.hxx" #include "Log.hxx" -#include <glib.h> +#ifndef WIN32 +/** + * The monotonic time stamp when MPD was started. It is used to + * calculate the uptime. + */ +static unsigned start_time; +#endif -static GTimer *uptime; static DatabaseStats stats; +enum class StatsValidity : uint8_t { + INVALID, VALID, FAILED, +}; + +static StatsValidity stats_validity = StatsValidity::INVALID; + void stats_global_init(void) { - uptime = g_timer_new(); +#ifndef WIN32 + start_time = MonotonicClockS(); +#endif } -void stats_global_finish(void) +void +stats_invalidate() { - g_timer_destroy(uptime); + assert(GetDatabase() != nullptr); + + stats_validity = StatsValidity::INVALID; } -void stats_update(void) +static bool +stats_update() { - assert(GetDatabase() != nullptr); + switch (stats_validity) { + case StatsValidity::INVALID: + break; - Error error; + case StatsValidity::VALID: + return true; + + case StatsValidity::FAILED: + return false; + } - DatabaseStats stats2; + Error error; const DatabaseSelection selection("", true); - if (GetDatabase()->GetStats(selection, stats2, error)) { - stats = stats2; + if (GetDatabase()->GetStats(selection, stats, error)) { + stats_validity = StatsValidity::VALID; + return true; } else { LogError(error); - stats.Clear(); + stats_validity = StatsValidity::FAILED; + return true; } } @@ -71,7 +98,10 @@ db_stats_print(Client &client) database plugin */ /* TODO: move this into the "proxy" database plugin as an "idle" handler */ - stats_update(); + stats_invalidate(); + + if (!stats_update()) + return; client_printf(client, "artists: %u\n" @@ -94,9 +124,13 @@ void stats_print(Client &client) { client_printf(client, - "uptime: %lu\n" + "uptime: %u\n" "playtime: %lu\n", - (unsigned long)g_timer_elapsed(uptime, NULL), +#ifdef WIN32 + GetProcessUptimeS(), +#else + MonotonicClockS() - start_time, +#endif (unsigned long)(client.player_control.GetTotalPlayTime() + 0.5)); if (GetDatabase() != nullptr) diff --git a/src/Stats.hxx b/src/Stats.hxx index dd131ce19..0d36ec0b2 100644 --- a/src/Stats.hxx +++ b/src/Stats.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,9 +24,8 @@ class Client; void stats_global_init(void); -void stats_global_finish(void); - -void stats_update(void); +void +stats_invalidate(); void stats_print(Client &client); diff --git a/src/StickerDatabase.cxx b/src/StickerDatabase.cxx deleted file mode 100644 index 869b91474..000000000 --- a/src/StickerDatabase.cxx +++ /dev/null @@ -1,604 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "util/Macros.hxx" -#include "Log.hxx" - -#include <string> -#include <map> - -#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[ARRAY_SIZE(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, nullptr); - if (ret != SQLITE_OK) { - error.Format(sticker_domain, ret, - "sqlite3_prepare_v2() failed: %s", - sqlite3_errmsg(sticker_db)); - return nullptr; - } - - return stmt; -} - -bool -sticker_global_init(Path path, Error &error) -{ - assert(!path.IsNull()); - - int ret; - - /* 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, - nullptr, nullptr, nullptr); - 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 < ARRAY_SIZE(sticker_sql); ++i) { - assert(sticker_sql[i] != nullptr); - - sticker_stmt[i] = sticker_prepare(sticker_sql[i], error); - if (sticker_stmt[i] == nullptr) - return false; - } - - return true; -} - -void -sticker_global_finish(void) -{ - if (sticker_db == nullptr) - /* not configured */ - return; - - for (unsigned i = 0; i < ARRAY_SIZE(sticker_stmt); ++i) { - assert(sticker_stmt[i] != nullptr); - - sqlite3_finalize(sticker_stmt[i]); - } - - sqlite3_close(sticker_db); -} - -bool -sticker_enabled(void) -{ - return sticker_db != nullptr; -} - -std::string -sticker_load_value(const char *type, const char *uri, const char *name) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_GET]; - int ret; - - assert(sticker_enabled()); - assert(type != nullptr); - assert(uri != nullptr); - assert(name != nullptr); - - if (*name == 0) - return std::string(); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return std::string(); - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return std::string(); - } - - ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return std::string(); - } - - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); - - std::string value; - if (ret == SQLITE_ROW) { - /* record found */ - value = (const char*)sqlite3_column_text(stmt, 0); - } else if (ret == SQLITE_DONE) { - /* no record found */ - } else { - /* error */ - LogError(sticker_db, "sqlite3_step() failed"); - } - - 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 != nullptr); - assert(uri != nullptr); - assert(sticker_enabled()); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); - 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 != nullptr); - assert(uri != nullptr); - assert(name != nullptr); - assert(*name != 0); - assert(value != nullptr); - - assert(sticker_enabled()); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, value, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 3, uri, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 4, name, -1, nullptr); - 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 != nullptr); - assert(uri != nullptr); - assert(name != nullptr); - assert(*name != 0); - assert(value != nullptr); - - assert(sticker_enabled()); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 4, value, -1, nullptr); - 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 != nullptr); - assert(uri != nullptr); - assert(name != nullptr); - assert(value != nullptr); - - 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 != nullptr); - assert(uri != nullptr); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); - 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 != nullptr); - assert(uri != nullptr); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr); - 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 sticker &sticker, - void (*func)(const char *name, const char *value, - void *user_data), - void *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 nullptr; - - if (s.table.empty()) - /* don't return empty sticker objects */ - return nullptr; - - 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, - void *user_data), - void *user_data) -{ - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_FIND]; - int ret; - - assert(type != nullptr); - assert(name != nullptr); - assert(func != nullptr); - assert(sticker_enabled()); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - if (base_uri == nullptr) - base_uri = ""; - - ret = sqlite3_bind_text(stmt, 2, base_uri, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr); - 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 deleted file mode 100644 index 42522b7b4..000000000 --- a/src/StickerDatabase.hxx +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 - -#include "Compiler.h" - -#include <string> - -class Error; -class Path; -struct sticker; - -/** - * Opens the sticker database. - * - * @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. - */ -gcc_const -bool -sticker_enabled(void); - -/** - * Returns one value from an object's sticker record. Returns an - * empty string if the value doesn't exist. - */ -std::string -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 nullptr if none was found - */ -gcc_pure -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 nullptr 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 nullptr 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 deleted file mode 100644 index 364d41356..000000000 --- a/src/StickerPrint.cxx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 sticker &sticker) -{ - sticker_foreach(sticker, print_sticker_cb, &client); -} diff --git a/src/StickerPrint.hxx b/src/StickerPrint.hxx deleted file mode 100644 index be6708486..000000000 --- a/src/StickerPrint.hxx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 sticker &sticker); - -#endif diff --git a/src/TagFile.cxx b/src/TagFile.cxx index 785a74987..a602c0b9b 100644 --- a/src/TagFile.cxx +++ b/src/TagFile.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,65 +22,77 @@ #include "fs/Path.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" -#include "DecoderList.hxx" -#include "DecoderPlugin.hxx" -#include "InputStream.hxx" +#include "decoder/DecoderList.hxx" +#include "decoder/DecoderPlugin.hxx" +#include "input/InputStream.hxx" #include "thread/Cond.hxx" #include <assert.h> -bool -tag_file_scan(Path path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - assert(!path_fs.IsNull()); - assert(handler != nullptr); +class TagFileScan { + const Path path_fs; + const char *const suffix; - /* check if there's a suffix and a plugin */ + const tag_handler &handler; + void *handler_ctx; - const char *suffix = uri_get_suffix(path_fs.c_str()); - if (suffix == nullptr) - return false; - - const struct DecoderPlugin *plugin = - decoder_plugin_from_suffix(suffix, nullptr); - if (plugin == nullptr) - return false; - - InputStream *is = nullptr; Mutex mutex; Cond cond; + InputStream *is; + +public: + TagFileScan(Path _path_fs, const char *_suffix, + const tag_handler &_handler, void *_handler_ctx) + :path_fs(_path_fs), suffix(_suffix), + handler(_handler), handler_ctx(_handler_ctx) , + is(nullptr) {} - do { - /* load file tag */ - if (plugin->ScanFile(path_fs.c_str(), - *handler, handler_ctx)) - break; + ~TagFileScan() { + if (is != nullptr) + is->Close(); + } - /* fall back to stream tag */ - if (plugin->scan_stream != nullptr) { - /* open the InputStream (if not already - open) */ + bool ScanFile(const DecoderPlugin &plugin) { + return plugin.ScanFile(path_fs.c_str(), handler, handler_ctx); + } + + bool ScanStream(const DecoderPlugin &plugin) { + if (plugin.scan_stream == nullptr) + return false; + + /* open the InputStream (if not already open) */ + if (is == nullptr) { + is = InputStream::OpenReady(path_fs.c_str(), + mutex, cond, + IgnoreError()); if (is == nullptr) - is = InputStream::Open(path_fs.c_str(), - mutex, cond, - IgnoreError()); + return false; + } else + is->LockRewind(IgnoreError()); + + /* now try the stream_tag() method */ + return plugin.ScanStream(*is, handler, handler_ctx); + } - /* now try the stream_tag() method */ - if (is != nullptr) { - if (plugin->ScanStream(*is, - *handler, handler_ctx)) - break; + bool Scan(const DecoderPlugin &plugin) { + return plugin.SupportsSuffix(suffix) && + (ScanFile(plugin) || ScanStream(plugin)); + } +}; - is->LockRewind(IgnoreError()); - } - } +bool +tag_file_scan(Path path_fs, const tag_handler &handler, void *handler_ctx) +{ + assert(!path_fs.IsNull()); - plugin = decoder_plugin_from_suffix(suffix, plugin); - } while (plugin != nullptr); + /* check if there's a suffix and a plugin */ - if (is != nullptr) - is->Close(); + const char *suffix = uri_get_suffix(path_fs.c_str()); + if (suffix == nullptr) + return false; - return plugin != nullptr; + TagFileScan tfs(path_fs, suffix, handler, handler_ctx); + return decoder_plugins_try([&](const DecoderPlugin &plugin){ + return tfs.Scan(plugin); + }); } diff --git a/src/TagFile.hxx b/src/TagFile.hxx index 078abebd9..b11a8ac1c 100644 --- a/src/TagFile.hxx +++ b/src/TagFile.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -33,7 +33,6 @@ struct tag_handler; * found) */ bool -tag_file_scan(Path path, - const struct tag_handler *handler, void *handler_ctx); +tag_file_scan(Path path, const tag_handler &handler, void *handler_ctx); #endif diff --git a/src/TagPrint.cxx b/src/TagPrint.cxx index 1191bd37c..228b5fd90 100644 --- a/src/TagPrint.cxx +++ b/src/TagPrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,8 +21,9 @@ #include "TagPrint.hxx" #include "tag/Tag.hxx" #include "tag/TagSettings.h" -#include "Song.hxx" -#include "Client.hxx" +#include "client/Client.hxx" + +#define SONG_TIME "Time: " void tag_print_types(Client &client) { @@ -35,6 +36,12 @@ void tag_print_types(Client &client) } } +void +tag_print(Client &client, TagType type, const char *value) +{ + client_printf(client, "%s: %s\n", tag_item_names[type], value); +} + void tag_print(Client &client, const Tag &tag) { if (tag.time >= 0) diff --git a/src/TagPrint.hxx b/src/TagPrint.hxx index ccc0c9aa4..20e7f3288 100644 --- a/src/TagPrint.hxx +++ b/src/TagPrint.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,12 +20,19 @@ #ifndef MPD_TAG_PRINT_HXX #define MPD_TAG_PRINT_HXX +#include <stdint.h> + +enum TagType : uint8_t; + struct Tag; class Client; void tag_print_types(Client &client); void +tag_print(Client &client, TagType type, const char *value); + +void tag_print(Client &client, const Tag &tag); #endif diff --git a/src/TagSave.cxx b/src/TagSave.cxx index b20d986c2..3a291e115 100644 --- a/src/TagSave.cxx +++ b/src/TagSave.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,8 @@ #include "config.h" #include "TagSave.hxx" #include "tag/Tag.hxx" -#include "Song.hxx" + +#define SONG_TIME "Time: " void tag_save(FILE *file, const Tag &tag) diff --git a/src/TagSave.hxx b/src/TagSave.hxx index 0b1359c89..d209c0a16 100644 --- a/src/TagSave.hxx +++ b/src/TagSave.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/TagStream.cxx b/src/TagStream.cxx new file mode 100644 index 000000000..b84166825 --- /dev/null +++ b/src/TagStream.cxx @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "TagStream.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "decoder/DecoderList.hxx" +#include "decoder/DecoderPlugin.hxx" +#include "input/InputStream.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +#include <assert.h> + +/** + * Does the #DecoderPlugin support either the suffix or the MIME type? + */ +gcc_pure +static bool +CheckDecoderPlugin(const DecoderPlugin &plugin, + const char *suffix, const char *mime) +{ + return (mime != nullptr && plugin.SupportsMimeType(mime)) || + (suffix != nullptr && plugin.SupportsSuffix(suffix)); +} + +bool +tag_stream_scan(InputStream &is, const tag_handler &handler, void *ctx) +{ + assert(is.ready); + + const char *const suffix = uri_get_suffix(is.uri.c_str()); + const char *const mime = is.mime.empty() ? nullptr : is.mime.c_str(); + + if (suffix == nullptr && mime == nullptr) + return false; + + return decoder_plugins_try([suffix, mime, &is, + &handler, ctx](const DecoderPlugin &plugin){ + is.LockRewind(IgnoreError()); + + return CheckDecoderPlugin(plugin, suffix, mime) && + plugin.ScanStream(is, handler, ctx); + }); +} + +bool +tag_stream_scan(const char *uri, const tag_handler &handler, void *ctx) +{ + Mutex mutex; + Cond cond; + + InputStream *is = InputStream::OpenReady(uri, mutex, cond, + IgnoreError()); + if (is == nullptr) + return false; + + bool success = tag_stream_scan(*is, handler, ctx); + is->Close(); + return success; +} diff --git a/src/TagStream.hxx b/src/TagStream.hxx new file mode 100644 index 000000000..2f93768ee --- /dev/null +++ b/src/TagStream.hxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_STREAM_HXX +#define MPD_TAG_STREAM_HXX + +#include "check.h" + +struct InputStream; +struct tag_handler; + +/** + * Scan the tags of an #InputStream. Invokes matching decoder + * plugins, but does not invoke the special "APE" and "ID3" scanners. + * + * @return true if the file was recognized (even if no metadata was + * found) + */ +bool +tag_stream_scan(InputStream &is, const tag_handler &handler, void *ctx); + +bool +tag_stream_scan(const char *uri, const tag_handler &handler, void *ctx); + +#endif diff --git a/src/TextFile.cxx b/src/TextFile.cxx deleted file mode 100644 index 4a64ee963..000000000 --- a/src/TextFile.cxx +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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(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 != nullptr); - assert(buffer != nullptr); - assert(buffer->allocated_len >= step); - - while (buffer->len < max_length) { - p = fgets(buffer->str + length, - buffer->allocated_len - length, file); - if (p == nullptr) { - if (length == 0 || ferror(file)) - return nullptr; - 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 deleted file mode 100644 index 9d8608711..000000000 --- a/src/TextFile.hxx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.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(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 nullptr on end-of-file or error - */ - char *ReadLine(); -}; - -#endif diff --git a/src/TextInputStream.cxx b/src/TextInputStream.cxx deleted file mode 100644 index 36a726aa6..000000000 --- a/src/TextInputStream.cxx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/CharUtil.hxx" -#include "util/fifo_buffer.h" -#include "util/Error.hxx" -#include "Log.hxx" - -#include <assert.h> -#include <string.h> - -bool TextInputStream::ReadLine(std::string &line) -{ - const char *src, *p; - - do { - size_t nbytes; - auto dest = buffer.Write(); - if (dest.size >= 2) { - /* reserve one byte for the null terminator if - the last line is not terminated by a - newline character */ - --dest.size; - - Error error; - nbytes = is.LockRead(dest.data, dest.size, error); - if (nbytes > 0) - buffer.Append(nbytes); - else if (error.IsDefined()) { - LogError(error); - return false; - } - } else - nbytes = 0; - - auto src_p = buffer.Read(); - if (src_p.IsEmpty()) - return false; - - src = src_p.data; - - p = reinterpret_cast<const char*>(memchr(src, '\n', src_p.size)); - if (p == nullptr && nbytes == 0) { - /* end of file (or line too long): terminate - the current line */ - dest = buffer.Write(); - assert(!dest.IsEmpty()); - dest.data[0] = '\n'; - buffer.Append(1); - } - } while (p == nullptr); - - size_t length = p - src + 1; - while (p > src && IsWhitespaceOrNull(p[-1])) - --p; - - line = std::string(src, p - src); - buffer.Consume(length); - return true; -} diff --git a/src/TextInputStream.hxx b/src/TextInputStream.hxx deleted file mode 100644 index a6c15f670..000000000 --- a/src/TextInputStream.hxx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "util/FifoBuffer.hxx" - -#include <string> - -struct InputStream; -struct fifo_buffer; - -class TextInputStream { - InputStream &is; - FifoBuffer<char, 4096> 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(InputStream &_is) - :is(_is) {} - - 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 index ee165d8e9..5526ec7d6 100644 --- a/src/TimePrint.cxx +++ b/src/TimePrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,7 +19,7 @@ #include "config.h" #include "TimePrint.hxx" -#include "Client.hxx" +#include "client/Client.hxx" void time_print(Client &client, const char *name, time_t t) diff --git a/src/TimePrint.hxx b/src/TimePrint.hxx index 55e235b66..afdb3c1c9 100644 --- a/src/TimePrint.hxx +++ b/src/TimePrint.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/Timer.cxx b/src/Timer.cxx index 661aa29ee..d3dcc714d 100644 --- a/src/Timer.cxx +++ b/src/Timer.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,13 +22,9 @@ #include "AudioFormat.hxx" #include "system/Clock.hxx" -#include <glib.h> - #include <limits> #include <assert.h> -#include <limits.h> -#include <stddef.h> Timer::Timer(const AudioFormat af) : time(0), @@ -69,14 +65,3 @@ unsigned Timer::GetDelay() const return delay; } - -void Timer::Synchronize() const -{ - int64_t sleep_duration; - - assert(started); - - sleep_duration = time - MonotonicClockUS(); - if (sleep_duration > 0) - g_usleep(sleep_duration); -} diff --git a/src/Timer.hxx b/src/Timer.hxx index c8b756e9c..3c935cfac 100644 --- a/src/Timer.hxx +++ b/src/Timer.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -42,8 +42,6 @@ public: * 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 deleted file mode 100644 index 3139a5926..000000000 --- a/src/UpdateArchive.cxx +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with 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/AllocatedPath.hxx" -#include "ArchiveList.hxx" -#include "ArchivePlugin.hxx" -#include "ArchiveFile.hxx" -#include "ArchiveVisitor.hxx" -#include "util/Error.hxx" -#include "Log.hxx" - -#include <string> - -#include <string.h> - -static void -update_archive_tree(Directory &directory, const char *name) -{ - const char *tmp = strchr(name, '/'); - if (tmp) { - const std::string child_name(name, tmp); - //add dir is not there already - db_lock(); - Directory *subdir = - directory.MakeChild(child_name.c_str()); - subdir->device = DEVICE_INARCHIVE; - db_unlock(); - - //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 == nullptr) { - song = Song::LoadFile(name, directory); - if (song != nullptr) { - db_lock(); - directory.AddSong(song); - db_unlock(); - - modified = true; - FormatDefault(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 != nullptr && 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 auto 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 == nullptr) { - LogError(error); - return; - } - - FormatDebug(update_domain, "archive %s opened", path_fs.c_str()); - - if (directory == nullptr) { - 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 == nullptr) - 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 deleted file mode 100644 index 8f52ca0b6..000000000 --- a/src/UpdateArchive.hxx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.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 deleted file mode 100644 index 80f059734..000000000 --- a/src/UpdateContainer.cxx +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with 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/AllocatedPath.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 nullptr 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 != nullptr) { - if (directory->mtime == st->st_mtime && !walk_discard) { - /* not modified */ - return nullptr; - } - - 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 DecoderPlugin &plugin) -{ - if (plugin.container_scan == nullptr) - return false; - - db_lock(); - Directory *contdir = make_directory_if_modified(directory, name, st); - if (contdir == nullptr) { - /* not modified */ - db_unlock(); - return true; - } - - contdir->device = DEVICE_CONTAINER; - db_unlock(); - - const auto 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)) != nullptr) { - Song *song = Song::NewFile(vtrack, contdir); - - // shouldn't be necessary but it's there.. - song->mtime = st->st_mtime; - - const auto child_path_fs = - map_directory_child_fs(*contdir, vtrack); - - plugin.ScanFile(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; - - FormatDefault(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 deleted file mode 100644 index 3b54fb39f..000000000 --- a/src/UpdateContainer.hxx +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 DecoderPlugin; - -bool -update_container_file(Directory &directory, - const char *name, - const struct stat *st, - const DecoderPlugin &plugin); - -#endif diff --git a/src/UpdateDatabase.cxx b/src/UpdateDatabase.cxx deleted file mode 100644 index 5dd2bb496..000000000 --- a/src/UpdateDatabase.cxx +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with 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 <assert.h> -#include <stddef.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 != nullptr); - - 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 != nullptr) { - delete_directory(directory); - modified = true; - } - - Song *song = parent.FindSong(name); - if (song != nullptr) { - delete_song(parent, song); - modified = true; - } - - parent.playlists.erase(name); - - db_unlock(); - - return modified; -} diff --git a/src/UpdateDatabase.hxx b/src/UpdateDatabase.hxx deleted file mode 100644 index 0462dc778..000000000 --- a/src/UpdateDatabase.hxx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index a2bbd5b70..000000000 --- a/src/UpdateDomain.cxx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License 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 deleted file mode 100644 index e7528a57e..000000000 --- a/src/UpdateDomain.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 12ea126a9..000000000 --- a/src/UpdateGlue.cxx +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "thread/Id.hxx" -#include "thread/Thread.hxx" - -#include <assert.h> - -static enum update_progress { - UPDATE_PROGRESS_IDLE = 0, - UPDATE_PROGRESS_RUNNING = 1, - UPDATE_PROGRESS_DONE = 2 -} progress; - -static bool modified; - -static Thread update_thread; - -static const unsigned update_task_id_max = 1 << 15; - -static unsigned update_task_id; - -static UpdateQueueItem next; - -unsigned -isUpdatingDB(void) -{ - return next.id; -} - -static void -update_task(gcc_unused void *ctx) -{ - if (!next.path_utf8.empty()) - FormatDebug(update_domain, "starting: %s", - next.path_utf8.c_str()); - else - LogDebug(update_domain, "starting"); - - modified = update_walk(next.path_utf8.c_str(), next.discard); - - if (modified || !db_exists()) { - Error error; - if (!db_save(error)) - LogError(error, "Failed to save database"); - } - - if (!next.path_utf8.empty()) - FormatDebug(update_domain, "finished: %s", - next.path_utf8.c_str()); - else - LogDebug(update_domain, "finished"); - - progress = UPDATE_PROGRESS_DONE; - GlobalEvents::Emit(GlobalEvents::UPDATE); -} - -static void -spawn_update_task(UpdateQueueItem &&i) -{ - assert(main_thread.IsInside()); - - progress = UPDATE_PROGRESS_RUNNING; - modified = false; - - next = std::move(i); - - Error error; - if (!update_thread.Start(update_task, nullptr, error)) - FatalError(error); - - FormatDebug(update_domain, - "spawned thread for update job id %i", next.id); -} - -static unsigned -generate_update_id() -{ - unsigned id = update_task_id + 1; - if (id > update_task_id_max) - id = 1; - return id; -} - -unsigned -update_enqueue(const char *path, bool discard) -{ - assert(main_thread.IsInside()); - - if (!db_is_simple() || !mapper_has_music_directory()) - return 0; - - if (progress != UPDATE_PROGRESS_IDLE) { - const unsigned id = generate_update_id(); - if (!update_queue_push(path, discard, id)) - return 0; - - update_task_id = id; - return id; - } - - const unsigned id = update_task_id = generate_update_id(); - spawn_update_task(UpdateQueueItem(path, discard, id)); - - idle_add(IDLE_UPDATE); - - return id; -} - -/** - * Called in the main thread after the database update is finished. - */ -static void update_finished_event(void) -{ - assert(progress == UPDATE_PROGRESS_DONE); - assert(next.IsDefined()); - - update_thread.Join(); - next = UpdateQueueItem(); - - idle_add(IDLE_UPDATE); - - if (modified) - /* send "idle" events */ - instance->DatabaseModified(); - - auto i = update_queue_shift(); - if (i.IsDefined()) { - /* schedule the next path */ - spawn_update_task(std::move(i)); - } 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 deleted file mode 100644 index c9fc0f9a1..000000000 --- a/src/UpdateGlue.hxx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 - -#include "Compiler.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 an empty string, - * the whole music directory is updated - * @return the job id, or 0 on error - */ -gcc_nonnull_all -unsigned -update_enqueue(const char *path, bool discard); - -#endif diff --git a/src/UpdateIO.cxx b/src/UpdateIO.cxx deleted file mode 100644 index e70e07db0..000000000 --- a/src/UpdateIO.cxx +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with 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/AllocatedPath.hxx" -#include "fs/FileSystem.hxx" -#include "Log.hxx" - -#include <errno.h> -#include <unistd.h> - -int -stat_directory(const Directory &directory, struct stat *st) -{ - const auto 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 auto 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 auto 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 auto 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 auto 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 deleted file mode 100644 index 9d6c197f2..000000000 --- a/src/UpdateIO.hxx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index de0850ece..000000000 --- a/src/UpdateInternal.hxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 2a30e5d5f..000000000 --- a/src/UpdateQueue.cxx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 <queue> -#include <list> - -static constexpr unsigned MAX_UPDATE_QUEUE_SIZE = 32; - -static std::queue<UpdateQueueItem, std::list<UpdateQueueItem>> update_queue; - -bool -update_queue_push(const char *path, bool discard, unsigned id) -{ - if (update_queue.size() >= MAX_UPDATE_QUEUE_SIZE) - return false; - - update_queue.emplace(path, discard, id); - return true; -} - -UpdateQueueItem -update_queue_shift() -{ - if (update_queue.empty()) - return UpdateQueueItem(); - - auto i = std::move(update_queue.front()); - update_queue.pop(); - return i; -} diff --git a/src/UpdateQueue.hxx b/src/UpdateQueue.hxx deleted file mode 100644 index 2769cc589..000000000 --- a/src/UpdateQueue.hxx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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" - -#include <string> - -struct UpdateQueueItem { - std::string path_utf8; - unsigned id; - bool discard; - - UpdateQueueItem():id(0) {} - UpdateQueueItem(const char *_path, bool _discard, - unsigned _id) - :path_utf8(_path), id(_id), discard(_discard) {} - - bool IsDefined() const { - return id != 0; - } -}; - -bool -update_queue_push(const char *path, bool discard, unsigned id); - -UpdateQueueItem -update_queue_shift(); - -#endif diff --git a/src/UpdateRemove.cxx b/src/UpdateRemove.cxx deleted file mode 100644 index f4043b2f3..000000000 --- a/src/UpdateRemove.cxx +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with 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 <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) -{ - assert(removed_song != nullptr); - - { - const auto uri = removed_song->GetURI(); - FormatDefault(update_domain, "removing %s", uri.c_str()); - } - -#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 = nullptr; - 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 == nullptr); - - removed_song = song; - - GlobalEvents::Emit(GlobalEvents::DELETE); - - remove_mutex.lock(); - - while (removed_song != nullptr) - remove_cond.wait(remove_mutex); - - remove_mutex.unlock(); -} diff --git a/src/UpdateRemove.hxx b/src/UpdateRemove.hxx deleted file mode 100644 index bef27d766..000000000 --- a/src/UpdateRemove.hxx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index bfab5c4a0..000000000 --- a/src/UpdateSong.cxx +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with 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 DecoderPlugin &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 != nullptr) { - db_lock(); - delete_song(directory, song); - db_unlock(); - } - - return; - } - - if (!(song != nullptr && st->st_mtime == song->mtime && - !walk_discard) && - update_container_file(directory, name, st, plugin)) { - if (song != nullptr) { - db_lock(); - delete_song(directory, song); - db_unlock(); - } - - return; - } - - if (song == nullptr) { - FormatDebug(update_domain, "reading %s/%s", - directory.GetPath(), name); - song = Song::LoadFile(name, &directory); - if (song == nullptr) { - FormatDebug(update_domain, - "ignoring unrecognized file %s/%s", - directory.GetPath(), name); - return; - } - - db_lock(); - directory.AddSong(song); - db_unlock(); - - modified = true; - FormatDefault(update_domain, "added %s/%s", - directory.GetPath(), name); - } else if (st->st_mtime != song->mtime || walk_discard) { - FormatDefault(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 DecoderPlugin *plugin = - decoder_plugin_from_suffix(suffix, nullptr); - if (plugin == nullptr) - return false; - - update_song_file2(directory, name, st, *plugin); - return true; -} diff --git a/src/UpdateSong.hxx b/src/UpdateSong.hxx deleted file mode 100644 index 00a7bfd27..000000000 --- a/src/UpdateSong.hxx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index d4586456b..000000000 --- a/src/UpdateWalk.cxx +++ /dev/null @@ -1,488 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with 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/AllocatedPath.hxx" -#include "fs/Traits.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 - -static constexpr bool DEFAULT_FOLLOW_INSIDE_SYMLINKS = true; -static constexpr bool 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 auto name_fs = AllocatedPath::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 auto name_fs = AllocatedPath::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 auto 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 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 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 == nullptr) - 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, '/') == nullptr); - - 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(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') != nullptr; -} - -gcc_pure -static bool -skip_symlink(const Directory *directory, const char *utf8_name) -{ -#ifndef WIN32 - const auto path_fs = map_directory_child_fs(*directory, utf8_name); - if (path_fs.IsNull()) - return true; - - const auto 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 (PathTraits::IsAbsoluteFS(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] == '.' && PathTraits::IsSeparatorFS(p[2])) { - /* "../" moves to parent directory */ - directory = directory->parent; - if (directory == nullptr) { - /* we have moved outside the music - directory - skip this symlink - if such symlinks are not allowed */ - return !follow_outside_symlinks; - } - p += 3; - } else if (PathTraits::IsSeparatorFS(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 auto 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(AllocatedPath::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 auto 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 != nullptr) - 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 nullptr; - - if (skip_symlink(&parent, name_utf8)) - return nullptr; - - /* 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, '/')) != nullptr) { - *slash = 0; - - if (*name_utf8 == 0) - continue; - - directory = directory_make_child_checked(*directory, - name_utf8); - if (directory == nullptr) - 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 == nullptr) - return; - - const char *name = PathTraits::GetBaseUTF8(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); -} - -bool -update_walk(const char *path, bool discard) -{ - walk_discard = discard; - modified = false; - - if (path != nullptr && !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 deleted file mode 100644 index 62c0d0a8e..000000000 --- a/src/UpdateWalk.hxx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 6c5f8dc4d..000000000 --- a/src/Volume.cxx +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 != nullptr); - - if (last_hardware_volume >= 0 && - g_timer_elapsed(hardware_volume_timer, nullptr) < 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 = nullptr; - 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 deleted file mode 100644 index 6b937aca3..000000000 --- a/src/Volume.hxx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.h" - -#include <stdio.h> - -void volume_init(void); - -void volume_finish(void); - -gcc_pure -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. - */ -gcc_pure -unsigned -sw_volume_state_get_hash(void); - -#endif diff --git a/src/Win32Main.cxx b/src/Win32Main.cxx deleted file mode 100644 index 0d2a70348..000000000 --- a/src/Win32Main.cxx +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "Compiler.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}, - {nullptr, nullptr} -}; - -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, nullptr); - - 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 deleted file mode 100644 index 083647b42..000000000 --- a/src/ZeroconfAvahi.cxx +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "AvahiPoll.hxx" -#include "ZeroconfInternal.hxx" -#include "Listen.hxx" -#include "system/FatalError.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <avahi-client/client.h> -#include <avahi-client/publish.h> - -#include <avahi-common/watch.h> -#include <avahi-common/alternative.h> -#include <avahi-common/domain.h> -#include <avahi-common/malloc.h> -#include <avahi-common/error.h> - -#include <stddef.h> - -static constexpr Domain avahi_domain("avahi"); - -static char *avahiName; -static bool avahi_running; -static MyAvahiPoll *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 */ - FormatDefault(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; - - FormatDefault(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 */ - avahi_running = false; - 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, nullptr); - 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, nullptr, - nullptr, listen_port, nullptr); - 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: - avahi_running = false; -} - -/* 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) { - LogDefault(avahi_domain, - "Client Disconnected, will reconnect shortly"); - if (avahiGroup) { - avahi_entry_group_free(avahiGroup); - avahiGroup = nullptr; - } - if (avahiClient) - avahi_client_free(avahiClient); - avahiClient = - avahi_client_new(avahi_poll, - AVAHI_CLIENT_NO_FAIL, - avahiClientCallback, nullptr, - &reason); - if (!avahiClient) { - FormatWarning(avahi_domain, - "Could not reconnect: %s", - avahi_strerror(reason)); - avahi_running = false; - } - } else { - FormatWarning(avahi_domain, - "Client failure: %s (terminal)", - avahi_strerror(reason)); - avahi_running = false; - } - 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); - - avahi_running = true; - - avahi_poll = new MyAvahiPoll(loop); - - int error; - avahiClient = avahi_client_new(avahi_poll, AVAHI_CLIENT_NO_FAIL, - avahiClientCallback, nullptr, &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 = nullptr; - } - - if (avahiClient) { - avahi_client_free(avahiClient); - avahiClient = nullptr; - } - - delete avahi_poll; - avahi_poll = nullptr; - - avahi_free(avahiName); - avahiName = nullptr; -} diff --git a/src/ZeroconfAvahi.hxx b/src/ZeroconfAvahi.hxx deleted file mode 100644 index bb046350a..000000000 --- a/src/ZeroconfAvahi.hxx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 73e84fbc2..000000000 --- a/src/ZeroconfBonjour.cxx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "Compiler.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, nullptr, nullptr, - g_htons(listen_port), 0, - nullptr, - dnsRegisterCallback, - nullptr); - - if (error != kDNSServiceErr_NoError) { - LogError(bonjour_domain, - "Failed to register zeroconf service"); - - if (dnsReference) { - DNSServiceRefDeallocate(dnsReference); - dnsReference = nullptr; - } - return; - } - - bonjour_monitor = new BonjourMonitor(loop, dnsReference); -} - -void -BonjourDeinit() -{ - delete bonjour_monitor; -} diff --git a/src/ZeroconfBonjour.hxx b/src/ZeroconfBonjour.hxx deleted file mode 100644 index d91fe9a0d..000000000 --- a/src/ZeroconfBonjour.hxx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 7c9c973cc..000000000 --- a/src/ZeroconfGlue.cxx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "Compiler.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) { - LogWarning(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 deleted file mode 100644 index 2a291ce29..000000000 --- a/src/ZeroconfGlue.hxx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 775e2dda2..000000000 --- a/src/ZeroconfInternal.hxx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License 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/archive/ArchiveDomain.cxx b/src/archive/ArchiveDomain.cxx new file mode 100644 index 000000000..4adf4a886 --- /dev/null +++ b/src/archive/ArchiveDomain.cxx @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ArchiveDomain.hxx" +#include "util/Domain.hxx" + +const Domain archive_domain("archive"); diff --git a/src/archive/ArchiveDomain.hxx b/src/archive/ArchiveDomain.hxx new file mode 100644 index 000000000..817ae5835 --- /dev/null +++ b/src/archive/ArchiveDomain.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_DOMAIN_HXX +#define MPD_ARCHIVE_DOMAIN_HXX + +extern const class Domain archive_domain; + +#endif diff --git a/src/archive/ArchiveFile.hxx b/src/archive/ArchiveFile.hxx new file mode 100644 index 000000000..b95edcc8d --- /dev/null +++ b/src/archive/ArchiveFile.hxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 Mutex; +class Cond; +class Error; +class ArchiveVisitor; +struct InputStream; + +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 InputStream of a file within the archive. + * + * @param path the path within the archive + */ + virtual InputStream *OpenStream(const char *path, + Mutex &mutex, Cond &cond, + Error &error) = 0; +}; + +#endif diff --git a/src/archive/ArchiveList.cxx b/src/archive/ArchiveList.cxx new file mode 100644 index 000000000..bf6493588 --- /dev/null +++ b/src/archive/ArchiveList.cxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "plugins/Bzip2ArchivePlugin.hxx" +#include "plugins/Iso9660ArchivePlugin.hxx" +#include "plugins/ZzipArchivePlugin.hxx" +#include "util/Macros.hxx" + +#include <string.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 + nullptr +}; + +/** which plugins have been initialized successfully? */ +static bool archive_plugins_enabled[ARRAY_SIZE(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 == nullptr) + return nullptr; + + archive_plugins_for_each_enabled(plugin) + if (plugin->suffixes != nullptr && + string_array_contains(plugin->suffixes, suffix)) + return plugin; + + return nullptr; +} + +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 nullptr; +} + +void archive_plugin_init_all(void) +{ + for (unsigned i = 0; archive_plugins[i] != nullptr; ++i) { + const struct archive_plugin *plugin = archive_plugins[i]; + if (plugin->init == nullptr || archive_plugins[i]->init()) + archive_plugins_enabled[i] = true; + } +} + +void archive_plugin_deinit_all(void) +{ + archive_plugins_for_each_enabled(plugin) + if (plugin->finish != nullptr) + plugin->finish(); +} + diff --git a/src/archive/ArchiveList.hxx b/src/archive/ArchiveList.hxx new file mode 100644 index 000000000..49798a93e --- /dev/null +++ b/src/archive/ArchiveList.hxx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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) != nullptr; \ + ++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/ArchiveLookup.cxx b/src/archive/ArchiveLookup.cxx new file mode 100644 index 000000000..53730c504 --- /dev/null +++ b/src/archive/ArchiveLookup.cxx @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with 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 "ArchiveDomain.hxx" +#include "Log.hxx" + +#include <string.h> +#include <sys/stat.h> +#include <errno.h> + +gcc_pure +static char * +FindSlash(char *p, size_t i) +{ + for (; i > 0; --i) + if (p[i] == '/') + return p + i; + + return nullptr; +} + +gcc_pure +static const char * +FindSuffix(const char *p, const char *i) +{ + for (; i > p; --i) { + if (*i == '.') + return i + 1; + } + + return nullptr; +} + +bool +archive_lookup(char *pathname, const char **archive, + const char **inpath, const char **suffix) +{ + size_t idx = strlen(pathname); + + char *slash = nullptr; + + while (true) { + //try to stat if its real directory + struct stat st_info; + if (stat(pathname, &st_info) == -1) { + if (errno != ENOTDIR) { + FormatErrno(archive_domain, + "Failed to stat %s", pathname); + return false; + } + } else { + //is something found ins original path (is not an archive) + if (slash == nullptr) + return false; + + //its a file ? + if (S_ISREG(st_info.st_mode)) { + //so the upper should be file + *archive = pathname; + *inpath = slash + 1; + + //try to get suffix + *suffix = FindSuffix(pathname, slash - 1); + return true; + } else { + FormatError(archive_domain, + "Not a regular file: %s", + pathname); + return false; + } + } + + //find one dir up + if (slash != nullptr) + *slash = '/'; + + slash = FindSlash(pathname, idx - 1); + if (slash == nullptr) + return false; + + *slash = 0; + idx = slash - pathname; + } +} + diff --git a/src/archive/ArchiveLookup.hxx b/src/archive/ArchiveLookup.hxx new file mode 100644 index 000000000..0c08951a9 --- /dev/null +++ b/src/archive/ArchiveLookup.hxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 + +/** + * + * 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, const char **archive, + const char **inpath, const char **suffix); + +#endif + diff --git a/src/archive/ArchivePlugin.cxx b/src/archive/ArchivePlugin.cxx new file mode 100644 index 000000000..f8025a121 --- /dev/null +++ b/src/archive/ArchivePlugin.cxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 != nullptr); + assert(plugin->open != nullptr); + assert(path != nullptr); + + ArchiveFile *file = plugin->open(path, error); + assert((file == nullptr) == error.IsDefined()); + + return file; +} diff --git a/src/archive/ArchivePlugin.hxx b/src/archive/ArchivePlugin.hxx new file mode 100644 index 000000000..602de240a --- /dev/null +++ b/src/archive/ArchivePlugin.hxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 + +class ArchiveFile; +class Error; + +struct archive_plugin { + const char *name; + + /** + * optional, set this to nullptr 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 nullptr 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 nullptr 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 nullptr + */ + const char *const*suffixes; +}; + +ArchiveFile * +archive_file_open(const struct archive_plugin *plugin, const char *path, + Error &error); + +#endif diff --git a/src/archive/ArchiveVisitor.hxx b/src/archive/ArchiveVisitor.hxx new file mode 100644 index 000000000..6759695ca --- /dev/null +++ b/src/archive/ArchiveVisitor.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/archive/Bzip2ArchivePlugin.cxx b/src/archive/Bzip2ArchivePlugin.cxx deleted file mode 100644 index d1e6b51af..000000000 --- a/src/archive/Bzip2ArchivePlugin.cxx +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 "InputStream.hxx" -#include "InputPlugin.hxx" -#include "util/RefCount.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "fs/Traits.hxx" - -#include <bzlib.h> - -#include <stdint.h> -#include <stddef.h> -#include <string.h> - -#ifdef HAVE_OLDER_BZIP2 -#define BZ2_bzDecompressInit bzDecompressInit -#define BZ2_bzDecompress bzDecompress -#endif - -class Bzip2ArchiveFile final : public ArchiveFile { -public: - RefCount ref; - - std::string name; - InputStream *const istream; - - Bzip2ArchiveFile(const char *path, InputStream *_is) - :ArchiveFile(bz2_archive_plugin), - name(PathTraits::GetBaseUTF8(path)), - istream(_is) { - // remove .bz2 suffix - const size_t len = name.length(); - if (len > 4) - name.erase(len - 4); - } - - ~Bzip2ArchiveFile() { - istream->Close(); - } - - void Ref() { - ref.Increment(); - } - - void Unref() { - if (!ref.Decrement()) - return; - - delete this; - } - - virtual void Close() override { - Unref(); - } - - virtual void Visit(ArchiveVisitor &visitor) override { - visitor.VisitArchiveEntry(name.c_str()); - } - - virtual InputStream *OpenStream(const char *path, - Mutex &mutex, Cond &cond, - Error &error) override; -}; - -struct Bzip2InputStream { - InputStream 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 InputPlugin bz2_inputplugin; - -static constexpr Domain bz2_domain("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; - InputStream *is = InputStream::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(); -} - -InputStream * -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 nullptr; - } - - return &bis->base; -} - -static void -bz2_is_close(InputStream *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(InputStream *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(InputStream *is) -{ - Bzip2InputStream *bis = (Bzip2InputStream *)is; - - return bis->eof; -} - -/* exported structures */ - -static const char *const bz2_extensions[] = { - "bz2", - nullptr -}; - -const InputPlugin 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 deleted file mode 100644 index a7933a7a7..000000000 --- a/src/archive/Bzip2ArchivePlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index ad21d4a3d..000000000 --- a/src/archive/Iso9660ArchivePlugin.cxx +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 "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 <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 InputStream *OpenStream(const char *path, - Mutex &mutex, Cond &cond, - Error &error) override; -}; - -extern const InputPlugin 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 nullptr; - } - - return new Iso9660ArchiveFile(iso); -} - -void -Iso9660ArchiveFile::Visit(ArchiveVisitor &visitor) -{ - Visit("/", visitor); -} - -/* single archive handling */ - -struct Iso9660InputStream { - InputStream 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(); - } -}; - -InputStream * -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 nullptr; - } - - Iso9660InputStream *iis = - new Iso9660InputStream(*this, pathname, mutex, cond, - statbuf); - return &iis->base; -} - -static void -iso9660_input_close(InputStream *is) -{ - Iso9660InputStream *iis = (Iso9660InputStream *)is; - - delete iis; -} - - -static size_t -iso9660_input_read(InputStream *is, void *ptr, size_t size, - Error &error) -{ - Iso9660InputStream *iis = (Iso9660InputStream *)is; - int 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) { - no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE); - } else { - no_blocks = size / 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(InputStream *is) -{ - return is->offset == is->size; -} - -/* exported structures */ - -static const char *const iso9660_archive_extensions[] = { - "iso", - nullptr -}; - -const InputPlugin 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 deleted file mode 100644 index 6fbab6159..000000000 --- a/src/archive/Iso9660ArchivePlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 973fe91dc..000000000 --- a/src/archive/ZzipArchivePlugin.cxx +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 "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 InputStream *OpenStream(const char *path, - Mutex &mutex, Cond &cond, - Error &error) override; -}; - -extern const InputPlugin 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, nullptr); - if (dir == nullptr) { - error.Format(zzip_domain, "Failed to open ZIP file %s", - pathname); - return nullptr; - } - - 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 { - InputStream 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(); - } -}; - -InputStream * -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 nullptr; - } - - ZzipInputStream *zis = - new ZzipInputStream(*this, pathname, - mutex, cond, - _file); - return &zis->base; -} - -static void -zzip_input_close(InputStream *is) -{ - ZzipInputStream *zis = (ZzipInputStream *)is; - - delete zis; -} - -static size_t -zzip_input_read(InputStream *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(InputStream *is) -{ - ZzipInputStream *zis = (ZzipInputStream *)is; - - return (InputPlugin::offset_type)zzip_tell(zis->file) == is->size; -} - -static bool -zzip_input_seek(InputStream *is, InputPlugin::offset_type 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", - nullptr -}; - -const InputPlugin 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 deleted file mode 100644 index 4ba16849b..000000000 --- a/src/archive/ZzipArchivePlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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/plugins/Bzip2ArchivePlugin.cxx b/src/archive/plugins/Bzip2ArchivePlugin.cxx new file mode 100644 index 000000000..c9648050b --- /dev/null +++ b/src/archive/plugins/Bzip2ArchivePlugin.cxx @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "input/InputStream.hxx" +#include "input/InputPlugin.hxx" +#include "util/RefCount.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "fs/Traits.hxx" + +#include <bzlib.h> + +#include <stddef.h> + +#ifdef HAVE_OLDER_BZIP2 +#define BZ2_bzDecompressInit bzDecompressInit +#define BZ2_bzDecompress bzDecompress +#endif + +class Bzip2ArchiveFile final : public ArchiveFile { +public: + RefCount ref; + + std::string name; + InputStream *const istream; + + Bzip2ArchiveFile(const char *path, InputStream *_is) + :ArchiveFile(bz2_archive_plugin), + name(PathTraitsUTF8::GetBase(path)), + istream(_is) { + // remove .bz2 suffix + const size_t len = name.length(); + if (len > 4) + name.erase(len - 4); + } + + ~Bzip2ArchiveFile() { + istream->Close(); + } + + void Ref() { + ref.Increment(); + } + + void Unref() { + if (!ref.Decrement()) + return; + + delete this; + } + + virtual void Close() override { + Unref(); + } + + virtual void Visit(ArchiveVisitor &visitor) override { + visitor.VisitArchiveEntry(name.c_str()); + } + + virtual InputStream *OpenStream(const char *path, + Mutex &mutex, Cond &cond, + Error &error) override; +}; + +struct Bzip2InputStream { + InputStream 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 InputPlugin bz2_inputplugin; + +static constexpr Domain bz2_domain("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; + InputStream *is = InputStream::OpenReady(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(); +} + +InputStream * +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 nullptr; + } + + return &bis->base; +} + +static void +bz2_is_close(InputStream *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(InputStream *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(InputStream *is) +{ + Bzip2InputStream *bis = (Bzip2InputStream *)is; + + return bis->eof; +} + +/* exported structures */ + +static const char *const bz2_extensions[] = { + "bz2", + nullptr +}; + +const InputPlugin 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/plugins/Bzip2ArchivePlugin.hxx b/src/archive/plugins/Bzip2ArchivePlugin.hxx new file mode 100644 index 000000000..060780633 --- /dev/null +++ b/src/archive/plugins/Bzip2ArchivePlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/Iso9660ArchivePlugin.cxx b/src/archive/plugins/Iso9660ArchivePlugin.cxx new file mode 100644 index 000000000..1a126d0f2 --- /dev/null +++ b/src/archive/plugins/Iso9660ArchivePlugin.cxx @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "input/InputStream.hxx" +#include "input/InputPlugin.hxx" +#include "util/RefCount.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <cdio/iso9660.h> + +#include <stdlib.h> +#include <string.h> + +#define CEILING(x, y) ((x+(y-1))/y) + +class Iso9660ArchiveFile final : public ArchiveFile { + RefCount ref; + + iso9660_t *iso; + +public: + Iso9660ArchiveFile(iso9660_t *_iso) + :ArchiveFile(iso9660_archive_plugin), iso(_iso) {} + + ~Iso9660ArchiveFile() { + iso9660_close(iso); + } + + void Ref() { + ref.Increment(); + } + + void Unref() { + if (ref.Decrement()) + delete this; + } + + long SeekRead(void *ptr, lsn_t start, long int i_size) const { + return iso9660_iso_seek_read(iso, ptr, start, i_size); + } + + void Visit(const char *path, ArchiveVisitor &visitor); + + virtual void Close() override { + Unref(); + } + + virtual void Visit(ArchiveVisitor &visitor) override; + + virtual InputStream *OpenStream(const char *path, + Mutex &mutex, Cond &cond, + Error &error) override; +}; + +extern const InputPlugin 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 nullptr; + } + + return new Iso9660ArchiveFile(iso); +} + +void +Iso9660ArchiveFile::Visit(ArchiveVisitor &visitor) +{ + Visit("/", visitor); +} + +/* single archive handling */ + +class Iso9660InputStream { + InputStream base; + + Iso9660ArchiveFile &archive; + + iso9660_stat_t *statbuf; + +public: + 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) { + base.ready = true; + base.size = statbuf->size; + + archive.Ref(); + } + + ~Iso9660InputStream() { + free(statbuf); + archive.Unref(); + } + + InputStream *Get() { + return &base; + } + + size_t Read(void *ptr, size_t size, Error &error); +}; + +InputStream * +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 nullptr; + } + + Iso9660InputStream *iis = + new Iso9660InputStream(*this, pathname, mutex, cond, + statbuf); + return iis->Get(); +} + +static void +iso9660_input_close(InputStream *is) +{ + Iso9660InputStream *iis = (Iso9660InputStream *)is; + + delete iis; +} + +inline size_t +Iso9660InputStream::Read(void *ptr, size_t size, Error &error) +{ + int readed = 0; + int no_blocks, cur_block; + size_t left_bytes = statbuf->size - base.offset; + + if (left_bytes < size) { + no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE); + } else { + no_blocks = size / ISO_BLOCKSIZE; + } + + if (no_blocks == 0) + return 0; + + cur_block = base.offset / ISO_BLOCKSIZE; + + readed = archive.SeekRead(ptr, 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; + } + + base.offset += readed; + return readed; +} + +static size_t +iso9660_input_read(InputStream *is, void *ptr, size_t size, + Error &error) +{ + Iso9660InputStream *iis = (Iso9660InputStream *)is; + return iis->Read(ptr, size, error); +} + +static bool +iso9660_input_eof(InputStream *is) +{ + return is->offset == is->size; +} + +/* exported structures */ + +static const char *const iso9660_archive_extensions[] = { + "iso", + nullptr +}; + +const InputPlugin 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/plugins/Iso9660ArchivePlugin.hxx b/src/archive/plugins/Iso9660ArchivePlugin.hxx new file mode 100644 index 000000000..e92d5962b --- /dev/null +++ b/src/archive/plugins/Iso9660ArchivePlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/ZzipArchivePlugin.cxx b/src/archive/plugins/ZzipArchivePlugin.cxx new file mode 100644 index 000000000..61b3c2b45 --- /dev/null +++ b/src/archive/plugins/ZzipArchivePlugin.cxx @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "input/InputStream.hxx" +#include "input/InputPlugin.hxx" +#include "util/RefCount.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <zzip/zzip.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 InputStream *OpenStream(const char *path, + Mutex &mutex, Cond &cond, + Error &error) override; +}; + +extern const InputPlugin 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, nullptr); + if (dir == nullptr) { + error.Format(zzip_domain, "Failed to open ZIP file %s", + pathname); + return nullptr; + } + + 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 { + InputStream 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(); + } +}; + +InputStream * +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 nullptr; + } + + ZzipInputStream *zis = + new ZzipInputStream(*this, pathname, + mutex, cond, + _file); + return &zis->base; +} + +static void +zzip_input_close(InputStream *is) +{ + ZzipInputStream *zis = (ZzipInputStream *)is; + + delete zis; +} + +static size_t +zzip_input_read(InputStream *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(InputStream *is) +{ + ZzipInputStream *zis = (ZzipInputStream *)is; + + return (InputPlugin::offset_type)zzip_tell(zis->file) == is->size; +} + +static bool +zzip_input_seek(InputStream *is, InputPlugin::offset_type 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", + nullptr +}; + +const InputPlugin 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/plugins/ZzipArchivePlugin.hxx b/src/archive/plugins/ZzipArchivePlugin.hxx new file mode 100644 index 000000000..f82f62eb6 --- /dev/null +++ b/src/archive/plugins/ZzipArchivePlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/check.h b/src/check.h index 0642a4b91..efb4556d1 100644 --- a/src/check.h +++ b/src/check.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/client/Client.cxx b/src/client/Client.cxx new file mode 100644 index 000000000..ce99faa89 --- /dev/null +++ b/src/client/Client.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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"); diff --git a/src/client/Client.hxx b/src/client/Client.hxx new file mode 100644 index 000000000..ec7d2d741 --- /dev/null +++ b/src/client/Client.hxx @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "check.h" +#include "ClientMessage.hxx" +#include "command/CommandListBuilder.hxx" +#include "event/FullyBufferedSocket.hxx" +#include "event/TimeoutMonitor.hxx" +#include "Compiler.h" + +#include <set> +#include <string> +#include <list> + +#include <stddef.h> +#include <stdarg.h> + +struct sockaddr; +class EventLoop; +struct Partition; + +class Client final : private FullyBufferedSocket, TimeoutMonitor { +public: + Partition &partition; + struct playlist &playlist; + struct PlayerControl &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); + + ~Client() { + if (FullyBufferedSocket::IsDefined()) + FullyBufferedSocket::Close(); + } + + bool IsConnected() const { + return FullyBufferedSocket::IsDefined(); + } + + gcc_pure + bool IsExpired() const { + return !FullyBufferedSocket::IsDefined(); + } + + void Close(); + void SetExpired(); + + using FullyBufferedSocket::Write; + + /** + * returns the uid of the client process, or a negative value + * if the uid is unknown + */ + int GetUID() const { + return uid; + } + + /** + * Is this client running on the same machine, connected with + * a local (UNIX domain) socket? + */ + bool IsLocal() const { + return uid > 0; + } + + unsigned GetPermission() const { + return permission; + } + + void SetPermission(unsigned _permission) { + permission = _permission; + } + + /** + * Send "idle" response to this client. + */ + void IdleNotify(); + void IdleAdd(unsigned flags); + bool IdleWait(unsigned flags); + + enum class SubscribeResult { + /** success */ + OK, + + /** invalid channel name */ + INVALID, + + /** already subscribed to this channel */ + ALREADY, + + /** too many subscriptions */ + FULL, + }; + + gcc_pure + bool IsSubscribed(const char *channel_name) const { + return subscriptions.find(channel_name) != subscriptions.end(); + } + + SubscribeResult Subscribe(const char *channel); + bool Unsubscribe(const char *channel); + void UnsubscribeAll(); + bool PushMessage(const ClientMessage &msg); + +private: + /* virtual methods from class BufferedSocket */ + virtual InputResult OnSocketInput(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; +}; + +void client_manager_init(void); + +void +client_new(EventLoop &loop, Partition &partition, + int fd, const sockaddr *sa, size_t sa_length, int uid); + +/** + * 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_printf(2,3) +void +client_printf(Client &client, const char *fmt, ...); + +#endif diff --git a/src/client/ClientEvent.cxx b/src/client/ClientEvent.cxx new file mode 100644 index 000000000..fd9f24b0d --- /dev/null +++ b/src/client/ClientEvent.cxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/client/ClientExpire.cxx b/src/client/ClientExpire.cxx new file mode 100644 index 000000000..5891756b6 --- /dev/null +++ b/src/client/ClientExpire.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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" + +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/client/ClientFile.cxx b/src/client/ClientFile.cxx new file mode 100644 index 000000000..bdd9b0426 --- /dev/null +++ b/src/client/ClientFile.cxx @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 <sys/stat.h> +#include <unistd.h> + +bool +client_allow_file(const Client &client, 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.GetUID(); + 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/client/ClientFile.hxx b/src/client/ClientFile.hxx new file mode 100644 index 000000000..5a02a8df7 --- /dev/null +++ b/src/client/ClientFile.hxx @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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, Path path_fs, Error &error); + +#endif diff --git a/src/client/ClientGlobal.cxx b/src/client/ClientGlobal.cxx new file mode 100644 index 000000000..8d90721e9 --- /dev/null +++ b/src/client/ClientGlobal.cxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "config/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/client/ClientIdle.cxx b/src/client/ClientIdle.cxx new file mode 100644 index 000000000..0b4fa5751 --- /dev/null +++ b/src/client/ClientIdle.cxx @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/client/ClientInternal.hxx b/src/client/ClientInternal.hxx new file mode 100644 index 000000000..a819d64b8 --- /dev/null +++ b/src/client/ClientInternal.hxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "command/CommandResult.hxx" + +static constexpr unsigned CLIENT_MAX_SUBSCRIPTIONS = 16; +static constexpr unsigned CLIENT_MAX_MESSAGES = 64; + +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; + +CommandResult +client_process_line(Client &client, char *line); + +#endif diff --git a/src/client/ClientList.cxx b/src/client/ClientList.cxx new file mode 100644 index 000000000..101802479 --- /dev/null +++ b/src/client/ClientList.cxx @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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()) { + delete list.front(); + list.pop_front(); + +#ifndef NDEBUG + --size; +#endif + } + + assert(size == 0); +} + +void +ClientList::IdleAdd(unsigned flags) +{ + assert(flags != 0); + + for (const auto &client : list) + client->IdleAdd(flags); +} diff --git a/src/client/ClientList.hxx b/src/client/ClientList.hxx new file mode 100644 index 000000000..35022fbf1 --- /dev/null +++ b/src/client/ClientList.hxx @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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) {} + ~ClientList() { + CloseAll(); + } + + 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/client/ClientMessage.cxx b/src/client/ClientMessage.cxx new file mode 100644 index 000000000..be6d2f007 --- /dev/null +++ b/src/client/ClientMessage.cxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License 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 "util/CharUtil.hxx" +#include "Compiler.h" + +gcc_const +static bool +valid_channel_char(const char ch) +{ + return IsAlphaNumericASCII(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/client/ClientMessage.hxx b/src/client/ClientMessage.hxx new file mode 100644 index 000000000..3d28e01dc --- /dev/null +++ b/src/client/ClientMessage.hxx @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.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/client/ClientNew.cxx b/src/client/ClientNew.cxx new file mode 100644 index 000000000..a080e9ec6 --- /dev/null +++ b/src/client/ClientNew.cxx @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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> +#ifdef WIN32 +#include <winsock2.h> +#else +#include <sys/socket.h> +#endif + +#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; + const auto remote = sockaddr_to_string(sa, sa_length); + + assert(fd >= 0); + +#ifdef HAVE_LIBWRAP + if (sa->sa_family != AF_UNIX) { + // TODO: shall we obtain the program name from argv[0]? + const char *progname = "mpd"; + + 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, remote.c_str()); + + close_socket(fd); + return; + } + } +#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); + + FormatInfo(client_domain, "[%u] opened from %s", + client->num, remote.c_str()); +} + +void +Client::Close() +{ + partition.instance.client_list->Remove(*this); + + SetExpired(); + + FormatInfo(client_domain, "[%u] closed", num); + delete this; +} diff --git a/src/client/ClientProcess.cxx b/src/client/ClientProcess.cxx new file mode 100644 index 000000000..96099a91c --- /dev/null +++ b/src/client/ClientProcess.cxx @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "command/AllCommands.hxx" +#include "Log.hxx" + +#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 CommandResult +client_process_command_list(Client &client, bool list_ok, + std::list<std::string> &&list) +{ + CommandResult ret = CommandResult::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 != CommandResult::OK || client.IsExpired()) + break; + else if (list_ok) + client_puts(client, "list_OK\n"); + } + + return ret; +} + +CommandResult +client_process_line(Client &client, char *line) +{ + CommandResult 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 CommandResult::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 CommandResult::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 == CommandResult::CLOSE || + client.IsExpired()) + return CommandResult::CLOSE; + + if (ret == CommandResult::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 CommandResult::CLOSE; + } + + ret = CommandResult::OK; + } + } else { + if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) { + client.cmd_list.Begin(false); + ret = CommandResult::OK; + } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) { + client.cmd_list.Begin(true); + ret = CommandResult::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 == CommandResult::CLOSE || + client.IsExpired()) + return CommandResult::CLOSE; + + if (ret == CommandResult::OK) + command_success(client); + } + } + + return ret; +} diff --git a/src/client/ClientRead.cxx b/src/client/ClientRead.cxx new file mode 100644 index 000000000..19deebd52 --- /dev/null +++ b/src/client/ClientRead.cxx @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "util/CharUtil.hxx" + +#include <string.h> + +BufferedSocket::InputResult +Client::OnSocketInput(void *data, size_t length) +{ + char *p = (char *)data; + char *newline = (char *)memchr(p, '\n', length); + if (newline == nullptr) + return InputResult::MORE; + + TimeoutMonitor::ScheduleSeconds(client_timeout); + + BufferedSocket::ConsumeInput(newline + 1 - p); + + /* skip whitespace at the end of the line */ + while (newline > p && IsWhitespaceOrNull(newline[-1])) + --newline; + + /* terminate the string at the end of the line */ + *newline = 0; + + CommandResult result = client_process_line(*this, p); + switch (result) { + case CommandResult::OK: + case CommandResult::IDLE: + case CommandResult::ERROR: + break; + + case CommandResult::KILL: + Close(); + main_loop->Break(); + return InputResult::CLOSED; + + case CommandResult::FINISH: + if (Flush()) + Close(); + return InputResult::CLOSED; + + case CommandResult::CLOSE: + Close(); + return InputResult::CLOSED; + } + + if (IsExpired()) { + Close(); + return InputResult::CLOSED; + } + + return InputResult::AGAIN; +} diff --git a/src/client/ClientSubscribe.cxx b/src/client/ClientSubscribe.cxx new file mode 100644 index 000000000..8ea2e363b --- /dev/null +++ b/src/client/ClientSubscribe.cxx @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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> + +Client::SubscribeResult +Client::Subscribe(const char *channel) +{ + assert(channel != nullptr); + + if (!client_message_valid_channel_name(channel)) + return Client::SubscribeResult::INVALID; + + if (num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS) + return Client::SubscribeResult::FULL; + + auto r = subscriptions.insert(channel); + if (!r.second) + return Client::SubscribeResult::ALREADY; + + ++num_subscriptions; + + idle_add(IDLE_SUBSCRIPTION); + + return Client::SubscribeResult::OK; +} + +bool +Client::Unsubscribe(const char *channel) +{ + const auto i = subscriptions.find(channel); + if (i == subscriptions.end()) + return false; + + assert(num_subscriptions > 0); + + subscriptions.erase(i); + --num_subscriptions; + + idle_add(IDLE_SUBSCRIPTION); + + assert((num_subscriptions == 0) == + subscriptions.empty()); + + return true; +} + +void +Client::UnsubscribeAll() +{ + subscriptions.clear(); + num_subscriptions = 0; +} + +bool +Client::PushMessage(const ClientMessage &msg) +{ + if (messages.size() >= CLIENT_MAX_MESSAGES || + !IsSubscribed(msg.GetChannel())) + return false; + + if (messages.empty()) + IdleAdd(IDLE_MESSAGE); + + messages.push_back(msg); + return true; +} diff --git a/src/client/ClientWrite.cxx b/src/client/ClientWrite.cxx new file mode 100644 index 000000000..b5d172a8d --- /dev/null +++ b/src/client/ClientWrite.cxx @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/FormatString.hxx" + +#include <string.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) +{ + char *p = FormatNewV(fmt, args); + client_write(client, p, strlen(p)); + delete[] p; +} + +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/command/AllCommands.cxx b/src/command/AllCommands.cxx index 74802ced4..750fd1219 100644 --- a/src/command/AllCommands.cxx +++ b/src/command/AllCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,23 +20,25 @@ #include "config.h" #include "AllCommands.hxx" #include "QueueCommands.hxx" +#include "TagCommands.hxx" #include "PlayerCommands.hxx" #include "PlaylistCommands.hxx" #include "DatabaseCommands.hxx" #include "FileCommands.hxx" #include "OutputCommands.hxx" #include "MessageCommands.hxx" +#include "NeighborCommands.hxx" #include "OtherCommands.hxx" #include "Permission.hxx" #include "tag/TagType.h" #include "protocol/Result.hxx" -#include "Client.hxx" +#include "client/Client.hxx" #include "util/Tokenizer.hxx" #include "util/Error.hxx" #ifdef ENABLE_SQLITE #include "StickerCommands.hxx" -#include "StickerDatabase.hxx" +#include "sticker/StickerDatabase.hxx" #endif #include <assert.h> @@ -74,9 +76,11 @@ handle_not_commands(Client &client, int argc, char *argv[]); static const struct command commands[] = { { "add", PERMISSION_ADD, 1, 1, handle_add }, { "addid", PERMISSION_ADD, 1, 2, handle_addid }, + { "addtagid", PERMISSION_ADD, 3, 3, handle_addtagid }, { "channels", PERMISSION_READ, 0, 0, handle_channels }, { "clear", PERMISSION_CONTROL, 0, 0, handle_clear }, { "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror }, + { "cleartagid", PERMISSION_ADD, 1, 2, handle_cleartagid }, { "close", PERMISSION_NONE, -1, -1, handle_close }, { "commands", PERMISSION_NONE, 0, 0, handle_commands }, { "config", PERMISSION_ADMIN, 0, 0, handle_config }, @@ -96,6 +100,9 @@ static const struct command commands[] = { { "list", PERMISSION_READ, 1, -1, handle_list }, { "listall", PERMISSION_READ, 0, 1, handle_listall }, { "listallinfo", PERMISSION_READ, 0, 1, handle_listallinfo }, +#ifdef ENABLE_NEIGHBOR_PLUGINS + { "listneighbors", PERMISSION_READ, 0, 0, handle_listneighbors }, +#endif { "listplaylist", PERMISSION_READ, 1, 1, handle_listplaylist }, { "listplaylistinfo", PERMISSION_READ, 1, 1, handle_listplaylistinfo }, { "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists }, @@ -176,6 +183,11 @@ command_available(gcc_unused const struct command *cmd) return sticker_enabled(); #endif +#ifdef ENABLE_NEIGHBOR_PLUGINS + if (strcmp(cmd->cmd, "listneighbors") == 0) + return neighbor_commands_available(); +#endif + return true; } diff --git a/src/command/AllCommands.hxx b/src/command/AllCommands.hxx index 2be94c136..b7834a8de 100644 --- a/src/command/AllCommands.hxx +++ b/src/command/AllCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/command/CommandError.cxx b/src/command/CommandError.cxx index fc14d4a5d..73e363f24 100644 --- a/src/command/CommandError.cxx +++ b/src/command/CommandError.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,14 +19,13 @@ #include "config.h" #include "CommandError.hxx" -#include "DatabaseError.hxx" +#include "db/DatabaseError.hxx" #include "protocol/Result.hxx" #include "util/Error.hxx" #include "Log.hxx" -#include <glib.h> - #include <assert.h> +#include <string.h> #include <errno.h> CommandResult @@ -38,7 +37,7 @@ print_playlist_result(Client &client, PlaylistResult result) case PlaylistResult::ERRNO: command_error(client, ACK_ERROR_SYSTEM, "%s", - g_strerror(errno)); + strerror(errno)); return CommandResult::ERROR; case PlaylistResult::DENIED: @@ -115,7 +114,7 @@ print_error(Client &client, const Error &error) } } else if (error.IsDomain(errno_domain)) { command_error(client, ACK_ERROR_SYSTEM, "%s", - g_strerror(error.GetCode())); + strerror(error.GetCode())); return CommandResult::ERROR; } diff --git a/src/command/CommandError.hxx b/src/command/CommandError.hxx index c7d3fff76..b48baa5bf 100644 --- a/src/command/CommandError.hxx +++ b/src/command/CommandError.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/command/CommandListBuilder.cxx b/src/command/CommandListBuilder.cxx index cc10f7205..1dcbf2946 100644 --- a/src/command/CommandListBuilder.cxx +++ b/src/command/CommandListBuilder.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,7 +19,7 @@ #include "config.h" #include "CommandListBuilder.hxx" -#include "ClientInternal.hxx" +#include "client/ClientInternal.hxx" #include <string.h> diff --git a/src/command/CommandListBuilder.hxx b/src/command/CommandListBuilder.hxx index a112ac33b..0747c4697 100644 --- a/src/command/CommandListBuilder.hxx +++ b/src/command/CommandListBuilder.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/command/CommandResult.hxx b/src/command/CommandResult.hxx index 9916b70cb..a2e968fb6 100644 --- a/src/command/CommandResult.hxx +++ b/src/command/CommandResult.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/command/DatabaseCommands.cxx b/src/command/DatabaseCommands.cxx index b86cbdae7..2b871e565 100644 --- a/src/command/DatabaseCommands.cxx +++ b/src/command/DatabaseCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,21 +19,17 @@ #include "config.h" #include "DatabaseCommands.hxx" -#include "DatabaseQueue.hxx" -#include "DatabasePlaylist.hxx" -#include "DatabasePrint.hxx" -#include "DatabaseSelection.hxx" +#include "db/DatabaseQueue.hxx" +#include "db/DatabasePlaylist.hxx" +#include "db/DatabasePrint.hxx" +#include "db/Selection.hxx" #include "CommandError.hxx" -#include "Client.hxx" +#include "client/Client.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> - CommandResult handle_lsinfo2(Client &client, int argc, char *argv[]) { diff --git a/src/command/DatabaseCommands.hxx b/src/command/DatabaseCommands.hxx index 76b2ba817..8678f19c8 100644 --- a/src/command/DatabaseCommands.hxx +++ b/src/command/DatabaseCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/command/FileCommands.cxx b/src/command/FileCommands.cxx index eecc3102f..b3676fa48 100644 --- a/src/command/FileCommands.cxx +++ b/src/command/FileCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,16 +22,19 @@ #include "CommandError.hxx" #include "protocol/Ack.hxx" #include "protocol/Result.hxx" -#include "ClientFile.hxx" -#include "Client.hxx" +#include "client/ClientFile.hxx" +#include "client/Client.hxx" #include "util/CharUtil.hxx" +#include "util/UriUtil.hxx" #include "util/Error.hxx" #include "tag/TagHandler.hxx" #include "tag/ApeTag.hxx" #include "tag/TagId3.hxx" +#include "TagStream.hxx" #include "TagFile.hxx" #include "Mapper.hxx" #include "fs/AllocatedPath.hxx" +#include "ls.hxx" #include <assert.h> @@ -80,6 +83,25 @@ static constexpr tag_handler print_comment_handler = { print_pair, }; +static CommandResult +read_stream_comments(Client &client, const char *uri) +{ + if (!uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return CommandResult::ERROR; + } + + if (!tag_stream_scan(uri, print_comment_handler, &client)) { + command_error(client, ACK_ERROR_NO_EXIST, + "Failed to load file"); + return CommandResult::ERROR; + } + + return CommandResult::OK; + +} + CommandResult handle_read_comments(Client &client, gcc_unused int argc, char *argv[]) { @@ -102,6 +124,8 @@ handle_read_comments(Client &client, gcc_unused int argc, char *argv[]) Error error; if (!client_allow_file(client, path_fs, error)) return print_error(client, error); + } else if (uri_has_scheme(uri)) { + return read_stream_comments(client, uri); } else if (*uri != '/') { path_fs = map_uri_fs(uri); if (path_fs.IsNull()) { @@ -114,7 +138,7 @@ handle_read_comments(Client &client, gcc_unused int argc, char *argv[]) return CommandResult::ERROR; } - if (!tag_file_scan(path_fs, &print_comment_handler, &client)) { + if (!tag_file_scan(path_fs, print_comment_handler, &client)) { command_error(client, ACK_ERROR_NO_EXIST, "Failed to load file"); return CommandResult::ERROR; diff --git a/src/command/FileCommands.hxx b/src/command/FileCommands.hxx index e184d6e30..8858b62c9 100644 --- a/src/command/FileCommands.hxx +++ b/src/command/FileCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/command/MessageCommands.cxx b/src/command/MessageCommands.cxx index 7d9893e70..b04e72c07 100644 --- a/src/command/MessageCommands.cxx +++ b/src/command/MessageCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,12 +19,11 @@ #include "config.h" #include "MessageCommands.hxx" -#include "Client.hxx" -#include "ClientList.hxx" +#include "client/Client.hxx" +#include "client/ClientList.hxx" #include "Instance.hxx" #include "Main.hxx" #include "protocol/Result.hxx" -#include "protocol/ArgParser.hxx" #include <set> #include <string> diff --git a/src/command/MessageCommands.hxx b/src/command/MessageCommands.hxx index 6a107f69d..2edc9e816 100644 --- a/src/command/MessageCommands.hxx +++ b/src/command/MessageCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/command/NeighborCommands.cxx b/src/command/NeighborCommands.cxx new file mode 100644 index 000000000..ee88c7935 --- /dev/null +++ b/src/command/NeighborCommands.cxx @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "NeighborCommands.hxx" +#include "client/Client.hxx" +#include "Instance.hxx" +#include "Main.hxx" +#include "protocol/Result.hxx" +#include "neighbor/Glue.hxx" +#include "neighbor/Info.hxx" + +#include <set> +#include <string> + +#include <assert.h> + +bool +neighbor_commands_available() +{ + return instance->neighbors != nullptr; +} + +CommandResult +handle_listneighbors(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + if (instance->neighbors == nullptr) { + command_error(client, ACK_ERROR_UNKNOWN, + "No neighbor plugin configured"); + return CommandResult::ERROR; + } + + const auto neighbors = instance->neighbors->GetList(); + for (const auto &i : neighbors) + client_printf(client, + "neighbor: %s\n" + "name: %s\n", + i.uri.c_str(), + i.display_name.c_str()); + return CommandResult::OK; +} diff --git a/src/command/NeighborCommands.hxx b/src/command/NeighborCommands.hxx new file mode 100644 index 000000000..7bad946b4 --- /dev/null +++ b/src/command/NeighborCommands.hxx @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NEIGHBOR_COMMANDS_HXX +#define MPD_NEIGHBOR_COMMANDS_HXX + +#include "CommandResult.hxx" +#include "Compiler.h" + +class Client; + +gcc_pure +bool +neighbor_commands_available(); + +CommandResult +handle_listneighbors(Client &client, int argc, char *argv[]); + +#endif diff --git a/src/command/OtherCommands.cxx b/src/command/OtherCommands.cxx index 7b2cb1331..6432ce4e7 100644 --- a/src/command/OtherCommands.cxx +++ b/src/command/OtherCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,19 +20,21 @@ #include "config.h" #include "OtherCommands.hxx" #include "DatabaseCommands.hxx" +#include "db/update/UpdateGlue.hxx" #include "CommandError.hxx" -#include "UpdateGlue.hxx" -#include "Directory.hxx" -#include "Song.hxx" +#include "db/Directory.hxx" +#include "DetachedSong.hxx" #include "SongPrint.hxx" #include "TagPrint.hxx" +#include "TagStream.hxx" +#include "tag/TagHandler.hxx" #include "TimePrint.hxx" #include "Mapper.hxx" -#include "DecoderPrint.hxx" +#include "decoder/DecoderPrint.hxx" #include "protocol/ArgParser.hxx" #include "protocol/Result.hxx" #include "ls.hxx" -#include "Volume.hxx" +#include "mixer/Volume.hxx" #include "util/ASCII.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" @@ -40,14 +42,11 @@ #include "Stats.hxx" #include "Permission.hxx" #include "PlaylistFile.hxx" -#include "ClientFile.hxx" -#include "Client.hxx" +#include "PlaylistVector.hxx" +#include "client/ClientFile.hxx" +#include "client/Client.hxx" #include "Idle.hxx" -#ifdef ENABLE_SQLITE -#include "StickerDatabase.hxx" -#endif - #include <assert.h> #include <string.h> @@ -102,6 +101,20 @@ handle_close(gcc_unused Client &client, return CommandResult::FINISH; } +static void +print_tag(TagType type, const char *value, void *ctx) +{ + Client &client = *(Client *)ctx; + + tag_print(client, type, value); +} + +static constexpr tag_handler print_tag_handler = { + nullptr, + print_tag, + nullptr, +}; + CommandResult handle_lsinfo(Client &client, int argc, char *argv[]) { @@ -128,15 +141,30 @@ handle_lsinfo(Client &client, int argc, char *argv[]) if (!client_allow_file(client, path_fs, error)) return print_error(client, error); - Song *song = Song::LoadFile(path_utf8, nullptr); - if (song == nullptr) { + DetachedSong song(path_utf8); + if (!song.Update()) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such file"); + return CommandResult::ERROR; + } + + song_print_info(client, song); + return CommandResult::OK; + } + + if (uri_has_scheme(uri)) { + if (!uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return CommandResult::ERROR; + } + + if (!tag_stream_scan(uri, print_tag_handler, &client)) { command_error(client, ACK_ERROR_NO_EXIST, "No such file"); return CommandResult::ERROR; } - song_print_info(client, *song); - song->Free(); return CommandResult::OK; } diff --git a/src/command/OtherCommands.hxx b/src/command/OtherCommands.hxx index 1a0b16ed1..4f54303bd 100644 --- a/src/command/OtherCommands.hxx +++ b/src/command/OtherCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/command/OutputCommands.cxx b/src/command/OutputCommands.cxx index e949448af..4ed3b89f1 100644 --- a/src/command/OutputCommands.cxx +++ b/src/command/OutputCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,13 +19,11 @@ #include "config.h" #include "OutputCommands.hxx" -#include "OutputPrint.hxx" -#include "OutputCommand.hxx" +#include "output/OutputPrint.hxx" +#include "output/OutputCommand.hxx" #include "protocol/Result.hxx" #include "protocol/ArgParser.hxx" -#include <string.h> - CommandResult handle_enableoutput(Client &client, gcc_unused int argc, char *argv[]) { diff --git a/src/command/OutputCommands.hxx b/src/command/OutputCommands.hxx index a5edc22e2..c12c14049 100644 --- a/src/command/OutputCommands.hxx +++ b/src/command/OutputCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/command/PlayerCommands.cxx b/src/command/PlayerCommands.cxx index 12c71dfd4..28258dded 100644 --- a/src/command/PlayerCommands.cxx +++ b/src/command/PlayerCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,10 +22,10 @@ #include "CommandError.hxx" #include "Playlist.hxx" #include "PlaylistPrint.hxx" -#include "UpdateGlue.hxx" -#include "Client.hxx" -#include "Volume.hxx" -#include "OutputAll.hxx" +#include "db/update/UpdateGlue.hxx" +#include "client/Client.hxx" +#include "mixer/Volume.hxx" +#include "output/OutputAll.hxx" #include "Partition.hxx" #include "protocol/Result.hxx" #include "protocol/ArgParser.hxx" diff --git a/src/command/PlayerCommands.hxx b/src/command/PlayerCommands.hxx index 8dad0855b..c90fa4548 100644 --- a/src/command/PlayerCommands.hxx +++ b/src/command/PlayerCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/command/PlaylistCommands.cxx b/src/command/PlaylistCommands.cxx index d178fa097..fbbb66757 100644 --- a/src/command/PlaylistCommands.cxx +++ b/src/command/PlaylistCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,15 +19,16 @@ #include "config.h" #include "PlaylistCommands.hxx" -#include "DatabasePlaylist.hxx" +#include "db/DatabasePlaylist.hxx" #include "CommandError.hxx" #include "PlaylistPrint.hxx" #include "PlaylistSave.hxx" #include "PlaylistFile.hxx" #include "PlaylistVector.hxx" -#include "PlaylistQueue.hxx" +#include "playlist/PlaylistQueue.hxx" +#include "playlist/Print.hxx" #include "TimePrint.hxx" -#include "Client.hxx" +#include "client/Client.hxx" #include "protocol/ArgParser.hxx" #include "protocol/Result.hxx" #include "ls.hxx" @@ -35,9 +36,6 @@ #include "util/UriUtil.hxx" #include "util/Error.hxx" -#include <assert.h> -#include <stdlib.h> - static void print_spl_list(Client &client, const PlaylistVector &list) { diff --git a/src/command/PlaylistCommands.hxx b/src/command/PlaylistCommands.hxx index 802d6ff2f..c737c54eb 100644 --- a/src/command/PlaylistCommands.hxx +++ b/src/command/PlaylistCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/command/QueueCommands.cxx b/src/command/QueueCommands.cxx index a21eb75f0..ed2b551c4 100644 --- a/src/command/QueueCommands.cxx +++ b/src/command/QueueCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,13 +20,13 @@ #include "config.h" #include "QueueCommands.hxx" #include "CommandError.hxx" -#include "DatabaseQueue.hxx" +#include "db/DatabaseQueue.hxx" #include "SongFilter.hxx" -#include "DatabaseSelection.hxx" +#include "db/Selection.hxx" #include "Playlist.hxx" #include "PlaylistPrint.hxx" -#include "ClientFile.hxx" -#include "Client.hxx" +#include "client/ClientFile.hxx" +#include "client/Client.hxx" #include "Partition.hxx" #include "protocol/ArgParser.hxx" #include "protocol/Result.hxx" diff --git a/src/command/QueueCommands.hxx b/src/command/QueueCommands.hxx index 90d744447..af5413d8c 100644 --- a/src/command/QueueCommands.hxx +++ b/src/command/QueueCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/command/StickerCommands.cxx b/src/command/StickerCommands.cxx index b65e6f607..68a0d585f 100644 --- a/src/command/StickerCommands.cxx +++ b/src/command/StickerCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,19 +20,17 @@ #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 "db/DatabaseLock.hxx" +#include "db/DatabasePlugin.hxx" +#include "db/DatabaseGlue.hxx" +#include "db/DatabaseSimple.hxx" +#include "sticker/SongSticker.hxx" +#include "sticker/StickerPrint.hxx" +#include "sticker/StickerDatabase.hxx" #include "CommandError.hxx" #include "protocol/Result.hxx" #include "util/Error.hxx" -#include <glib.h> - #include <string.h> struct sticker_song_find_data { @@ -41,7 +39,7 @@ struct sticker_song_find_data { }; static void -sticker_song_find_print_cb(Song &song, const char *value, +sticker_song_find_print_cb(const LightSong &song, const char *value, void *user_data) { struct sticker_song_find_data *data = @@ -61,11 +59,11 @@ handle_sticker_song(Client &client, int argc, char *argv[]) /* get song song_id key */ if (argc == 5 && strcmp(argv[1], "get") == 0) { - Song *song = db->GetSong(argv[3], error); + const LightSong *song = db->GetSong(argv[3], error); if (song == nullptr) return print_error(client, error); - const auto value = sticker_song_get_value(song, argv[4]); + const auto value = sticker_song_get_value(*song, argv[4]); db->ReturnSong(song); if (value.empty()) { command_error(client, ACK_ERROR_NO_EXIST, @@ -78,11 +76,11 @@ handle_sticker_song(Client &client, int argc, char *argv[]) return CommandResult::OK; /* list song song_id */ } else if (argc == 4 && strcmp(argv[1], "list") == 0) { - Song *song = db->GetSong(argv[3], error); + const LightSong *song = db->GetSong(argv[3], error); if (song == nullptr) return print_error(client, error); - sticker *sticker = sticker_song_get(song); + sticker *sticker = sticker_song_get(*song); db->ReturnSong(song); if (sticker) { sticker_print(client, *sticker); @@ -92,11 +90,11 @@ handle_sticker_song(Client &client, int argc, char *argv[]) return CommandResult::OK; /* set song song_id id key */ } else if (argc == 6 && strcmp(argv[1], "set") == 0) { - Song *song = db->GetSong(argv[3], error); + const LightSong *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]); + bool ret = sticker_song_set_value(*song, argv[4], argv[5]); db->ReturnSong(song); if (!ret) { command_error(client, ACK_ERROR_SYSTEM, @@ -108,13 +106,13 @@ handle_sticker_song(Client &client, int argc, char *argv[]) /* delete song song_id [key] */ } else if ((argc == 4 || argc == 5) && strcmp(argv[1], "delete") == 0) { - Song *song = db->GetSong(argv[3], error); + const LightSong *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]); + ? sticker_song_delete(*song) + : sticker_song_delete_value(*song, argv[4]); db->ReturnSong(song); if (!ret) { command_error(client, ACK_ERROR_SYSTEM, diff --git a/src/command/StickerCommands.hxx b/src/command/StickerCommands.hxx index 9e4380f37..ab8ac0db1 100644 --- a/src/command/StickerCommands.hxx +++ b/src/command/StickerCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/command/TagCommands.cxx b/src/command/TagCommands.cxx new file mode 100644 index 000000000..02e95af71 --- /dev/null +++ b/src/command/TagCommands.cxx @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "TagCommands.hxx" +#include "CommandError.hxx" +#include "client/Client.hxx" +#include "protocol/ArgParser.hxx" +#include "protocol/Result.hxx" +#include "tag/Tag.hxx" +#include "Partition.hxx" + +CommandResult +handle_addtagid(Client &client, gcc_unused int argc, char *argv[]) +{ + unsigned song_id; + if (!check_unsigned(client, &song_id, argv[1])) + return CommandResult::ERROR; + + const char *const tag_name = argv[2]; + const TagType tag_type = tag_name_parse_i(tag_name); + if (tag_type == TAG_NUM_OF_ITEM_TYPES) { + command_error(client, ACK_ERROR_ARG, + "Unknown tag type: %s", tag_name); + return CommandResult::ERROR; + } + + const char *const value = argv[3]; + + Error error; + if (!client.partition.playlist.AddSongIdTag(song_id, tag_type, value, + error)) + return print_error(client, error); + + return CommandResult::OK; +} + +CommandResult +handle_cleartagid(Client &client, int argc, char *argv[]) +{ + unsigned song_id; + if (!check_unsigned(client, &song_id, argv[1])) + return CommandResult::ERROR; + + TagType tag_type = TAG_NUM_OF_ITEM_TYPES; + if (argc >= 3) { + const char *const tag_name = argv[2]; + tag_type = tag_name_parse_i(tag_name); + if (tag_type == TAG_NUM_OF_ITEM_TYPES) { + command_error(client, ACK_ERROR_ARG, + "Unknown tag type: %s", tag_name); + return CommandResult::ERROR; + } + } + + Error error; + if (!client.partition.playlist.ClearSongIdTag(song_id, tag_type, + error)) + return print_error(client, error); + + return CommandResult::OK; +} diff --git a/src/command/TagCommands.hxx b/src/command/TagCommands.hxx new file mode 100644 index 000000000..b54ddb178 --- /dev/null +++ b/src/command/TagCommands.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_COMMANDS_HXX +#define MPD_TAG_COMMANDS_HXX + +#include "CommandResult.hxx" + +class Client; + +CommandResult +handle_addtagid(Client &client, int argc, char *argv[]); + +CommandResult +handle_cleartagid(Client &client, int argc, char *argv[]); + +#endif diff --git a/src/config/ConfigData.cxx b/src/config/ConfigData.cxx new file mode 100644 index 000000000..70e1e55ed --- /dev/null +++ b/src/config/ConfigData.cxx @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/AllocatedPath.hxx" +#include "system/FatalError.hxx" + +#include <assert.h> +#include <stdlib.h> + +int +block_param::GetIntValue() const +{ + char *endptr; + long value2 = strtol(value.c_str(), &endptr, 0); + if (*endptr != 0) + FormatFatalError("Not a valid number in line %i", line); + + return value2; +} + +unsigned +block_param::GetUnsignedValue() const +{ + char *endptr; + unsigned long value2 = strtoul(value.c_str(), &endptr, 0); + if (*endptr != 0) + FormatFatalError("Not a valid 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(_value), line(_line), used(false) {} + +config_param::~config_param() +{ + delete next; +} + +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(); +} + +AllocatedPath +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 AllocatedPath::Null(); + + s = default_value; + } + + AllocatedPath path = ParsePath(s, error); + if (gcc_unlikely(path.IsNull())) + error.FormatPrefix("Invalid path in \"%s\" at line %i: ", + name, line2); + + return path; +} + +AllocatedPath +config_param::GetBlockPath(const char *name, Error &error) const +{ + return GetBlockPath(name, nullptr, error); +} + +int +config_param::GetBlockValue(const char *name, int default_value) const +{ + const block_param *bp = GetBlockParam(name); + if (bp == nullptr) + return default_value; + + return bp->GetIntValue(); +} + +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/config/ConfigData.hxx b/src/config/ConfigData.hxx new file mode 100644 index 000000000..e42d674ba --- /dev/null +++ b/src/config/ConfigData.hxx @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.h" + +#include <string> +#include <array> +#include <vector> + +class AllocatedPath; +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 + int GetIntValue() const; + + 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; + + std::string 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), 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; + + /** + * Same as config_dup_path(), but looks up the setting in the + * specified block. + */ + AllocatedPath GetBlockPath(const char *name, const char *default_value, + Error &error) const; + + AllocatedPath GetBlockPath(const char *name, Error &error) const; + + gcc_pure + int GetBlockValue(const char *name, int default_value) 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/config/ConfigDefaults.hxx b/src/config/ConfigDefaults.hxx new file mode 100644 index 000000000..c50f28c91 --- /dev/null +++ b/src/config/ConfigDefaults.hxx @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/config/ConfigError.cxx b/src/config/ConfigError.cxx new file mode 100644 index 000000000..70aff7175 --- /dev/null +++ b/src/config/ConfigError.cxx @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License 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/config/ConfigError.hxx b/src/config/ConfigError.hxx new file mode 100644 index 000000000..cbfa79df3 --- /dev/null +++ b/src/config/ConfigError.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/config/ConfigFile.cxx b/src/config/ConfigFile.cxx new file mode 100644 index 000000000..f045213a4 --- /dev/null +++ b/src/config/ConfigFile.cxx @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "ConfigData.hxx" +#include "ConfigTemplates.hxx" +#include "util/Tokenizer.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "fs/Limits.hxx" +#include "fs/Path.hxx" +#include "fs/FileSystem.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <stdio.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 == nullptr) { + assert(!tokenizer.IsEnd()); + return false; + } + + const char *value = tokenizer.NextString(error); + if (value == nullptr) { + 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 != nullptr) { + 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 == nullptr) { + delete ret; + error.Set(config_file_domain, + "Expected '}' before end-of-file"); + return nullptr; + } + + (*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 nullptr; + } + } +} + +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 == nullptr) { + 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 == nullptr) { + return false; + } + } else { + /* a string value */ + + value = tokenizer.NextString(error); + if (value == nullptr) { + 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, 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/config/ConfigFile.hxx b/src/config/ConfigFile.hxx new file mode 100644 index 000000000..b87182c6a --- /dev/null +++ b/src/config/ConfigFile.hxx @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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, Path path, Error &error); + +#endif diff --git a/src/config/ConfigGlobal.cxx b/src/config/ConfigGlobal.cxx new file mode 100644 index 000000000..dd76e3ca3 --- /dev/null +++ b/src/config/ConfigGlobal.cxx @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "fs/AllocatedPath.hxx" +#include "util/Error.hxx" +#include "system/FatalError.hxx" +#include "Log.hxx" + +#include <stdlib.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(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 config_param * +config_find_block(ConfigOption option, const char *key, const char *value) +{ + const config_param *param = nullptr; + while ((param = config_get_next_param(option, param)) != nullptr) { + const char *value2 = param->GetBlockValue(key); + if (value2 == nullptr) + FormatFatalError("block without '%s' name in line %d", + key, param->line); + + if (strcmp(value2, value) == 0) + return param; + } + + return nullptr; +} + +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.c_str(); +} + +AllocatedPath +config_get_path(ConfigOption option, Error &error) +{ + const struct config_param *param = config_get_param(option); + if (param == nullptr) + return AllocatedPath::Null(); + + return config_parse_path(param, error); +} + +AllocatedPath +config_parse_path(const struct config_param *param, Error & error) +{ + AllocatedPath path = ParsePath(param->value.c_str(), 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.c_str(), &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.c_str(), &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.c_str(), &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/config/ConfigGlobal.hxx b/src/config/ConfigGlobal.hxx new file mode 100644 index 000000000..84ef7dd5f --- /dev/null +++ b/src/config/ConfigGlobal.hxx @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.h" + +class Error; +class Path; +class AllocatedPath; + +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(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); +} + +/** + * Find a block with a matching attribute. + * + * @param option the blocks to search + * @param key the attribute name + * @param value the expected attribute value + */ +gcc_pure +const config_param * +config_find_block(ConfigOption option, const char *key, const char *value); + +/* 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 AllocatedPath::Null() if the value is not present. If the path + * could not be parsed, returns AllocatedPath::Null() and sets the error. + */ +AllocatedPath +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 AllocatedPath::Null() and sets the error. + */ +AllocatedPath +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/config/ConfigOption.hxx b/src/config/ConfigOption.hxx new file mode 100644 index 000000000..506c9e9dc --- /dev/null +++ b/src/config/ConfigOption.hxx @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.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_NEIGHBORS, + CONF_MAX +}; + +/** + * @return #CONF_MAX if not found + */ +gcc_pure +enum ConfigOption +ParseConfigOptionName(const char *name); + +#endif diff --git a/src/config/ConfigParser.cxx b/src/config/ConfigParser.cxx new file mode 100644 index 000000000..3535c9a13 --- /dev/null +++ b/src/config/ConfigParser.cxx @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License 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/config/ConfigParser.hxx b/src/config/ConfigParser.hxx new file mode 100644 index 000000000..06151b0bd --- /dev/null +++ b/src/config/ConfigParser.hxx @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/config/ConfigPath.cxx b/src/config/ConfigPath.cxx new file mode 100644 index 000000000..a3b3f83a5 --- /dev/null +++ b/src/config/ConfigPath.cxx @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/AllocatedPath.hxx" +#include "fs/Traits.hxx" +#include "fs/Domain.hxx" +#include "fs/StandardDirectory.hxx" +#include "util/Error.hxx" +#include "ConfigGlobal.hxx" + +#include <assert.h> +#include <string.h> + +#ifndef WIN32 +#include <pwd.h> + +/** + * Determine a given user's home directory. + */ +static AllocatedPath +GetHome(const char *user, Error &error) +{ + AllocatedPath result = GetHomeDir(user); + if (result.IsNull()) { + error.Format(path_domain, + "no such user: %s", user); + return AllocatedPath::Null(); + } + + return result; +} + +/** + * Determine the current user's home directory. + */ +static AllocatedPath +GetHome(Error &error) +{ + AllocatedPath result = GetHomeDir(); + if (result.IsNull()) { + error.Set(path_domain, + "problems getting home for current user"); + return AllocatedPath::Null(); + } + + return result; +} + +/** + * Determine the configured user's home directory. + */ +static AllocatedPath +GetConfiguredHome(Error &error) +{ + const char *user = config_get_string(CONF_USER, nullptr); + return user != nullptr + ? GetHome(user, error) + : GetHome(error); +} + +#endif + +AllocatedPath +ParsePath(const char *path, Error &error) +{ + assert(path != nullptr); + +#ifndef WIN32 + if (path[0] == '~') { + ++path; + + if (*path == '\0') + return GetConfiguredHome(error); + + AllocatedPath home = AllocatedPath::Null(); + + if (*path == '/') { + home = GetConfiguredHome(error); + + ++path; + } else { + const char *slash = strchr(path, '/'); + const char *end = slash == nullptr + ? path + strlen(path) + : slash; + const std::string user(path, end); + home = GetHome(user.c_str(), error); + + if (slash == nullptr) + return home; + + path = slash + 1; + } + + if (home.IsNull()) + return AllocatedPath::Null(); + + AllocatedPath path2 = AllocatedPath::FromUTF8(path, error); + if (path2.IsNull()) + return AllocatedPath::Null(); + + return AllocatedPath::Build(home, path2); + } else if (!PathTraitsUTF8::IsAbsolute(path)) { + error.Format(path_domain, + "not an absolute path: %s", path); + return AllocatedPath::Null(); + } else { +#endif + return AllocatedPath::FromUTF8(path, error); +#ifndef WIN32 + } +#endif +} diff --git a/src/config/ConfigPath.hxx b/src/config/ConfigPath.hxx new file mode 100644 index 000000000..a5518a497 --- /dev/null +++ b/src/config/ConfigPath.hxx @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 AllocatedPath; +class Error; + +AllocatedPath +ParsePath(const char *path, Error &error); + +#endif diff --git a/src/config/ConfigTemplates.cxx b/src/config/ConfigTemplates.cxx new file mode 100644 index 000000000..8eaa22bdd --- /dev/null +++ b/src/config/ConfigTemplates.cxx @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License 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 }, + { "neighbors", true, 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/config/ConfigTemplates.hxx b/src/config/ConfigTemplates.hxx new file mode 100644 index 000000000..90d098dc0 --- /dev/null +++ b/src/config/ConfigTemplates.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 + +struct ConfigTemplate { + const char *const name; + const bool repeatable; + const bool block; +}; + +extern const ConfigTemplate config_templates[]; + +#endif diff --git a/src/cue/CueParser.cxx b/src/cue/CueParser.cxx index 60b33b6b4..dc96218f4 100644 --- a/src/cue/CueParser.cxx +++ b/src/cue/CueParser.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,19 +19,18 @@ #include "config.h" #include "CueParser.hxx" +#include "util/Alloc.hxx" #include "util/StringUtil.hxx" #include "util/CharUtil.hxx" -#include "Song.hxx" +#include "DetachedSong.hxx" #include "tag/Tag.hxx" -#include <glib.h> - #include <assert.h> #include <string.h> #include <stdlib.h> CueParser::CueParser() - :state(HEADER), tag(new Tag()), + :state(HEADER), current(nullptr), previous(nullptr), finished(nullptr), @@ -39,16 +38,9 @@ CueParser::CueParser() CueParser::~CueParser() { - delete tag; - - if (current != nullptr) - current->Free(); - - if (previous != nullptr) - previous->Free(); - - if (finished != nullptr) - finished->Free(); + delete current; + delete previous; + delete finished; } static const char * @@ -109,7 +101,7 @@ cue_next_value(char **pp) } static void -cue_add_tag(Tag &tag, TagType type, char *p) +cue_add_tag(TagBuilder &tag, TagType type, char *p) { const char *value = cue_next_value(&p); if (value != nullptr) @@ -118,7 +110,7 @@ cue_add_tag(Tag &tag, TagType type, char *p) } static void -cue_parse_rem(char *p, Tag &tag) +cue_parse_rem(char *p, TagBuilder &tag) { const char *type = cue_next_token(&p); if (type == nullptr) @@ -129,13 +121,13 @@ cue_parse_rem(char *p, Tag &tag) cue_add_tag(tag, type2, p); } -Tag * +TagBuilder * CueParser::GetCurrentTag() { if (state == HEADER) - return tag; + return &header_tag; else if (state == TRACK) - return current->tag; + return &song_tag; else return nullptr; } @@ -172,6 +164,9 @@ CueParser::Commit() if (current == nullptr) return; + assert(!current->GetTag().IsDefined()); + current->SetTag(song_tag.Commit()); + finished = previous; previous = current; current = nullptr; @@ -188,9 +183,9 @@ CueParser::Feed2(char *p) return; if (strcmp(command, "REM") == 0) { - Tag *current_tag = GetCurrentTag(); - if (current_tag != nullptr) - cue_parse_rem(p, *current_tag); + TagBuilder *tag = GetCurrentTag(); + if (tag != nullptr) + 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 @@ -202,14 +197,14 @@ CueParser::Feed2(char *p) ? TAG_ARTIST : TAG_ALBUM_ARTIST; - Tag *current_tag = GetCurrentTag(); - if (current_tag != nullptr) - cue_add_tag(*current_tag, type, p); + TagBuilder *tag = GetCurrentTag(); + if (tag != nullptr) + cue_add_tag(*tag, type, p); } else if (strcmp(command, "TITLE") == 0) { if (state == HEADER) - cue_add_tag(*tag, TAG_ALBUM, p); + cue_add_tag(header_tag, TAG_ALBUM, p); else if (state == TRACK) - cue_add_tag(*current->tag, TAG_TITLE, p); + cue_add_tag(song_tag, TAG_TITLE, p); } else if (strcmp(command, "FILE") == 0) { Commit(); @@ -249,10 +244,12 @@ CueParser::Feed2(char *p) } state = TRACK; - current = Song::NewRemote(filename.c_str()); - assert(current->tag == nullptr); - current->tag = new Tag(*tag); - current->tag->AddItem(TAG_TRACK, nr); + current = new DetachedSong(filename); + assert(!current->GetTag().IsDefined()); + + song_tag = header_tag; + song_tag.AddItem(TAG_TRACK, nr); + last_updated = false; } else if (state == IGNORE_TRACK) { return; @@ -270,14 +267,14 @@ CueParser::Feed2(char *p) return; if (!last_updated && previous != nullptr && - previous->start_ms < (unsigned)position_ms) { + previous->GetStartMS() < (unsigned)position_ms) { last_updated = true; - previous->end_ms = position_ms; - previous->tag->time = - (previous->end_ms - previous->start_ms + 500) / 1000; + previous->SetEndMS(position_ms); + previous->WritableTag().time = + (previous->GetEndMS() - previous->GetStartMS() + 500) / 1000; } - current->start_ms = position_ms; + current->SetStartMS(position_ms); } } @@ -287,9 +284,9 @@ CueParser::Feed(const char *line) assert(!end); assert(line != nullptr); - char *allocated = g_strdup(line); + char *allocated = xstrdup(line); Feed2(allocated); - g_free(allocated); + free(allocated); } void @@ -303,7 +300,7 @@ CueParser::Finish() end = true; } -Song * +DetachedSong * CueParser::Get() { if (finished == nullptr && end) { @@ -315,7 +312,7 @@ CueParser::Get() previous = nullptr; } - Song *song = finished; + DetachedSong *song = finished; finished = nullptr; return song; } diff --git a/src/cue/CueParser.hxx b/src/cue/CueParser.hxx index abcceaa2e..7e040169b 100644 --- a/src/cue/CueParser.hxx +++ b/src/cue/CueParser.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,11 +21,12 @@ #define MPD_CUE_PARSER_HXX #include "check.h" +#include "tag/TagBuilder.hxx" #include "Compiler.h" #include <string> -struct Song; +class DetachedSong; struct Tag; class CueParser { @@ -56,26 +57,36 @@ class CueParser { IGNORE_TRACK, } state; - Tag *tag; + /** + * Tags read from the CUE header. + */ + TagBuilder header_tag; + + /** + * Tags read for the current song (attribute #current). When + * #current gets moved to #previous, TagBuilder::Commit() will + * be called. + */ + TagBuilder song_tag; std::string filename; /** * The song currently being edited. */ - Song *current; + DetachedSong *current; /** * The previous song. It is remembered because its end_time * will be set to the current song's start time. */ - Song *previous; + DetachedSong *previous; /** * A song that is completely finished and can be returned to * the caller via cue_parser_get(). */ - Song *finished; + DetachedSong *finished; /** * Set to true after previous.end_time has been updated to the @@ -114,11 +125,11 @@ public: * @return a song object that must be freed by the caller, or NULL if * no song was finished at this time */ - Song *Get(); + DetachedSong *Get(); private: gcc_pure - Tag *GetCurrentTag(); + TagBuilder *GetCurrentTag(); /** * Commit the current song. It will be moved to "previous", diff --git a/src/db/DatabaseError.cxx b/src/db/DatabaseError.cxx new file mode 100644 index 000000000..e0cbdd6a3 --- /dev/null +++ b/src/db/DatabaseError.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/db/DatabaseError.hxx b/src/db/DatabaseError.hxx new file mode 100644 index 000000000..1485a21b6 --- /dev/null +++ b/src/db/DatabaseError.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/db/DatabaseGlue.cxx b/src/db/DatabaseGlue.cxx new file mode 100644 index 000000000..3734e156c --- /dev/null +++ b/src/db/DatabaseGlue.cxx @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "Registry.hxx" +#include "DatabaseError.hxx" +#include "Directory.hxx" +#include "util/Error.hxx" +#include "config/ConfigData.hxx" +#include "Stats.hxx" +#include "DatabasePlugin.hxx" +#include "plugins/SimpleDatabasePlugin.hxx" + +#include <assert.h> +#include <string.h> + +static Database *db; +static bool db_is_open; +static bool is_simple; + +bool +DatabaseGlobalInit(EventLoop &loop, DatabaseListener &listener, + const config_param ¶m, Error &error) +{ + assert(db == nullptr); + 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 == nullptr) { + error.Format(db_domain, + "No such database plugin: %s", plugin_name); + return false; + } + + db = plugin->create(loop, listener, param, error); + return db != nullptr; +} + +void +DatabaseGlobalDeinit(void) +{ + if (db_is_open) + db->Close(); + + if (db != nullptr) + delete db; +} + +const Database * +GetDatabase() +{ + assert(db == nullptr || 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 == nullptr || db_is_open); + + return is_simple; +} + +Directory * +db_get_root(void) +{ + assert(db != nullptr); + assert(db_is_simple()); + + return ((SimpleDatabase *)db)->GetRoot(); +} + +Directory * +db_get_directory(const char *name) +{ + if (db == nullptr) + return nullptr; + + Directory *music_root = db_get_root(); + if (name == nullptr) + return music_root; + + return music_root->LookupDirectory(name); +} + +bool +db_save(Error &error) +{ + assert(db != nullptr); + assert(db_is_open); + assert(db_is_simple()); + + return ((SimpleDatabase *)db)->Save(error); +} + +bool +DatabaseGlobalOpen(Error &error) +{ + assert(db != nullptr); + assert(!db_is_open); + + if (!db->Open(error)) + return false; + + db_is_open = true; + + return true; +} + +bool +db_exists() +{ + assert(db != nullptr); + assert(db_is_open); + assert(db_is_simple()); + + return ((SimpleDatabase *)db)->GetUpdateStamp() > 0; +} diff --git a/src/db/DatabaseGlue.hxx b/src/db/DatabaseGlue.hxx new file mode 100644 index 000000000..78032edb2 --- /dev/null +++ b/src/db/DatabaseGlue.hxx @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.h" + +struct config_param; +class EventLoop; +class DatabaseListener; +class Database; +class Error; + +/** + * Initialize the database library. + * + * @param param the database configuration block + */ +bool +DatabaseGlobalInit(EventLoop &loop, DatabaseListener &listener, + const config_param ¶m, Error &error); + +void +DatabaseGlobalDeinit(void); + +bool +DatabaseGlobalOpen(Error &error); + +/** + * Returns the global #Database instance. May return nullptr if this MPD + * configuration has no database (no music_directory was configured). + */ +gcc_const +const Database * +GetDatabase(); + +/** + * Returns the global #Database instance. May return nullptr if this MPD + * configuration has no database (no music_directory was configured). + */ +gcc_pure +const Database * +GetDatabase(Error &error); + +#endif diff --git a/src/db/DatabaseListener.hxx b/src/db/DatabaseListener.hxx new file mode 100644 index 000000000..4da458866 --- /dev/null +++ b/src/db/DatabaseListener.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_CLIENT_HXX +#define MPD_DATABASE_CLIENT_HXX + +/** + * An object that listens to events from the #Database. + * + * @see #Instance + */ +class DatabaseListener { +public: + /** + * The database has been modified. This must be called in the + * thread that has created the #Database instance and that + * runs the #EventLoop. + */ + virtual void OnDatabaseModified() = 0; +}; + +#endif diff --git a/src/db/DatabaseLock.cxx b/src/db/DatabaseLock.cxx new file mode 100644 index 000000000..c0b5e4844 --- /dev/null +++ b/src/db/DatabaseLock.cxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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" + +Mutex db_mutex; + +#ifndef NDEBUG +ThreadId db_mutex_holder; +#endif diff --git a/src/db/DatabaseLock.hxx b/src/db/DatabaseLock.hxx new file mode 100644 index 000000000..9d0b0c152 --- /dev/null +++ b/src/db/DatabaseLock.hxx @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "Compiler.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/db/DatabasePlaylist.cxx b/src/db/DatabasePlaylist.cxx new file mode 100644 index 000000000..64b365d2a --- /dev/null +++ b/src/db/DatabasePlaylist.cxx @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "Selection.hxx" +#include "PlaylistFile.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" +#include "DetachedSong.hxx" +#include "Mapper.hxx" + +#include <functional> + +static bool +AddSong(const char *playlist_path_utf8, + const LightSong &song, Error &error) +{ + return spl_append_song(playlist_path_utf8, map_song_detach(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/db/DatabasePlaylist.hxx b/src/db/DatabasePlaylist.hxx new file mode 100644 index 000000000..1ee7584d3 --- /dev/null +++ b/src/db/DatabasePlaylist.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.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/db/DatabasePlugin.hxx b/src/db/DatabasePlugin.hxx new file mode 100644 index 000000000..b0cb41502 --- /dev/null +++ b/src/db/DatabasePlugin.hxx @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "Visitor.hxx" +#include "tag/TagType.h" +#include "Compiler.h" + +#include <time.h> + +struct config_param; +struct DatabaseSelection; +struct db_visitor; +struct LightSong; +class Error; +class EventLoop; +class DatabaseListener; + +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 const LightSong *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(const LightSong *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, + TagType tag_type, + VisitString visit_string, + Error &error) const = 0; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + Error &error) const = 0; + + /** + * Returns the time stamp of the last database update. + * Returns 0 if that is not not known/available. + */ + gcc_pure + virtual time_t GetUpdateStamp() const = 0; +}; + +struct DatabasePlugin { + const char *name; + + /** + * Allocates and configures a database. + */ + Database *(*create)(EventLoop &loop, DatabaseListener &listener, + const config_param ¶m, + Error &error); +}; + +#endif diff --git a/src/db/DatabasePrint.cxx b/src/db/DatabasePrint.cxx new file mode 100644 index 000000000..9ed0b0826 --- /dev/null +++ b/src/db/DatabasePrint.cxx @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "Selection.hxx" +#include "SongFilter.hxx" +#include "SongPrint.hxx" +#include "TimePrint.hxx" +#include "client/Client.hxx" +#include "tag/Tag.hxx" +#include "LightSong.hxx" +#include "LightDirectory.hxx" +#include "PlaylistInfo.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" + +#include <functional> + +static bool +PrintDirectoryBrief(Client &client, const LightDirectory &directory) +{ + if (!directory.IsRoot()) + client_printf(client, "directory: %s\n", directory.GetPath()); + + return true; +} + +static bool +PrintDirectoryFull(Client &client, const LightDirectory &directory) +{ + if (!directory.IsRoot()) { + client_printf(client, "directory: %s\n", directory.GetPath()); + + if (directory.mtime > 0) + time_print(client, "Last-Modified", directory.mtime); + } + + return true; +} + +static void +print_playlist_in_directory(Client &client, + const char *directory, + const char *name_utf8) +{ + if (directory == nullptr) + client_printf(client, "playlist: %s\n", name_utf8); + else + client_printf(client, "playlist: %s/%s\n", + directory, name_utf8); +} + +static void +print_playlist_in_directory(Client &client, + const LightDirectory *directory, + const char *name_utf8) +{ + if (directory == nullptr || 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, const LightSong &song) +{ + song_print_uri(client, song); + + if (song.tag->has_playlist) + /* this song file has an embedded CUE sheet */ + print_playlist_in_directory(client, song.directory, song.uri); + + return true; +} + +static bool +PrintSongFull(Client &client, const LightSong &song) +{ + song_print_info(client, song); + + if (song.tag->has_playlist) + /* this song file has an embedded CUE sheet */ + print_playlist_in_directory(client, song.directory, song.uri); + + return true; +} + +static bool +PrintPlaylistBrief(Client &client, + const PlaylistInfo &playlist, + const LightDirectory &directory) +{ + print_playlist_in_directory(client, &directory, playlist.name.c_str()); + return true; +} + +static bool +PrintPlaylistFull(Client &client, + const PlaylistInfo &playlist, + const LightDirectory &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, + std::ref(client), _1) + : VisitDirectory(); + const auto s = std::bind(full ? PrintSongFull : PrintSongBrief, + std::ref(client), _1); + const auto p = selection.filter == nullptr + ? std::bind(full ? PrintPlaylistFull : PrintPlaylistBrief, + std::ref(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, const LightSong &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, const LightSong &song) +{ + song_print_uri(client, song); + + return true; +} + +static bool +PrintUniqueTag(Client &client, TagType 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, + std::ref(client), _1); + return db->Visit(selection, f, error); + } else { + using namespace std::placeholders; + const auto f = std::bind(PrintUniqueTag, std::ref(client), + (TagType)type, _1); + return db->VisitUniqueTags(selection, (TagType)type, + f, error); + } +} diff --git a/src/db/DatabasePrint.hxx b/src/db/DatabasePrint.hxx new file mode 100644 index 000000000..2007e256b --- /dev/null +++ b/src/db/DatabasePrint.hxx @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.h" + +class SongFilter; +struct DatabaseSelection; +class Client; +class Error; + +bool +db_selection_print(Client &client, const DatabaseSelection &selection, + bool full, Error &error); + +gcc_nonnull(2) +bool +printAllIn(Client &client, const char *uri_utf8, Error &error); + +gcc_nonnull(2) +bool +printInfoForAllIn(Client &client, const char *uri_utf8, + Error &error); + +gcc_nonnull(2) +bool +searchStatsForSongsIn(Client &client, const char *name, + const SongFilter *filter, + Error &error); + +bool +listAllUniqueTags(Client &client, int type, + const SongFilter *filter, + Error &error); + +#endif diff --git a/src/db/DatabaseQueue.cxx b/src/db/DatabaseQueue.cxx new file mode 100644 index 000000000..ee1dbd57c --- /dev/null +++ b/src/db/DatabaseQueue.cxx @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" +#include "Partition.hxx" +#include "util/Error.hxx" +#include "DetachedSong.hxx" +#include "Mapper.hxx" + +#include <functional> + +static bool +AddToQueue(Partition &partition, const LightSong &song, Error &error) +{ + PlaylistResult result = + partition.playlist.AppendSong(partition.pc, + map_song_detach(song), + nullptr); + if (result != PlaylistResult::SUCCESS) { + error.Set(playlist_domain, int(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/db/DatabaseQueue.hxx b/src/db/DatabaseQueue.hxx new file mode 100644 index 000000000..e653f973c --- /dev/null +++ b/src/db/DatabaseQueue.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/db/DatabaseSave.cxx b/src/db/DatabaseSave.cxx new file mode 100644 index 000000000..e9c81442b --- /dev/null +++ b/src/db/DatabaseSave.cxx @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "fs/TextFile.hxx" +#include "tag/Tag.hxx" +#include "tag/TagSettings.h" +#include "fs/Charset.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <string.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: " + +static constexpr unsigned DB_FORMAT = 1; + +void +db_save_internal(FILE *fp, const Directory &music_root) +{ + 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, GetFSCharset()); + + 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; + unsigned format = 0; + bool found_charset = false, found_version = false; + bool success; + bool tags[TAG_NUM_OF_ITEM_TYPES]; + + /* get initial info */ + line = file.ReadLine(); + if (line == nullptr || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) { + error.Set(db_domain, "Database corrupted"); + return false; + } + + memset(tags, false, sizeof(tags)); + + while ((line = file.ReadLine()) != nullptr && + strcmp(line, DIRECTORY_INFO_END) != 0) { + if (StringStartsWith(line, DB_FORMAT_PREFIX)) { + format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1); + } else if (StringStartsWith(line, DIRECTORY_MPD_VERSION)) { + if (found_version) { + error.Set(db_domain, "Duplicate version line"); + return false; + } + + found_version = true; + } else if (StringStartsWith(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 char *const old_charset = GetFSCharset(); + if (*old_charset != 0 + && strcmp(new_charset, old_charset) != 0) { + error.Format(db_domain, + "Existing database has charset " + "\"%s\" instead of \"%s\"; " + "discarding database file", + new_charset, old_charset); + return false; + } + } else if (StringStartsWith(line, DB_TAG_PREFIX)) { + const char *name = line + sizeof(DB_TAG_PREFIX) - 1; + TagType 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/db/DatabaseSave.hxx b/src/db/DatabaseSave.hxx new file mode 100644 index 000000000..3bd3377ae --- /dev/null +++ b/src/db/DatabaseSave.hxx @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/db/DatabaseSimple.hxx b/src/db/DatabaseSimple.hxx new file mode 100644 index 000000000..b99b3bfa5 --- /dev/null +++ b/src/db/DatabaseSimple.hxx @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.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); + +/** + * Returns true if there is a valid database file on the disk. + * + * May only be used if db_is_simple() returns true. + */ +gcc_pure +bool +db_exists(); + +#endif diff --git a/src/db/DatabaseSong.cxx b/src/db/DatabaseSong.cxx new file mode 100644 index 000000000..592d38b85 --- /dev/null +++ b/src/db/DatabaseSong.cxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseSong.hxx" +#include "DatabaseGlue.hxx" +#include "DatabasePlugin.hxx" +#include "DetachedSong.hxx" +#include "Mapper.hxx" + +DetachedSong * +DatabaseDetachSong(const char *uri, Error &error) +{ + const Database *db = GetDatabase(error); + if (db == nullptr) + return nullptr; + + const LightSong *tmp = db->GetSong(uri, error); + if (tmp == nullptr) + return nullptr; + + DetachedSong *song = new DetachedSong(map_song_detach(*tmp)); + db->ReturnSong(tmp); + return song; +} diff --git a/src/db/DatabaseSong.hxx b/src/db/DatabaseSong.hxx new file mode 100644 index 000000000..0200af6b8 --- /dev/null +++ b/src/db/DatabaseSong.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_SONG_HXX +#define MPD_DATABASE_SONG_HXX + +#include "Compiler.h" + +class DetachedSong; +class Error; + +/** + * Look up a song in the database and convert it to a #DetachedSong + * instance. The caller is responsible for freeing it. + * + * @return nullptr on error + */ +gcc_malloc gcc_nonnull_all +DetachedSong * +DatabaseDetachSong(const char *uri, Error &error); + +#endif diff --git a/src/db/Directory.cxx b/src/db/Directory.cxx new file mode 100644 index 000000000..e74eabd19 --- /dev/null +++ b/src/db/Directory.cxx @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "LightDirectory.hxx" +#include "SongFilter.hxx" +#include "PlaylistVector.hxx" +#include "db/DatabaseLock.hxx" +#include "SongSort.hxx" +#include "Song.hxx" +#include "LightSong.hxx" +#include "fs/Traits.hxx" +#include "util/Alloc.hxx" +#include "util/Error.hxx" + +extern "C" { +#include "util/list_sort.h" +} + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +Directory::Directory(const char *_path_utf8, Directory *_parent) + :parent(_parent), + mtime(0), have_stat(false), + path(_path_utf8) +{ + INIT_LIST_HEAD(&children); + INIT_LIST_HEAD(&songs); +} + +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) + delete child; +} + +void +Directory::Delete() +{ + assert(holding_db_lock()); + assert(parent != nullptr); + + list_del(&siblings); + delete this; +} + +const char * +Directory::GetName() const +{ + assert(!IsRoot()); + + return PathTraitsUTF8::GetBase(path.c_str()); +} + +Directory * +Directory::CreateChild(const char *name_utf8) +{ + assert(holding_db_lock()); + assert(name_utf8 != nullptr); + assert(*name_utf8 != 0); + + char *allocated; + const char *path_utf8; + if (IsRoot()) { + allocated = nullptr; + path_utf8 = name_utf8; + } else { + allocated = g_strconcat(GetPath(), + "/", name_utf8, nullptr); + path_utf8 = allocated; + } + + Directory *child = new Directory(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 nullptr; +} + +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 != nullptr); + + if (isRootDirectory(uri)) + return this; + + char *duplicated = xstrdup(uri), *name = duplicated; + + Directory *d = this; + while (1) { + char *slash = strchr(name, '/'); + if (slash == name) { + d = nullptr; + break; + } + + if (slash != nullptr) + *slash = '\0'; + + d = d->FindChild(name); + if (d == nullptr || slash == nullptr) + break; + + name = slash + 1; + } + + free(duplicated); + + return d; +} + +void +Directory::AddSong(Song *song) +{ + assert(holding_db_lock()); + assert(song != nullptr); + assert(song->parent == this); + + list_add_tail(&song->siblings, &songs); +} + +void +Directory::RemoveSong(Song *song) +{ + assert(holding_db_lock()); + assert(song != nullptr); + assert(song->parent == this); + + list_del(&song->siblings); +} + +const Song * +Directory::FindSong(const char *name_utf8) const +{ + assert(holding_db_lock()); + assert(name_utf8 != nullptr); + + Song *song; + directory_for_each_song(song, *this) { + assert(song->parent == this); + + if (strcmp(song->uri, name_utf8) == 0) + return song; + } + + return nullptr; +} + +Song * +Directory::LookupSong(const char *uri) +{ + char *duplicated, *base; + + assert(holding_db_lock()); + assert(uri != nullptr); + + duplicated = xstrdup(uri); + base = strrchr(duplicated, '/'); + + Directory *d = this; + if (base != nullptr) { + *base++ = 0; + d = d->LookupDirectory(duplicated); + if (d == nullptr) { + free(duplicated); + return nullptr; + } + } else + base = duplicated; + + Song *song = d->FindSong(base); + assert(song == nullptr || song->parent == d); + + 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.c_str(), b->path.c_str()); +} + +void +Directory::Sort() +{ + assert(holding_db_lock()); + + list_sort(nullptr, &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) { + const LightSong song2 = song->Export(); + if ((filter == nullptr || filter->Match(song2)) && + !visit_song(song2, error)) + return false; + } + } + + if (visit_playlist) { + for (const PlaylistInfo &p : playlists) + if (!visit_playlist(p, Export(), error)) + return false; + } + + Directory *child; + directory_for_each_child(child, *this) { + if (visit_directory && + !visit_directory(child->Export(), error)) + return false; + + if (recursive && + !child->Walk(recursive, filter, + visit_directory, visit_song, visit_playlist, + error)) + return false; + } + + return true; +} + +LightDirectory +Directory::Export() const +{ + return LightDirectory(GetPath(), mtime); +} diff --git a/src/db/Directory.hxx b/src/db/Directory.hxx new file mode 100644 index 000000000..e114b27f4 --- /dev/null +++ b/src/db/Directory.hxx @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.h" +#include "db/Visitor.hxx" +#include "PlaylistVector.hxx" + +#include <string> + +#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 */ + + std::string path; + +public: + Directory(const char *_path_utf8, Directory *_parent); + ~Directory(); + + /** + * Create a new root #Directory object. + */ + gcc_malloc + static Directory *NewRoot() { + return new Directory("", nullptr); + } + + /** + * 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 nullptr 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.c_str(); + } + + /** + * 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 == nullptr; + } + + /** + * 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 nullptr 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; + + gcc_pure + LightDirectory Export() const; +}; + +static inline bool +isRootDirectory(const char *name) +{ + return name[0] == 0 || (name[0] == '/' && name[1] == 0); +} + +#endif diff --git a/src/db/DirectorySave.cxx b/src/db/DirectorySave.cxx new file mode 100644 index 000000000..499f84734 --- /dev/null +++ b/src/db/DirectorySave.cxx @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "DetachedSong.hxx" +#include "PlaylistDatabase.hxx" +#include "fs/TextFile.hxx" +#include "util/StringUtil.hxx" +#include "util/NumberParser.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <stddef.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) { + fprintf(fp, DIRECTORY_DIR "%s\n", cur->GetName()); + + 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 nullptr; + } + + Directory *directory = parent.CreateChild(name); + + const char *line = file.ReadLine(); + if (line == nullptr) { + error.Set(directory_domain, "Unexpected end of file"); + directory->Delete(); + return nullptr; + } + + if (StringStartsWith(line, DIRECTORY_MTIME)) { + directory->mtime = + ParseUint64(line + sizeof(DIRECTORY_MTIME) - 1); + + line = file.ReadLine(); + if (line == nullptr) { + error.Set(directory_domain, "Unexpected end of file"); + directory->Delete(); + return nullptr; + } + } + + if (!StringStartsWith(line, DIRECTORY_BEGIN)) { + error.Format(directory_domain, "Malformed line: %s", line); + directory->Delete(); + return nullptr; + } + + success = directory_load(file, *directory, error); + if (!success) { + directory->Delete(); + return nullptr; + } + + return directory; +} + +bool +directory_load(TextFile &file, Directory &directory, Error &error) +{ + const char *line; + + while ((line = file.ReadLine()) != nullptr && + !StringStartsWith(line, DIRECTORY_END)) { + if (StringStartsWith(line, DIRECTORY_DIR)) { + Directory *subdir = + directory_load_subdir(file, directory, + line + sizeof(DIRECTORY_DIR) - 1, + error); + if (subdir == nullptr) + return false; + } else if (StringStartsWith(line, SONG_BEGIN)) { + const char *name = line + sizeof(SONG_BEGIN) - 1; + + if (directory.FindSong(name) != nullptr) { + error.Format(directory_domain, + "Duplicate song '%s'", name); + return false; + } + + DetachedSong *song = song_load(file, name, error); + if (song == nullptr) + return false; + + directory.AddSong(Song::NewFrom(std::move(*song), + directory)); + delete song; + } else if (StringStartsWith(line, PLAYLIST_META_BEGIN)) { + const char *name = line + sizeof(PLAYLIST_META_BEGIN) - 1; + if (!playlist_metadata_load(file, directory.playlists, + name, error)) + return false; + } else { + error.Format(directory_domain, + "Malformed line: %s", line); + return false; + } + } + + return true; +} diff --git a/src/db/DirectorySave.hxx b/src/db/DirectorySave.hxx new file mode 100644 index 000000000..07e9e158b --- /dev/null +++ b/src/db/DirectorySave.hxx @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/db/Helpers.cxx b/src/db/Helpers.cxx new file mode 100644 index 000000000..579b83e15 --- /dev/null +++ b/src/db/Helpers.cxx @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "Helpers.hxx" +#include "DatabasePlugin.hxx" +#include "LightSong.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, TagType tag_type, const LightSong &song) +{ + const Tag *tag = song.tag; + + 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, + TagType 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, + const LightSong &song) +{ + ++stats.song_count; + + 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/db/Helpers.hxx b/src/db/Helpers.hxx new file mode 100644 index 000000000..24db260c0 --- /dev/null +++ b/src/db/Helpers.hxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Visitor.hxx" +#include "tag/TagType.h" + +class Error; +class Database; +struct DatabaseSelection; +struct DatabaseStats; + +bool +VisitUniqueTags(const Database &db, const DatabaseSelection &selection, + TagType tag_type, + VisitString visit_string, + Error &error); + +bool +GetStats(const Database &db, const DatabaseSelection &selection, + DatabaseStats &stats, Error &error); + +#endif diff --git a/src/db/LightDirectory.hxx b/src/db/LightDirectory.hxx new file mode 100644 index 000000000..d134151a4 --- /dev/null +++ b/src/db/LightDirectory.hxx @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_LIGHT_DIRECTORY_HXX +#define MPD_LIGHT_DIRECTORY_HXX + +#include "Compiler.h" + +#include <string> + +#include <time.h> + +struct Tag; + +/** + * A reference to a directory. Unlike the #Directory class, this one + * consists only of pointers. It is supposed to be as light as + * possible while still providing all the information MPD has about a + * directory. This class does not manage any memory, and the pointers + * become invalid quickly. Only to be used to pass around during + * well-defined situations. + */ +struct LightDirectory { + const char *uri; + + time_t mtime; + + constexpr LightDirectory(const char *_uri, time_t _mtime) + :uri(_uri), mtime(_mtime) {} + + static constexpr LightDirectory Root() { + return LightDirectory("", 0); + } + + bool IsRoot() const { + return *uri == 0; + } + + gcc_pure + const char *GetPath() const { + return uri; + } +}; + +#endif diff --git a/src/db/LightSong.cxx b/src/db/LightSong.cxx new file mode 100644 index 000000000..af1e801f8 --- /dev/null +++ b/src/db/LightSong.cxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "LightSong.hxx" +#include "tag/Tag.hxx" + +double +LightSong::GetDuration() const +{ + if (end_ms > 0) + return (end_ms - start_ms) / 1000.0; + + if (tag->time <= 0) + return 0; + + return tag->time - start_ms / 1000.0; +} diff --git a/src/db/LightSong.hxx b/src/db/LightSong.hxx new file mode 100644 index 000000000..c0cd47749 --- /dev/null +++ b/src/db/LightSong.hxx @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_LIGHT_SONG_HXX +#define MPD_LIGHT_SONG_HXX + +#include "Compiler.h" + +#include <string> + +#include <time.h> + +struct Tag; + +/** + * A reference to a song file. Unlike the other "Song" classes in the + * MPD code base, this one consists only of pointers. It is supposed + * to be as light as possible while still providing all the + * information MPD has about a song file. This class does not manage + * any memory, and the pointers become invalid quickly. Only to be + * used to pass around during well-defined situations. + */ +struct LightSong { + /** + * If this is not nullptr, then it denotes a prefix for the + * #uri. To build the full URI, join directory and uri with a + * slash. + */ + const char *directory; + + const char *uri; + + /** + * The "real" URI, the one to be used for opening the + * resource. If this attribute is empty, then #uri (and + * #directory) shall be used. + * + * This attribute is used for songs from the database which + * have a relative URI. + */ + std::string real_uri; + + /** + * Must not be nullptr. + */ + const Tag *tag; + + 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; + + gcc_pure + std::string GetURI() const { + if (directory == nullptr) + return std::string(uri); + + std::string result(directory); + result.push_back('/'); + result.append(uri); + return result; + } + + gcc_pure + double GetDuration() const; +}; + +#endif diff --git a/src/db/ProxyDatabasePlugin.cxx b/src/db/ProxyDatabasePlugin.cxx deleted file mode 100644 index 00b5d445f..000000000 --- a/src/db/ProxyDatabasePlugin.cxx +++ /dev/null @@ -1,656 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "SongFilter.hxx" -#include "Compiler.h" -#include "ConfigData.hxx" -#include "tag/TagBuilder.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "protocol/Ack.hxx" - -#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; - - /* this is mutable because GetStats() must be "const" */ - mutable time_t update_stamp; - -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, - TagType tag_type, - VisitString visit_string, - Error &error) const override; - - virtual bool GetStats(const DatabaseSelection &selection, - DatabaseStats &stats, - Error &error) const override; - - virtual time_t GetUpdateStamp() const override { - return update_stamp; - } - -private: - bool Configure(const config_param ¶m, Error &error); - - bool Connect(Error &error); - bool CheckConnection(Error &error); - bool EnsureConnected(Error &error); -}; - -static constexpr Domain libmpdclient_domain("libmpdclient"); - -static constexpr struct { - TagType 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(TagType 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; - - if (code == MPD_ERROR_SERVER) { - /* libmpdclient's "enum mpd_server_error" is the same - as our "enum ack" */ - const auto server_error = - mpd_connection_get_server_error(connection); - error.Set(ack_domain, (int)server_error, - mpd_connection_get_error_message(connection)); - } else { - error.Set(libmpdclient_domain, (int)code, - mpd_connection_get_error_message(connection)); - } - - mpd_connection_clear_error(connection); - return false; -} - -static bool -SendConstraints(mpd_connection *connection, const SongFilter::Item &item) -{ - switch (item.GetTag()) { - mpd_tag_type tag; - -#if LIBMPDCLIENT_CHECK_VERSION(2,9,0) - case LOCATE_TAG_BASE_TYPE: - if (mpd_connection_cmp_server_version(connection, 0, 18, 0) < 0) - /* requires MPD 0.18 */ - return true; - - return mpd_search_add_base_constraint(connection, - MPD_OPERATOR_DEFAULT, - item.GetValue().c_str()); -#endif - - case LOCATE_TAG_FILE_TYPE: - return mpd_search_add_uri_constraint(connection, - MPD_OPERATOR_DEFAULT, - item.GetValue().c_str()); - - case LOCATE_TAG_ANY_TYPE: - return mpd_search_add_any_tag_constraint(connection, - MPD_OPERATOR_DEFAULT, - item.GetValue().c_str()); - - default: - tag = Convert(TagType(item.GetTag())); - if (tag == MPD_TAG_COUNT) - return true; - - return mpd_search_add_tag_constraint(connection, - MPD_OPERATOR_DEFAULT, - tag, - item.GetValue().c_str()); - } -} - -static bool -SendConstraints(mpd_connection *connection, const SongFilter &filter) -{ - for (const auto &i : filter.GetItems()) - if (!SendConstraints(connection, i)) - return false; - - return true; -} - -static bool -SendConstraints(mpd_connection *connection, const DatabaseSelection &selection) -{ -#if LIBMPDCLIENT_CHECK_VERSION(2,9,0) - if (!selection.uri.empty() && - mpd_connection_cmp_server_version(connection, 0, 18, 0) >= 0) { - /* requires MPD 0.18 */ - if (!mpd_search_add_base_constraint(connection, - MPD_OPERATOR_DEFAULT, - selection.uri.c_str())) - return false; - } -#endif - - if (selection.filter != nullptr && - !SendConstraints(connection, *selection.filter)) - return false; - - return true; -} - -Database * -ProxyDatabase::Create(const config_param ¶m, Error &error) -{ - ProxyDatabase *db = new ProxyDatabase(); - if (!db->Configure(param, error)) { - delete db; - db = nullptr; - } - - 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) -{ - if (!Connect(error)) - return false; - - root = Directory::NewRoot(); - update_stamp = 0; - - return true; -} - -void -ProxyDatabase::Close() -{ - root->Free(); - - if (connection != nullptr) - mpd_connection_free(connection); -} - -bool -ProxyDatabase::Connect(Error &error) -{ - const char *_host = host.empty() ? nullptr : host.c_str(); - connection = mpd_connection_new(_host, port, 0); - if (connection == nullptr) { - error.Set(libmpdclient_domain, (int)MPD_ERROR_OOM, - "Out of memory"); - return false; - } - - if (!CheckError(connection, error)) { - if (connection != nullptr) { - mpd_connection_free(connection); - connection = nullptr; - } - - return false; - } - - return true; -} - -bool -ProxyDatabase::CheckConnection(Error &error) -{ - assert(connection != nullptr); - - if (!mpd_connection_clear_error(connection)) { - mpd_connection_free(connection); - return Connect(error); - } - - return true; -} - -bool -ProxyDatabase::EnsureConnected(Error &error) -{ - return connection != nullptr - ? CheckConnection(error) - : Connect(error); -} - -static Song * -Convert(const struct mpd_song *song); - -Song * -ProxyDatabase::GetSong(const char *uri, Error &error) const -{ - // TODO: eliminate the const_cast - if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error)) - return nullptr; - - 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; - if (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, const SongFilter *filter, - VisitDirectory visit_directory, VisitSong visit_song, - VisitPlaylist visit_playlist, Error &error); - -static bool -Visit(struct mpd_connection *connection, - bool recursive, const SongFilter *filter, - 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, filter, - visit_directory, visit_song, visit_playlist, error)) - return false; - - return true; -} - -static void -Copy(TagBuilder &tag, TagType 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 == nullptr) - 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; -} - -gcc_pure -static bool -Match(const SongFilter *filter, const Song &song) -{ - return filter == nullptr || filter->Match(song); -} - -static bool -Visit(const SongFilter *filter, - const struct mpd_song *song, - VisitSong visit_song, Error &error) -{ - if (!visit_song) - return true; - - Song *s = Convert(song); - bool success = !Match(filter, *s) || 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)) != nullptr) - entities.push_back(ProxyEntity(entity)); - - mpd_response_finish(connection); - return entities; -} - -static bool -Visit(struct mpd_connection *connection, const char *uri, - bool recursive, const SongFilter *filter, - 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, filter, - mpd_entity_get_directory(entity), - visit_directory, visit_song, visit_playlist, - error)) - return false; - break; - - case MPD_ENTITY_TYPE_SONG: - if (!Visit(filter, - 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); -} - -static bool -SearchSongs(struct mpd_connection *connection, - const DatabaseSelection &selection, - VisitSong visit_song, - Error &error) -{ - assert(selection.recursive); - assert(visit_song); - - const bool exact = selection.filter == nullptr || - !selection.filter->HasFoldCase(); - - if (!mpd_search_db_songs(connection, exact) || - !SendConstraints(connection, selection) || - !mpd_search_commit(connection)) - return CheckError(connection, error); - - bool result = true; - struct mpd_song *song; - while (result && (song = mpd_recv_song(connection)) != nullptr) { - Song *song2 = Convert(song); - mpd_song_free(song); - - result = !Match(selection.filter, *song2) || - visit_song(*song2, error); - song2->Free(); - } - - mpd_response_finish(connection); - return result && CheckError(connection, error); -} - -bool -ProxyDatabase::Visit(const DatabaseSelection &selection, - VisitDirectory visit_directory, - VisitSong visit_song, - VisitPlaylist visit_playlist, - Error &error) const -{ - // TODO: eliminate the const_cast - if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error)) - return nullptr; - - if (!visit_directory && !visit_playlist && selection.recursive) - /* this optimized code path can only be used under - certain conditions */ - return ::SearchSongs(connection, selection, visit_song, error); - - /* fall back to recursive walk (slow!) */ - return ::Visit(connection, selection.uri.c_str(), - selection.recursive, selection.filter, - visit_directory, visit_song, visit_playlist, - error); -} - -bool -ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection, - TagType tag_type, - VisitString visit_string, - Error &error) const -{ - // TODO: eliminate the const_cast - if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error)) - return nullptr; - - 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); - - if (!SendConstraints(connection, selection)) - return CheckError(connection, error); - - 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; - - // TODO: eliminate the const_cast - if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error)) - return nullptr; - - struct mpd_stats *stats2 = - mpd_run_stats(connection); - if (stats2 == nullptr) - return CheckError(connection, error); - - update_stamp = (time_t)mpd_stats_get_db_update_time(stats2); - - 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 deleted file mode 100644 index 576c01c69..000000000 --- a/src/db/ProxyDatabasePlugin.hxx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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/Registry.cxx b/src/db/Registry.cxx new file mode 100644 index 000000000..295d3cf2a --- /dev/null +++ b/src/db/Registry.cxx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Registry.hxx" +#include "plugins/SimpleDatabasePlugin.hxx" +#include "plugins/ProxyDatabasePlugin.hxx" +#include "plugins/UpnpDatabasePlugin.hxx" + +#include <string.h> + +const DatabasePlugin *const database_plugins[] = { + &simple_db_plugin, +#ifdef HAVE_LIBMPDCLIENT + &proxy_db_plugin, +#endif +#ifdef HAVE_LIBUPNP + &upnp_db_plugin, +#endif + nullptr +}; + +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/db/Registry.hxx b/src/db/Registry.hxx new file mode 100644 index 000000000..050842e21 --- /dev/null +++ b/src/db/Registry.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.h" + +struct DatabasePlugin; + +/** + * nullptr 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/db/Selection.cxx b/src/db/Selection.cxx new file mode 100644 index 000000000..96382eed7 --- /dev/null +++ b/src/db/Selection.cxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "Selection.hxx" +#include "SongFilter.hxx" + +DatabaseSelection::DatabaseSelection(const char *_uri, bool _recursive, + const SongFilter *_filter) + :uri(_uri), recursive(_recursive), filter(_filter) +{ + /* optimization: if the caller didn't specify a base URI, pick + the one from SongFilter */ + if (uri.empty() && filter != nullptr) + uri = filter->GetBase(); +} + +bool +DatabaseSelection::Match(const LightSong &song) const +{ + return filter == nullptr || filter->Match(song); +} diff --git a/src/db/Selection.hxx b/src/db/Selection.hxx new file mode 100644 index 000000000..a39ce7afe --- /dev/null +++ b/src/db/Selection.hxx @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.h" + +#include <string> + +class SongFilter; +struct LightSong; + +struct DatabaseSelection { + /** + * The base URI of the search (UTF-8). Must not begin or end + * with a slash. An empty string searches the whole database. + */ + std::string uri; + + /** + * Recursively search all sub directories? + */ + bool recursive; + + const SongFilter *filter; + + DatabaseSelection(const char *_uri, bool _recursive, + const SongFilter *_filter=nullptr); + + gcc_pure + bool Match(const LightSong &song) const; +}; + +#endif diff --git a/src/db/SimpleDatabasePlugin.cxx b/src/db/SimpleDatabasePlugin.cxx deleted file mode 100644 index e7ea7a62d..000000000 --- a/src/db/SimpleDatabasePlugin.cxx +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 <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 = nullptr; - } - - 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 auto 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 != nullptr); - - 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 != nullptr); - assert(borrowed_song_count == 0); - - root->Free(); -} - -Song * -SimpleDatabase::GetSong(const char *uri, Error &error) const -{ - assert(root != nullptr); - - db_lock(); - Song *song = root->LookupSong(uri); - db_unlock(); - if (song == nullptr) - 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 != nullptr); - assert(uri != nullptr); - - 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.c_str()); - if (directory == nullptr) { - if (visit_song) { - Song *song = root->LookupSong(selection.uri.c_str()); - 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, - TagType 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 deleted file mode 100644 index dfe981dd8..000000000 --- a/src/db/SimpleDatabasePlugin.hxx +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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/AllocatedPath.hxx" -#include "Compiler.h" - -#include <cassert> - -struct Directory; - -class SimpleDatabase : public Database { - AllocatedPath path; - std::string path_utf8; - - Directory *root; - - time_t mtime; - -#ifndef NDEBUG - unsigned borrowed_song_count; -#endif - - SimpleDatabase() - :path(AllocatedPath::Null()) {} - -public: - gcc_pure - Directory *GetRoot() { - assert(root != NULL); - - return root; - } - - bool Save(Error &error); - - 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, - TagType tag_type, - VisitString visit_string, - Error &error) const override; - - virtual bool GetStats(const DatabaseSelection &selection, - DatabaseStats &stats, - Error &error) const override; - - virtual time_t GetUpdateStamp() const override { - return mtime; - } - -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/Song.cxx b/src/db/Song.cxx new file mode 100644 index 000000000..15924a40a --- /dev/null +++ b/src/db/Song.cxx @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "util/VarSize.hxx" +#include "DetachedSong.hxx" +#include "LightSong.hxx" + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +inline Song::Song(const char *_uri, size_t uri_length, Directory &_parent) + :parent(&_parent), mtime(0), start_ms(0), end_ms(0) +{ + memcpy(uri, _uri, uri_length + 1); +} + +inline Song::~Song() +{ +} + +static Song * +song_alloc(const char *uri, Directory &parent) +{ + size_t uri_length; + + assert(uri); + uri_length = strlen(uri); + assert(uri_length); + + return NewVarSize<Song>(sizeof(Song::uri), + uri_length + 1, + uri, uri_length, parent); +} + +Song * +Song::NewFrom(DetachedSong &&other, Directory &parent) +{ + Song *song = song_alloc(other.GetURI(), parent); + song->tag = std::move(other.WritableTag()); + song->mtime = other.GetLastModified(); + song->start_ms = other.GetStartMS(); + song->end_ms = other.GetEndMS(); + return song; +} + +Song * +Song::NewFile(const char *path, Directory &parent) +{ + return song_alloc(path, parent); +} + +void +Song::Free() +{ + DeleteVarSize(this); +} + +std::string +Song::GetURI() const +{ + assert(*uri); + + if (parent->IsRoot()) + return std::string(uri); + else { + const char *path = parent->GetPath(); + + std::string result; + result.reserve(strlen(path) + 1 + strlen(uri)); + result.assign(path); + result.push_back('/'); + result.append(uri); + return result; + } +} + +LightSong +Song::Export() const +{ + LightSong dest; + dest.directory = parent->IsRoot() + ? nullptr : parent->GetPath(); + dest.uri = uri; + dest.tag = &tag; + dest.mtime = mtime; + dest.start_ms = start_ms; + dest.end_ms = end_ms; + return dest; +} diff --git a/src/db/Song.hxx b/src/db/Song.hxx new file mode 100644 index 000000000..0b94fe6d0 --- /dev/null +++ b/src/db/Song.hxx @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "tag/Tag.hxx" +#include "Compiler.h" + +#include <string> + +#include <assert.h> +#include <time.h> + +struct LightSong; +struct Directory; +class DetachedSong; + +/** + * A song file inside the configured music directory. + */ +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; + + /** + * The #Directory that contains this song. May be nullptr if + * the current database plugin does not manage the parent + * directory this way. + */ + Directory *const 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; + + /** + * The file name. If #parent is nullptr, then this is the URI + * relative to the music directory. + */ + char uri[sizeof(int)]; + + Song(const char *_uri, size_t uri_length, Directory &parent); + ~Song(); + + gcc_malloc + static Song *NewFrom(DetachedSong &&other, Directory &parent); + + /** 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); + + void Free(); + + bool UpdateFile(); + bool UpdateFileInArchive(); + + /** + * Returns the URI of the song in UTF-8 encoding, including its + * location within the music directory. + */ + gcc_pure + std::string GetURI() const; + + gcc_pure + LightSong Export() const; +}; + +#endif diff --git a/src/db/SongSort.cxx b/src/db/SongSort.cxx new file mode 100644 index 000000000..dcea033b6 --- /dev/null +++ b/src/db/SongSort.cxx @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "tag/Tag.hxx" + +extern "C" { +#include "util/list_sort.h" +} + +#include <glib.h> + +#include <stdlib.h> + +static int +compare_utf8_string(const char *a, const char *b) +{ + if (a == nullptr) + return b == nullptr ? 0 : -1; + + if (b == nullptr) + return 1; + + return g_utf8_collate(a, b); +} + +/** + * Compare two string tag values, ignoring case. Either one may be + * nullptr. + */ +static int +compare_string_tag_item(const Tag &a, const Tag &b, + TagType type) +{ + return compare_utf8_string(a.GetValue(type), + b.GetValue(type)); +} + +/** + * Compare two tag values which should contain an integer value + * (e.g. disc or track number). Either one may be nullptr. + */ +static int +compare_number_string(const char *a, const char *b) +{ + long ai = a == nullptr ? 0 : strtol(a, nullptr, 10); + long bi = b == nullptr ? 0 : strtol(b, nullptr, 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, TagType type) +{ + return compare_number_string(a.GetValue(type), + b.GetValue(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(nullptr, songs, song_cmp); +} diff --git a/src/db/SongSort.hxx b/src/db/SongSort.hxx new file mode 100644 index 000000000..28b903532 --- /dev/null +++ b/src/db/SongSort.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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(list_head *songs); + +#endif diff --git a/src/db/Visitor.hxx b/src/db/Visitor.hxx new file mode 100644 index 000000000..0ec29bf49 --- /dev/null +++ b/src/db/Visitor.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 LightDirectory; +struct LightSong; +struct PlaylistInfo; +class Error; + +typedef std::function<bool(const LightDirectory &, Error &)> VisitDirectory; +typedef std::function<bool(const LightSong &, Error &)> VisitSong; +typedef std::function<bool(const PlaylistInfo &, const LightDirectory &, + Error &)> VisitPlaylist; + +typedef std::function<bool(const char *, Error &)> VisitString; + +#endif diff --git a/src/db/plugins/LazyDatabase.cxx b/src/db/plugins/LazyDatabase.cxx new file mode 100644 index 000000000..6a01ffb82 --- /dev/null +++ b/src/db/plugins/LazyDatabase.cxx @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "LazyDatabase.hxx" + +#include <assert.h> + +LazyDatabase::~LazyDatabase() +{ + assert(!open); + + delete db; +} + +bool +LazyDatabase::EnsureOpen(Error &error) const +{ + if (open) + return true; + + if (!db->Open(error)) + return false; + + open = true; + return true; +} + +void +LazyDatabase::Close() +{ + if (open) { + open = false; + db->Close(); + } +} + +const LightSong * +LazyDatabase::GetSong(const char *uri, Error &error) const +{ + return EnsureOpen(error) + ? db->GetSong(uri, error) + : nullptr; +} + +void +LazyDatabase::ReturnSong(const LightSong *song) const +{ + assert(open); + + db->ReturnSong(song); +} + +bool +LazyDatabase::Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const +{ + return EnsureOpen(error) && + db->Visit(selection, visit_directory, visit_song, + visit_playlist, error); +} + +bool +LazyDatabase::VisitUniqueTags(const DatabaseSelection &selection, + TagType tag_type, + VisitString visit_string, + Error &error) const +{ + return EnsureOpen(error) && + db->VisitUniqueTags(selection, tag_type, visit_string, error); +} + +bool +LazyDatabase::GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, Error &error) const +{ + return EnsureOpen(error) && db->GetStats(selection, stats, error); +} + +time_t +LazyDatabase::GetUpdateStamp() const +{ + return open ? db->GetUpdateStamp() : 0; +} diff --git a/src/db/plugins/LazyDatabase.hxx b/src/db/plugins/LazyDatabase.hxx new file mode 100644 index 000000000..336b8558f --- /dev/null +++ b/src/db/plugins/LazyDatabase.hxx @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_DATABASE_PLUGIN_HXX +#define MPD_LAZY_DATABASE_PLUGIN_HXX + +#include "db/DatabasePlugin.hxx" + +/** + * A wrapper for a #Database object that gets opened on the first + * database access. This works around daemonization problems with + * some plugins. + */ +class LazyDatabase final : public Database { + Database *const db; + + mutable bool open; + +public: + gcc_nonnull_all + LazyDatabase(Database *_db) + :db(_db), open(false) {} + + virtual ~LazyDatabase(); + + virtual void Close() override; + + virtual const LightSong *GetSong(const char *uri_utf8, + Error &error) const override; + virtual void ReturnSong(const LightSong *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, + TagType tag_type, + VisitString visit_string, + Error &error) const override; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + Error &error) const override; + + virtual time_t GetUpdateStamp() const override; + +private: + bool EnsureOpen(Error &error) const; +}; + +#endif diff --git a/src/db/plugins/ProxyDatabasePlugin.cxx b/src/db/plugins/ProxyDatabasePlugin.cxx new file mode 100644 index 000000000..daa963c7d --- /dev/null +++ b/src/db/plugins/ProxyDatabasePlugin.cxx @@ -0,0 +1,782 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "db/DatabasePlugin.hxx" +#include "db/DatabaseListener.hxx" +#include "db/Selection.hxx" +#include "db/DatabaseError.hxx" +#include "PlaylistInfo.hxx" +#include "db/LightDirectory.hxx" +#include "db/LightSong.hxx" +#include "SongFilter.hxx" +#include "Compiler.h" +#include "config/ConfigData.hxx" +#include "tag/TagBuilder.hxx" +#include "tag/Tag.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "protocol/Ack.hxx" +#include "Main.hxx" +#include "event/SocketMonitor.hxx" +#include "event/IdleMonitor.hxx" +#include "Log.hxx" + +#include <mpd/client.h> +#include <mpd/async.h> + +#include <cassert> +#include <string> +#include <list> + +class ProxySong : public LightSong { + Tag tag2; + +public: + explicit ProxySong(const mpd_song *song); +}; + +class AllocatedProxySong : public ProxySong { + mpd_song *const song; + +public: + explicit AllocatedProxySong(mpd_song *_song) + :ProxySong(_song), song(_song) {} + + ~AllocatedProxySong() { + mpd_song_free(song); + } +}; + +class ProxyDatabase final : public Database, SocketMonitor, IdleMonitor { + DatabaseListener &listener; + + std::string host; + unsigned port; + + struct mpd_connection *connection; + + /* this is mutable because GetStats() must be "const" */ + mutable time_t update_stamp; + + /** + * The libmpdclient idle mask that was removed from the other + * MPD. This will be handled by the next OnIdle() call. + */ + unsigned idle_received; + + /** + * Is the #connection currently "idle"? That is, did we send + * the "idle" command to it? + */ + bool is_idle; + +public: + ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener) + :SocketMonitor(_loop), IdleMonitor(_loop), + listener(_listener) {} + + static Database *Create(EventLoop &loop, DatabaseListener &listener, + const config_param ¶m, + Error &error); + + virtual bool Open(Error &error) override; + virtual void Close() override; + virtual const LightSong *GetSong(const char *uri_utf8, + Error &error) const override; + virtual void ReturnSong(const LightSong *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, + TagType tag_type, + VisitString visit_string, + Error &error) const override; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + Error &error) const override; + + virtual time_t GetUpdateStamp() const override { + return update_stamp; + } + +private: + bool Configure(const config_param ¶m, Error &error); + + bool Connect(Error &error); + bool CheckConnection(Error &error); + bool EnsureConnected(Error &error); + + void Disconnect(); + + /* virtual methods from SocketMonitor */ + virtual bool OnSocketReady(unsigned flags) override; + + /* virtual methods from IdleMonitor */ + virtual void OnIdle() override; +}; + +static constexpr Domain libmpdclient_domain("libmpdclient"); + +static constexpr struct { + TagType 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 } +}; + +static void +Copy(TagBuilder &tag, TagType 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 == nullptr) + break; + + tag.AddItem(d_tag, value); + } +} + +ProxySong::ProxySong(const mpd_song *song) +{ + directory = nullptr; + uri = mpd_song_get_uri(song); + tag = &tag2; + mtime = mpd_song_get_last_modified(song); + start_ms = mpd_song_get_start(song) * 1000; + end_ms = mpd_song_get_end(song) * 1000; + + TagBuilder tag_builder; + tag_builder.SetTime(mpd_song_get_duration(song)); + + for (const auto *i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i) + Copy(tag_builder, i->d, song, i->s); + + tag_builder.Commit(tag2); +} + +gcc_const +static enum mpd_tag_type +Convert(TagType 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; + + if (code == MPD_ERROR_SERVER) { + /* libmpdclient's "enum mpd_server_error" is the same + as our "enum ack" */ + const auto server_error = + mpd_connection_get_server_error(connection); + error.Set(ack_domain, (int)server_error, + mpd_connection_get_error_message(connection)); + } else { + error.Set(libmpdclient_domain, (int)code, + mpd_connection_get_error_message(connection)); + } + + mpd_connection_clear_error(connection); + return false; +} + +static bool +SendConstraints(mpd_connection *connection, const SongFilter::Item &item) +{ + switch (item.GetTag()) { + mpd_tag_type tag; + +#if LIBMPDCLIENT_CHECK_VERSION(2,9,0) + case LOCATE_TAG_BASE_TYPE: + if (mpd_connection_cmp_server_version(connection, 0, 18, 0) < 0) + /* requires MPD 0.18 */ + return true; + + return mpd_search_add_base_constraint(connection, + MPD_OPERATOR_DEFAULT, + item.GetValue().c_str()); +#endif + + case LOCATE_TAG_FILE_TYPE: + return mpd_search_add_uri_constraint(connection, + MPD_OPERATOR_DEFAULT, + item.GetValue().c_str()); + + case LOCATE_TAG_ANY_TYPE: + return mpd_search_add_any_tag_constraint(connection, + MPD_OPERATOR_DEFAULT, + item.GetValue().c_str()); + + default: + tag = Convert(TagType(item.GetTag())); + if (tag == MPD_TAG_COUNT) + return true; + + return mpd_search_add_tag_constraint(connection, + MPD_OPERATOR_DEFAULT, + tag, + item.GetValue().c_str()); + } +} + +static bool +SendConstraints(mpd_connection *connection, const SongFilter &filter) +{ + for (const auto &i : filter.GetItems()) + if (!SendConstraints(connection, i)) + return false; + + return true; +} + +static bool +SendConstraints(mpd_connection *connection, const DatabaseSelection &selection) +{ +#if LIBMPDCLIENT_CHECK_VERSION(2,9,0) + if (!selection.uri.empty() && + mpd_connection_cmp_server_version(connection, 0, 18, 0) >= 0) { + /* requires MPD 0.18 */ + if (!mpd_search_add_base_constraint(connection, + MPD_OPERATOR_DEFAULT, + selection.uri.c_str())) + return false; + } +#endif + + if (selection.filter != nullptr && + !SendConstraints(connection, *selection.filter)) + return false; + + return true; +} + +Database * +ProxyDatabase::Create(EventLoop &loop, DatabaseListener &listener, + const config_param ¶m, Error &error) +{ + ProxyDatabase *db = new ProxyDatabase(loop, listener); + if (!db->Configure(param, error)) { + delete db; + db = nullptr; + } + + 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) +{ + if (!Connect(error)) + return false; + + update_stamp = 0; + + return true; +} + +void +ProxyDatabase::Close() +{ + if (connection != nullptr) + Disconnect(); +} + +bool +ProxyDatabase::Connect(Error &error) +{ + const char *_host = host.empty() ? nullptr : host.c_str(); + connection = mpd_connection_new(_host, port, 0); + if (connection == nullptr) { + error.Set(libmpdclient_domain, (int)MPD_ERROR_OOM, + "Out of memory"); + return false; + } + + idle_received = unsigned(-1); + is_idle = false; + + SocketMonitor::Open(mpd_async_get_fd(mpd_connection_get_async(connection))); + IdleMonitor::Schedule(); + + if (!CheckError(connection, error)) { + if (connection != nullptr) + Disconnect(); + + return false; + } + + return true; +} + +bool +ProxyDatabase::CheckConnection(Error &error) +{ + assert(connection != nullptr); + + if (!mpd_connection_clear_error(connection)) { + Disconnect(); + return Connect(error); + } + + if (is_idle) { + unsigned idle = mpd_run_noidle(connection); + if (idle == 0 && !CheckError(connection, error)) { + Disconnect(); + return false; + } + + idle_received |= idle; + is_idle = false; + IdleMonitor::Schedule(); + } + + return true; +} + +bool +ProxyDatabase::EnsureConnected(Error &error) +{ + return connection != nullptr + ? CheckConnection(error) + : Connect(error); +} + +void +ProxyDatabase::Disconnect() +{ + assert(connection != nullptr); + + IdleMonitor::Cancel(); + SocketMonitor::Steal(); + + mpd_connection_free(connection); + connection = nullptr; +} + +bool +ProxyDatabase::OnSocketReady(gcc_unused unsigned flags) +{ + assert(connection != nullptr); + + if (!is_idle) { + // TODO: can this happen? + IdleMonitor::Schedule(); + return false; + } + + unsigned idle = (unsigned)mpd_recv_idle(connection, false); + if (idle == 0) { + Error error; + if (!CheckError(connection, error)) { + LogError(error); + Disconnect(); + return false; + } + } + + /* let OnIdle() handle this */ + idle_received |= idle; + is_idle = false; + IdleMonitor::Schedule(); + return false; +} + +void +ProxyDatabase::OnIdle() +{ + assert(connection != nullptr); + + /* handle previous idle events */ + + if (idle_received & MPD_IDLE_DATABASE) + listener.OnDatabaseModified(); + + idle_received = 0; + + /* send a new idle command to the other MPD */ + + if (is_idle) + // TODO: can this happen? + return; + + if (!mpd_send_idle_mask(connection, MPD_IDLE_DATABASE)) { + Error error; + if (!CheckError(connection, error)) + LogError(error); + + SocketMonitor::Steal(); + mpd_connection_free(connection); + connection = nullptr; + return; + } + + is_idle = true; + SocketMonitor::ScheduleRead(); +} + +const LightSong * +ProxyDatabase::GetSong(const char *uri, Error &error) const +{ + // TODO: eliminate the const_cast + if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error)) + return nullptr; + + if (!mpd_send_list_meta(connection, uri)) { + CheckError(connection, error); + return nullptr; + } + + struct mpd_song *song = mpd_recv_song(connection); + if (!mpd_response_finish(connection) && + !CheckError(connection, error)) { + if (song != nullptr) + mpd_song_free(song); + return nullptr; + } + + if (song == nullptr) { + error.Format(db_domain, DB_NOT_FOUND, "No such song: %s", uri); + return nullptr; + } + + return new AllocatedProxySong(song); +} + +void +ProxyDatabase::ReturnSong(const LightSong *_song) const +{ + assert(_song != nullptr); + + AllocatedProxySong *song = (AllocatedProxySong *) + const_cast<LightSong *>(_song); + delete song; +} + +static bool +Visit(struct mpd_connection *connection, const char *uri, + bool recursive, const SongFilter *filter, + VisitDirectory visit_directory, VisitSong visit_song, + VisitPlaylist visit_playlist, Error &error); + +static bool +Visit(struct mpd_connection *connection, + bool recursive, const SongFilter *filter, + 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 LIBMPDCLIENT_CHECK_VERSION(2,9,0) + time_t mtime = mpd_directory_get_last_modified(directory); +#else + time_t mtime = 0; +#endif + + if (visit_directory && + !visit_directory(LightDirectory(path, mtime), error)) + return false; + + if (recursive && + !Visit(connection, path, recursive, filter, + visit_directory, visit_song, visit_playlist, error)) + return false; + + return true; +} + +gcc_pure +static bool +Match(const SongFilter *filter, const LightSong &song) +{ + return filter == nullptr || filter->Match(song); +} + +static bool +Visit(const SongFilter *filter, + const mpd_song *_song, + VisitSong visit_song, Error &error) +{ + if (!visit_song) + return true; + + const ProxySong song(_song); + return !Match(filter, song) || visit_song(song, error); +} + +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, LightDirectory::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)) != nullptr) + entities.push_back(ProxyEntity(entity)); + + mpd_response_finish(connection); + return entities; +} + +static bool +Visit(struct mpd_connection *connection, const char *uri, + bool recursive, const SongFilter *filter, + 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, filter, + mpd_entity_get_directory(entity), + visit_directory, visit_song, visit_playlist, + error)) + return false; + break; + + case MPD_ENTITY_TYPE_SONG: + if (!Visit(filter, + 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); +} + +static bool +SearchSongs(struct mpd_connection *connection, + const DatabaseSelection &selection, + VisitSong visit_song, + Error &error) +{ + assert(selection.recursive); + assert(visit_song); + + const bool exact = selection.filter == nullptr || + !selection.filter->HasFoldCase(); + + if (!mpd_search_db_songs(connection, exact) || + !SendConstraints(connection, selection) || + !mpd_search_commit(connection)) + return CheckError(connection, error); + + bool result = true; + struct mpd_song *song; + while (result && (song = mpd_recv_song(connection)) != nullptr) { + AllocatedProxySong song2(song); + + result = !Match(selection.filter, song2) || + visit_song(song2, error); + } + + mpd_response_finish(connection); + return result && CheckError(connection, error); +} + +bool +ProxyDatabase::Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const +{ + // TODO: eliminate the const_cast + if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error)) + return nullptr; + + if (!visit_directory && !visit_playlist && selection.recursive) + /* this optimized code path can only be used under + certain conditions */ + return ::SearchSongs(connection, selection, visit_song, error); + + /* fall back to recursive walk (slow!) */ + return ::Visit(connection, selection.uri.c_str(), + selection.recursive, selection.filter, + visit_directory, visit_song, visit_playlist, + error); +} + +bool +ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection, + TagType tag_type, + VisitString visit_string, + Error &error) const +{ + // TODO: eliminate the const_cast + if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error)) + return nullptr; + + 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); + + if (!SendConstraints(connection, selection)) + return CheckError(connection, error); + + 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; + + // TODO: eliminate the const_cast + if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error)) + return nullptr; + + struct mpd_stats *stats2 = + mpd_run_stats(connection); + if (stats2 == nullptr) + return CheckError(connection, error); + + update_stamp = (time_t)mpd_stats_get_db_update_time(stats2); + + 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/plugins/ProxyDatabasePlugin.hxx b/src/db/plugins/ProxyDatabasePlugin.hxx new file mode 100644 index 000000000..699d374b5 --- /dev/null +++ b/src/db/plugins/ProxyDatabasePlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/SimpleDatabasePlugin.cxx b/src/db/plugins/SimpleDatabasePlugin.cxx new file mode 100644 index 000000000..55e08b6d7 --- /dev/null +++ b/src/db/plugins/SimpleDatabasePlugin.cxx @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "db/Selection.hxx" +#include "db/Helpers.hxx" +#include "db/LightDirectory.hxx" +#include "db/Directory.hxx" +#include "db/Song.hxx" +#include "SongFilter.hxx" +#include "db/DatabaseSave.hxx" +#include "db/DatabaseLock.hxx" +#include "db/DatabaseError.hxx" +#include "fs/TextFile.hxx" +#include "config/ConfigData.hxx" +#include "fs/FileSystem.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <errno.h> + +static constexpr Domain simple_db_domain("simple_db"); + +Database * +SimpleDatabase::Create(gcc_unused EventLoop &loop, + gcc_unused DatabaseListener &listener, + const config_param ¶m, Error &error) +{ + SimpleDatabase *db = new SimpleDatabase(); + if (!db->Configure(param, error)) { + delete db; + db = nullptr; + } + + 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)) { + /* 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 auto 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; + } + +#ifndef WIN32 + /* 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; + } +#endif + 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; + } + +#ifndef WIN32 + /* 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; + } +#endif + + return true; +} + +bool +SimpleDatabase::Load(Error &error) +{ + assert(!path.IsNull()); + assert(root != nullptr); + + 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)) { + delete root; + + LogError(error); + error.Clear(); + + if (!Check(error)) + return false; + + root = Directory::NewRoot(); + } + + return true; +} + +void +SimpleDatabase::Close() +{ + assert(root != nullptr); + assert(borrowed_song_count == 0); + + delete root; +} + +const LightSong * +SimpleDatabase::GetSong(const char *uri, Error &error) const +{ + assert(root != nullptr); + assert(borrowed_song_count == 0); + + db_lock(); + const Song *song = root->LookupSong(uri); + db_unlock(); + if (song == nullptr) { + error.Format(db_domain, DB_NOT_FOUND, + "No such song: %s", uri); + return nullptr; + } + + light_song = song->Export(); + +#ifndef NDEBUG + ++borrowed_song_count; +#endif + + return &light_song; +} + +void +SimpleDatabase::ReturnSong(gcc_unused const LightSong *song) const +{ + assert(song == &light_song); + +#ifndef NDEBUG + assert(borrowed_song_count > 0); + --borrowed_song_count; +#endif +} + +gcc_pure +const Directory * +SimpleDatabase::LookupDirectory(const char *uri) const +{ + assert(root != nullptr); + assert(uri != nullptr); + + 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.c_str()); + if (directory == nullptr) { + if (visit_song) { + Song *song = root->LookupSong(selection.uri.c_str()); + if (song != nullptr) { + const LightSong song2 = song->Export(); + return !selection.Match(song2) || + visit_song(song2, error); + } + } + + error.Set(db_domain, DB_NOT_FOUND, "No such directory"); + return false; + } + + if (selection.recursive && visit_directory && + !visit_directory(directory->Export(), error)) + return false; + + return directory->Walk(selection.recursive, selection.filter, + visit_directory, visit_song, visit_playlist, + error); +} + +bool +SimpleDatabase::VisitUniqueTags(const DatabaseSelection &selection, + TagType 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/plugins/SimpleDatabasePlugin.hxx b/src/db/plugins/SimpleDatabasePlugin.hxx new file mode 100644 index 000000000..137a60884 --- /dev/null +++ b/src/db/plugins/SimpleDatabasePlugin.hxx @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "db/DatabasePlugin.hxx" +#include "fs/AllocatedPath.hxx" +#include "db/LightSong.hxx" +#include "Compiler.h" + +#include <cassert> + +struct Directory; + +class SimpleDatabase : public Database { + AllocatedPath path; + std::string path_utf8; + + Directory *root; + + time_t mtime; + + /** + * A buffer for GetSong(). + */ + mutable LightSong light_song; + +#ifndef NDEBUG + mutable unsigned borrowed_song_count; +#endif + + SimpleDatabase() + :path(AllocatedPath::Null()) {} + +public: + gcc_pure + Directory *GetRoot() { + assert(root != NULL); + + return root; + } + + bool Save(Error &error); + + static Database *Create(EventLoop &loop, DatabaseListener &listener, + const config_param ¶m, + Error &error); + + virtual bool Open(Error &error) override; + virtual void Close() override; + + virtual const LightSong *GetSong(const char *uri_utf8, + Error &error) const override; + virtual void ReturnSong(const LightSong *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, + TagType tag_type, + VisitString visit_string, + Error &error) const override; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + Error &error) const override; + + virtual time_t GetUpdateStamp() const override { + return mtime; + } + +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/plugins/UpnpDatabasePlugin.cxx b/src/db/plugins/UpnpDatabasePlugin.cxx new file mode 100644 index 000000000..c921d5460 --- /dev/null +++ b/src/db/plugins/UpnpDatabasePlugin.cxx @@ -0,0 +1,776 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "UpnpDatabasePlugin.hxx" +#include "lib/upnp/Domain.hxx" +#include "lib/upnp/ClientInit.hxx" +#include "lib/upnp/Discovery.hxx" +#include "lib/upnp/ContentDirectoryService.hxx" +#include "lib/upnp/Util.hxx" +#include "upnp/Directory.hxx" +#include "upnp/Tags.hxx" +#include "db/DatabasePlugin.hxx" +#include "db/Selection.hxx" +#include "db/DatabaseError.hxx" +#include "db/LightDirectory.hxx" +#include "db/LightSong.hxx" +#include "config/ConfigData.hxx" +#include "tag/TagBuilder.hxx" +#include "tag/TagTable.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "fs/Traits.hxx" +#include "Log.hxx" +#include "SongFilter.hxx" + +#include <string> +#include <vector> +#include <set> + +#include <assert.h> +#include <string.h> + +static const char *const rootid = "0"; + +class UpnpSong : public LightSong { + std::string uri2, real_uri2; + + Tag tag2; + +public: + UpnpSong(UPnPDirObject &&object, std::string &&_uri) + :uri2(std::move(_uri)), + real_uri2(std::move(object.url)), + tag2(std::move(object.tag)) { + directory = nullptr; + uri = uri2.c_str(); + real_uri = real_uri2.c_str(); + tag = &tag2; + mtime = 0; + start_ms = end_ms = 0; + } +}; + +class UpnpDatabase : public Database { + UpnpClient_Handle handle; + UPnPDeviceDirectory *discovery; + +public: + static Database *Create(EventLoop &loop, DatabaseListener &listener, + const config_param ¶m, + Error &error); + + virtual bool Open(Error &error) override; + virtual void Close() override; + virtual const LightSong *GetSong(const char *uri_utf8, + Error &error) const override; + virtual void ReturnSong(const LightSong *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, + TagType tag_type, + VisitString visit_string, + Error &error) const override; + + virtual bool GetStats(const DatabaseSelection &selection, + DatabaseStats &stats, + Error &error) const override; + virtual time_t GetUpdateStamp() const {return 0;} + +protected: + bool Configure(const config_param ¶m, Error &error); + +private: + bool VisitServer(const ContentDirectoryService &server, + const std::list<std::string> &vpath, + const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const; + + /** + * Run an UPnP search according to MPD parameters, and + * visit_song the results. + */ + bool SearchSongs(const ContentDirectoryService &server, + const char *objid, + const DatabaseSelection &selection, + VisitSong visit_song, + Error &error) const; + + bool SearchSongs(const ContentDirectoryService &server, + const char *objid, + const DatabaseSelection &selection, + UPnPDirContent& dirbuf, + Error &error) const; + + bool Namei(const ContentDirectoryService &server, + const std::list<std::string> &vpath, + UPnPDirObject &dirent, + Error &error) const; + + /** + * Take server and objid, return metadata. + */ + bool ReadNode(const ContentDirectoryService &server, + const char *objid, UPnPDirObject& dirent, + Error &error) const; + + /** + * Get the path for an object Id. This works much like pwd, + * except easier cause our inodes have a parent id. Not used + * any more actually (see comments in SearchSongs). + */ + bool BuildPath(const ContentDirectoryService &server, + const UPnPDirObject& dirent, std::string &idpath, + Error &error) const; +}; + +Database * +UpnpDatabase::Create(gcc_unused EventLoop &loop, + gcc_unused DatabaseListener &listener, + const config_param ¶m, Error &error) +{ + UpnpDatabase *db = new UpnpDatabase(); + if (!db->Configure(param, error)) { + delete db; + return nullptr; + } + + /* libupnp loses its ability to receive multicast messages + apparently due to daemonization; using the LazyDatabase + wrapper works around this problem */ + return db; +} + +inline bool +UpnpDatabase::Configure(const config_param &, Error &) +{ + return true; +} + +bool +UpnpDatabase::Open(Error &error) +{ + if (!UpnpClientGlobalInit(handle, error)) + return false; + + discovery = new UPnPDeviceDirectory(handle); + if (!discovery->Start(error)) { + delete discovery; + UpnpClientGlobalFinish(); + return false; + } + + return true; +} + +void +UpnpDatabase::Close() +{ + delete discovery; + UpnpClientGlobalFinish(); +} + +void +UpnpDatabase::ReturnSong(const LightSong *_song) const +{ + assert(_song != nullptr); + + UpnpSong *song = (UpnpSong *)const_cast<LightSong *>(_song); + delete song; +} + +// Get song info by path. We can receive either the id path, or the titles +// one +const LightSong * +UpnpDatabase::GetSong(const char *uri, Error &error) const +{ + auto vpath = stringToTokens(uri, "/", true); + if (vpath.size() < 2) { + error.Format(db_domain, DB_NOT_FOUND, "No such song: %s", uri); + return nullptr; + } + + ContentDirectoryService server; + if (!discovery->getServer(vpath.front().c_str(), server, error)) + return nullptr; + + vpath.pop_front(); + + UPnPDirObject dirent; + if (vpath.front() != rootid) { + if (!Namei(server, vpath, dirent, error)) + return nullptr; + } else { + if (!ReadNode(server, vpath.back().c_str(), dirent, + error)) + return nullptr; + } + + return new UpnpSong(std::move(dirent), uri); +} + +/** + * Double-quote a string, adding internal backslash escaping. + */ +static void +dquote(std::string &out, const char *in) +{ + out.push_back('"'); + + for (; *in != 0; ++in) { + switch(*in) { + case '\\': + case '"': + out.push_back('\\'); + break; + } + + out.push_back(*in); + } + + out.push_back('"'); +} + +// Run an UPnP search, according to MPD parameters. Return results as +// UPnP items +bool +UpnpDatabase::SearchSongs(const ContentDirectoryService &server, + const char *objid, + const DatabaseSelection &selection, + UPnPDirContent &dirbuf, + Error &error) const +{ + const SongFilter *filter = selection.filter; + if (selection.filter == nullptr) + return true; + + std::list<std::string> searchcaps; + if (!server.getSearchCapabilities(handle, searchcaps, error)) + return false; + + if (searchcaps.empty()) + return true; + + std::string cond; + for (const auto &item : filter->GetItems()) { + switch (auto tag = item.GetTag()) { + case LOCATE_TAG_ANY_TYPE: + { + if (!cond.empty()) { + cond += " and "; + } + cond += '('; + bool first(true); + for (const auto& cap : searchcaps) { + if (first) + first = false; + else + cond += " or "; + cond += cap; + if (item.GetFoldCase()) { + cond += " contains "; + } else { + cond += " = "; + } + dquote(cond, item.GetValue().c_str()); + } + cond += ')'; + } + break; + + default: + /* Unhandled conditions like + LOCATE_TAG_BASE_TYPE or + LOCATE_TAG_FILE_TYPE won't have a + corresponding upnp prop, so they will be + skipped */ + if (tag == TAG_ALBUM_ARTIST) + tag = TAG_ARTIST; + + // TODO: support LOCATE_TAG_ANY_TYPE etc. + const char *name = tag_table_lookup(upnp_tags, + TagType(tag)); + if (name == nullptr) + continue; + + if (!cond.empty()) { + cond += " and "; + } + cond += name; + + /* FoldCase doubles up as contains/equal + switch. UpNP search is supposed to be + case-insensitive, but at least some servers + have the same convention as mpd (e.g.: + minidlna) */ + if (item.GetFoldCase()) { + cond += " contains "; + } else { + cond += " = "; + } + dquote(cond, item.GetValue().c_str()); + } + } + + return server.search(handle, + objid, cond.c_str(), dirbuf, + error); +} + +static bool +visitSong(const UPnPDirObject &meta, const char *path, + const DatabaseSelection &selection, + VisitSong visit_song, Error& error) +{ + if (!visit_song) + return true; + + LightSong song; + song.directory = nullptr; + song.uri = path; + song.real_uri = meta.url.c_str(); + song.tag = &meta.tag; + song.mtime = 0; + song.start_ms = song.end_ms = 0; + + return !selection.Match(song) || visit_song(song, error); +} + +/** + * Build synthetic path based on object id for search results. The use + * of "rootid" is arbitrary, any name that is not likely to be a top + * directory name would fit. + */ +static std::string +songPath(const std::string &servername, + const std::string &objid) +{ + return servername + "/" + rootid + "/" + objid; +} + +bool +UpnpDatabase::SearchSongs(const ContentDirectoryService &server, + const char *objid, + const DatabaseSelection &selection, + VisitSong visit_song, + Error &error) const +{ + UPnPDirContent dirbuf; + if (!visit_song) + return true; + if (!SearchSongs(server, objid, selection, dirbuf, error)) + return false; + + for (auto &dirent : dirbuf.objects) { + if (dirent.type != UPnPDirObject::Type::ITEM || + dirent.item_class != UPnPDirObject::ItemClass::MUSIC) + continue; + + // We get song ids as the result of the UPnP search. But our + // client expects paths (e.g. we get 1$4$3788 from minidlna, + // but we need to translate to /Music/All_Music/Satisfaction). + // We can do this in two ways: + // - Rebuild a normal path using BuildPath() which is a kind of pwd + // - Build a bogus path based on the song id. + // The first method is nice because the returned paths are pretty, but + // it has two big problems: + // - The song paths are ambiguous: e.g. minidlna returns all search + // results as being from the "All Music" directory, which can + // contain several songs with the same title (but different objids) + // - The performance of BuildPath() is atrocious on very big + // directories, even causing timeouts in clients. And of + // course, 'All Music' is very big. + // So we return synthetic and ugly paths based on the object id, + // which we later have to detect. + const std::string path = songPath(server.getFriendlyName(), + dirent.m_id); + if (!visitSong(std::move(dirent), path.c_str(), + selection, visit_song, + error)) + return false; + } + + return true; +} + +bool +UpnpDatabase::ReadNode(const ContentDirectoryService &server, + const char *objid, UPnPDirObject &dirent, + Error &error) const +{ + UPnPDirContent dirbuf; + if (!server.getMetadata(handle, objid, dirbuf, error)) + return false; + + if (dirbuf.objects.size() == 1) { + dirent = std::move(dirbuf.objects.front()); + } else { + error.Format(upnp_domain, "Bad resource"); + return false; + } + + return true; +} + +bool +UpnpDatabase::BuildPath(const ContentDirectoryService &server, + const UPnPDirObject& idirent, + std::string &path, + Error &error) const +{ + const char *pid = idirent.m_id.c_str(); + path.clear(); + UPnPDirObject dirent; + while (strcmp(pid, rootid) != 0) { + if (!ReadNode(server, pid, dirent, error)) + return false; + pid = dirent.m_pid.c_str(); + + if (path.empty()) + path = dirent.name; + else + path = PathTraitsUTF8::Build(dirent.name.c_str(), + path.c_str()); + } + + path = PathTraitsUTF8::Build(server.getFriendlyName(), + path.c_str()); + return true; +} + +// Take server and internal title pathname and return objid and metadata. +bool +UpnpDatabase::Namei(const ContentDirectoryService &server, + const std::list<std::string> &vpath, + UPnPDirObject &odirent, + Error &error) const +{ + if (vpath.empty()) { + // looking for root info + if (!ReadNode(server, rootid, odirent, error)) + return false; + + return true; + } + + std::string objid(rootid); + + // Walk the path elements, read each directory and try to find the next one + for (auto i = vpath.begin(), last = std::prev(vpath.end());; ++i) { + UPnPDirContent dirbuf; + if (!server.readDir(handle, objid.c_str(), dirbuf, error)) + return false; + + // Look for the name in the sub-container list + UPnPDirObject *child = dirbuf.FindObject(i->c_str()); + if (child == nullptr) { + error.Format(db_domain, DB_NOT_FOUND, + "No such object"); + return false; + } + + if (i == last) { + odirent = std::move(*child); + return true; + } + + if (child->type != UPnPDirObject::Type::CONTAINER) { + error.Format(db_domain, DB_NOT_FOUND, + "Not a container"); + return false; + } + + objid = std::move(child->m_id); + } +} + +static bool +VisitItem(const UPnPDirObject &object, const char *uri, + const DatabaseSelection &selection, + VisitSong visit_song, VisitPlaylist visit_playlist, + Error &error) +{ + assert(object.type == UPnPDirObject::Type::ITEM); + + switch (object.item_class) { + case UPnPDirObject::ItemClass::MUSIC: + return !visit_song || + visitSong(object, uri, + selection, visit_song, error); + + case UPnPDirObject::ItemClass::PLAYLIST: + if (visit_playlist) { + /* Note: I've yet to see a + playlist item (playlists + seem to be usually handled + as containers, so I'll + decide what to do when I + see one... */ + } + + return true; + + case UPnPDirObject::ItemClass::UNKNOWN: + return true; + } + + assert(false); + gcc_unreachable(); +} + +static bool +VisitObject(const UPnPDirObject &object, const char *uri, + const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) +{ + switch (object.type) { + case UPnPDirObject::Type::UNKNOWN: + assert(false); + gcc_unreachable(); + + case UPnPDirObject::Type::CONTAINER: + return !visit_directory || + visit_directory(LightDirectory(uri, 0), error); + + case UPnPDirObject::Type::ITEM: + return VisitItem(object, uri, selection, + visit_song, visit_playlist, + error); + } + + assert(false); + gcc_unreachable(); +} + +// vpath is a parsed and writeable version of selection.uri. There is +// really just one path parameter. +bool +UpnpDatabase::VisitServer(const ContentDirectoryService &server, + const std::list<std::string> &vpath, + const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const +{ + /* If the path begins with rootid, we know that this is a + song, not a directory (because that's how we set things + up). Just visit it. Note that the choice of rootid is + arbitrary, any value not likely to be the name of a top + directory would be ok. */ + /* !Note: this *can't* be handled by Namei further down, + because the path is not valid for traversal. Besides, it's + just faster to access the target node directly */ + if (!vpath.empty() && vpath.front() == rootid) { + switch (vpath.size()) { + case 1: + return true; + + case 2: + break; + + default: + error.Format(db_domain, DB_NOT_FOUND, + "Not found"); + return false; + } + + if (visit_song) { + UPnPDirObject dirent; + if (!ReadNode(server, vpath.back().c_str(), dirent, + error)) + return false; + + if (dirent.type != UPnPDirObject::Type::ITEM || + dirent.item_class != UPnPDirObject::ItemClass::MUSIC) { + error.Format(db_domain, DB_NOT_FOUND, + "Not found"); + return false; + } + + std::string path = songPath(server.getFriendlyName(), + dirent.m_id); + if (!visitSong(std::move(dirent), path.c_str(), + selection, + visit_song, error)) + return false; + } + return true; + } + + // Translate the target path into an object id and the associated metadata. + UPnPDirObject tdirent; + if (!Namei(server, vpath, tdirent, error)) + return false; + + /* If recursive is set, this is a search... No use sending it + if the filter is empty. In this case, we implement limited + recursion (1-deep) here, which will handle the "add dir" + case. */ + if (selection.recursive && selection.filter) + return SearchSongs(server, tdirent.m_id.c_str(), selection, + visit_song, error); + + const char *const base_uri = selection.uri.empty() + ? server.getFriendlyName() + : selection.uri.c_str(); + + if (tdirent.type == UPnPDirObject::Type::ITEM) { + return VisitItem(tdirent, base_uri, + selection, + visit_song, visit_playlist, + error); + } + + /* Target was a a container. Visit it. We could read slices + and loop here, but it's not useful as mpd will only return + data to the client when we're done anyway. */ + UPnPDirContent dirbuf; + if (!server.readDir(handle, tdirent.m_id.c_str(), dirbuf, + error)) + return false; + + for (auto &dirent : dirbuf.objects) { + const std::string uri = PathTraitsUTF8::Build(base_uri, + dirent.name.c_str()); + if (!VisitObject(dirent, uri.c_str(), + selection, + visit_directory, + visit_song, visit_playlist, + error)) + return false; + } + + return true; +} + +// Deal with the possibly multiple servers, call VisitServer if needed. +bool +UpnpDatabase::Visit(const DatabaseSelection &selection, + VisitDirectory visit_directory, + VisitSong visit_song, + VisitPlaylist visit_playlist, + Error &error) const +{ + auto vpath = stringToTokens(selection.uri, "/", true); + if (vpath.empty()) { + std::vector<ContentDirectoryService> servers; + if (!discovery->getDirServices(servers, error)) + return false; + + for (const auto &server : servers) { + if (visit_directory) { + const LightDirectory d(server.getFriendlyName(), 0); + if (!visit_directory(d, error)) + return false; + } + + if (selection.recursive && + !VisitServer(server, vpath, selection, + visit_directory, visit_song, visit_playlist, + error)) + return false; + } + + return true; + } + + // We do have a path: the first element selects the server + std::string servername(std::move(vpath.front())); + vpath.pop_front(); + + ContentDirectoryService server; + if (!discovery->getServer(servername.c_str(), server, error)) + return false; + + return VisitServer(server, vpath, selection, + visit_directory, visit_song, visit_playlist, error); +} + +bool +UpnpDatabase::VisitUniqueTags(const DatabaseSelection &selection, + TagType tag, + VisitString visit_string, + Error &error) const +{ + if (!visit_string) + return true; + + std::vector<ContentDirectoryService> servers; + if (!discovery->getDirServices(servers, error)) + return false; + + std::set<std::string> values; + for (auto& server : servers) { + UPnPDirContent dirbuf; + if (!SearchSongs(server, rootid, selection, dirbuf, error)) + return false; + + for (const auto &dirent : dirbuf.objects) { + if (dirent.type != UPnPDirObject::Type::ITEM || + dirent.item_class != UPnPDirObject::ItemClass::MUSIC) + continue; + + const char *value = dirent.tag.GetValue(tag); + if (value != nullptr) { +#if defined(__clang__) || GCC_CHECK_VERSION(4,8) + values.emplace(value); +#else + values.insert(value); +#endif + } + } + } + + for (const auto& value : values) + if (!visit_string(value.c_str(), error)) + return false; + + return true; +} + +bool +UpnpDatabase::GetStats(const DatabaseSelection &, + DatabaseStats &stats, Error &) const +{ + /* Note: this gets called before the daemonizing so we can't + reallyopen this would be a problem if we had real stats */ + stats.song_count = 0; + stats.total_duration = 0; + stats.artist_count = 0; + stats.album_count = 0; + return true; +} + +const DatabasePlugin upnp_db_plugin = { + "upnp", + UpnpDatabase::Create, +}; diff --git a/src/db/plugins/UpnpDatabasePlugin.hxx b/src/db/plugins/UpnpDatabasePlugin.hxx new file mode 100644 index 000000000..0228405cd --- /dev/null +++ b/src/db/plugins/UpnpDatabasePlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPNP_DATABASE_PLUGIN_HXX +#define MPD_UPNP_DATABASE_PLUGIN_HXX + +struct DatabasePlugin; + +extern const DatabasePlugin upnp_db_plugin; + +#endif diff --git a/src/db/plugins/upnp/ContentDirectoryService.cxx b/src/db/plugins/upnp/ContentDirectoryService.cxx new file mode 100644 index 000000000..c097f7644 --- /dev/null +++ b/src/db/plugins/upnp/ContentDirectoryService.cxx @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "lib/upnp/ContentDirectoryService.hxx" +#include "lib/upnp/Domain.hxx" +#include "lib/upnp/ixmlwrap.hxx" +#include "lib/upnp/Action.hxx" +#include "Directory.hxx" +#include "util/NumberParser.hxx" +#include "util/Error.hxx" + +#include <stdio.h> + +static bool +ReadResultTag(UPnPDirContent &dirbuf, IXML_Document *response, Error &error) +{ + const char *p = ixmlwrap::getFirstElementValue(response, "Result"); + if (p == nullptr) + p = ""; + + return dirbuf.parse(p, error); +} + +inline bool +ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl, + const char *objectId, unsigned offset, + unsigned count, UPnPDirContent &dirbuf, + unsigned &didreadp, unsigned &totalp, + Error &error) const +{ + // Create request + char ofbuf[100], cntbuf[100]; + sprintf(ofbuf, "%u", offset); + sprintf(cntbuf, "%u", count); + // Some devices require an empty SortCriteria, else bad params + IXML_Document *request = + MakeActionHelper("Browse", m_serviceType.c_str(), + "ObjectID", objectId, + "BrowseFlag", "BrowseDirectChildren", + "Filter", "*", + "SortCriteria", "", + "StartingIndex", ofbuf, + "RequestedCount", cntbuf); + if (request == nullptr) { + error.Set(upnp_domain, "UpnpMakeAction() failed"); + return false; + } + + IXML_Document *response; + int code = UpnpSendAction(hdl, m_actionURL.c_str(), m_serviceType.c_str(), + 0 /*devUDN*/, request, &response); + ixmlDocument_free(request); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpSendAction() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + const char *value = ixmlwrap::getFirstElementValue(response, "NumberReturned"); + didreadp = value != nullptr + ? ParseUnsigned(value) + : 0; + + value = ixmlwrap::getFirstElementValue(response, "TotalMatches"); + if (value != nullptr) + totalp = ParseUnsigned(value); + + bool success = ReadResultTag(dirbuf, response, error); + ixmlDocument_free(response); + return success; +} + +bool +ContentDirectoryService::readDir(UpnpClient_Handle handle, + const char *objectId, + UPnPDirContent &dirbuf, + Error &error) const +{ + unsigned offset = 0, total = -1, count; + + do { + if (!readDirSlice(handle, objectId, offset, m_rdreqcnt, dirbuf, + count, total, error)) + return false; + + offset += count; + } while (count > 0 && offset < total); + + return true; +} + +bool +ContentDirectoryService::search(UpnpClient_Handle hdl, + const char *objectId, + const char *ss, + UPnPDirContent &dirbuf, + Error &error) const +{ + unsigned offset = 0, total = -1, count; + + do { + char ofbuf[100]; + sprintf(ofbuf, "%d", offset); + + IXML_Document *request = + MakeActionHelper("Search", m_serviceType.c_str(), + "ContainerID", objectId, + "SearchCriteria", ss, + "Filter", "*", + "SortCriteria", "", + "StartingIndex", ofbuf, + "RequestedCount", "0"); // Setting a value here gets twonky into fits + if (request == 0) { + error.Set(upnp_domain, "UpnpMakeAction() failed"); + return false; + } + + IXML_Document *response; + auto code = UpnpSendAction(hdl, m_actionURL.c_str(), + m_serviceType.c_str(), + 0 /*devUDN*/, request, &response); + ixmlDocument_free(request); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpSendAction() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + const char *value = + ixmlwrap::getFirstElementValue(response, "NumberReturned"); + count = value != nullptr + ? ParseUnsigned(value) + : 0; + + offset += count; + + value = ixmlwrap::getFirstElementValue(response, "TotalMatches"); + if (value != nullptr) + total = ParseUnsigned(value); + + bool success = ReadResultTag(dirbuf, response, error); + ixmlDocument_free(response); + if (!success) + return false; + } while (count > 0 && offset < total); + + return true; +} + +bool +ContentDirectoryService::getMetadata(UpnpClient_Handle hdl, + const char *objectId, + UPnPDirContent &dirbuf, + Error &error) const +{ + // Create request + IXML_Document *request = + MakeActionHelper("Browse", m_serviceType.c_str(), + "ObjectID", objectId, + "BrowseFlag", "BrowseMetadata", + "Filter", "*", + "SortCriteria", "", + "StartingIndex", "0", + "RequestedCount", "1"); + if (request == nullptr) { + error.Set(upnp_domain, "UpnpMakeAction() failed"); + return false; + } + + IXML_Document *response; + auto code = UpnpSendAction(hdl, m_actionURL.c_str(), + m_serviceType.c_str(), + 0 /*devUDN*/, request, &response); + ixmlDocument_free(request); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpSendAction() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + bool success = ReadResultTag(dirbuf, response, error); + ixmlDocument_free(response); + return success; +} diff --git a/src/db/plugins/upnp/Directory.cxx b/src/db/plugins/upnp/Directory.cxx new file mode 100644 index 000000000..e43cd48a6 --- /dev/null +++ b/src/db/plugins/upnp/Directory.cxx @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "lib/upnp/Util.hxx" +#include "lib/expat/ExpatParser.hxx" +#include "Tags.hxx" +#include "tag/TagBuilder.hxx" +#include "tag/TagTable.hxx" +#include "util/NumberParser.hxx" + +#include <algorithm> +#include <string> + +#include <string.h> + +UPnPDirContent::~UPnPDirContent() +{ + /* this destructor exists here just so it won't get inlined */ +} + +gcc_pure gcc_nonnull_all +static bool +CompareStringLiteral(const char *literal, const char *value, size_t length) +{ + return length == strlen(literal) && + memcmp(literal, value, length) == 0; +} + +gcc_pure +static UPnPDirObject::ItemClass +ParseItemClass(const char *name, size_t length) +{ + if (CompareStringLiteral("object.item.audioItem.musicTrack", + name, length)) + return UPnPDirObject::ItemClass::MUSIC; + else if (CompareStringLiteral("object.item.playlistItem", + name, length)) + return UPnPDirObject::ItemClass::PLAYLIST; + else + return UPnPDirObject::ItemClass::UNKNOWN; +} + +gcc_pure +static int +ParseDuration(const char *duration) +{ + char *endptr; + + unsigned result = ParseUnsigned(duration, &endptr); + if (endptr == duration || *endptr != ':') + return 0; + + result *= 60; + duration = endptr + 1; + result += ParseUnsigned(duration, &endptr); + if (endptr == duration || *endptr != ':') + return 0; + + result *= 60; + duration = endptr + 1; + result += ParseUnsigned(duration, &endptr); + if (endptr == duration || *endptr != 0) + return 0; + + return result; +} + +/** + * Transform titles to turn '/' into '_' to make them acceptable path + * elements. There is a very slight risk of collision in doing + * this. Twonky returns directory names (titles) like 'Artist/Album'. + */ +gcc_pure +static std::string +titleToPathElt(std::string &&s) +{ + std::replace(s.begin(), s.end(), '/', '_'); + return s; +} + +/** + * An XML parser which builds directory contents from DIDL lite input. + */ +class UPnPDirParser final : public CommonExpatParser { + UPnPDirContent &m_dir; + + enum { + NONE, + RES, + CLASS, + } state; + + /** + * If not equal to #TAG_NUM_OF_ITEM_TYPES, then we're + * currently reading an element containing a tag value. The + * value is being constructed in #value. + */ + TagType tag_type; + + /** + * The text inside the current element. + */ + std::string value; + + UPnPDirObject m_tobj; + TagBuilder tag; + +public: + UPnPDirParser(UPnPDirContent& dir) + :m_dir(dir), + state(NONE), + tag_type(TAG_NUM_OF_ITEM_TYPES) + { + } + +protected: + virtual void StartElement(const XML_Char *name, const XML_Char **attrs) + { + if (m_tobj.type != UPnPDirObject::Type::UNKNOWN && + tag_type == TAG_NUM_OF_ITEM_TYPES) { + tag_type = tag_table_lookup(upnp_tags, name); + if (tag_type != TAG_NUM_OF_ITEM_TYPES) + return; + } else { + assert(tag_type == TAG_NUM_OF_ITEM_TYPES); + } + + switch (name[0]) { + case 'c': + if (!strcmp(name, "container")) { + m_tobj.clear(); + m_tobj.type = UPnPDirObject::Type::CONTAINER; + + const char *id = GetAttribute(attrs, "id"); + if (id != nullptr) + m_tobj.m_id = id; + + const char *pid = GetAttribute(attrs, "parentID"); + if (pid != nullptr) + m_tobj.m_pid = pid; + } + break; + + case 'i': + if (!strcmp(name, "item")) { + m_tobj.clear(); + m_tobj.type = UPnPDirObject::Type::ITEM; + + const char *id = GetAttribute(attrs, "id"); + if (id != nullptr) + m_tobj.m_id = id; + + const char *pid = GetAttribute(attrs, "parentID"); + if (pid != nullptr) + m_tobj.m_pid = pid; + } + break; + + case 'r': + if (!strcmp(name, "res")) { + // <res protocolInfo="http-get:*:audio/mpeg:*" size="5171496" + // bitrate="24576" duration="00:03:35" sampleFrequency="44100" + // nrAudioChannels="2"> + + const char *duration = + GetAttribute(attrs, "duration"); + if (duration != nullptr) + tag.SetTime(ParseDuration(duration)); + + state = RES; + } + + break; + + case 'u': + if (strcmp(name, "upnp:class") == 0) + state = CLASS; + } + } + + bool checkobjok() { + if (m_tobj.m_id.empty() || m_tobj.m_pid.empty() || + m_tobj.name.empty() || + (m_tobj.type == UPnPDirObject::Type::ITEM && + m_tobj.item_class == UPnPDirObject::ItemClass::UNKNOWN)) + return false; + + return true; + } + + virtual void EndElement(const XML_Char *name) + { + if (tag_type != TAG_NUM_OF_ITEM_TYPES) { + assert(m_tobj.type != UPnPDirObject::Type::UNKNOWN); + + tag.AddItem(tag_type, value.c_str()); + + if (tag_type == TAG_TITLE) + m_tobj.name = titleToPathElt(std::move(value)); + + value.clear(); + tag_type = TAG_NUM_OF_ITEM_TYPES; + return; + } + + if ((!strcmp(name, "container") || !strcmp(name, "item")) && + checkobjok()) { + tag.Commit(m_tobj.tag); + m_dir.objects.emplace_back(std::move(m_tobj)); + } + + state = NONE; + } + + virtual void CharacterData(const XML_Char *s, int len) + { + if (tag_type != TAG_NUM_OF_ITEM_TYPES) { + assert(m_tobj.type != UPnPDirObject::Type::UNKNOWN); + + value.append(s, len); + return; + } + + switch (state) { + case NONE: + break; + + case RES: + m_tobj.url.assign(s, len); + break; + + case CLASS: + m_tobj.item_class = ParseItemClass(s, len); + break; + } + } +}; + +bool +UPnPDirContent::parse(const char *input, Error &error) +{ + UPnPDirParser parser(*this); + return parser.Parse(input, strlen(input), true, error); +} diff --git a/src/db/plugins/upnp/Directory.hxx b/src/db/plugins/upnp/Directory.hxx new file mode 100644 index 000000000..433979900 --- /dev/null +++ b/src/db/plugins/upnp/Directory.hxx @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPNP_DIRECTORY_HXX +#define MPD_UPNP_DIRECTORY_HXX + +#include "Object.hxx" +#include "Compiler.h" + +#include <string> +#include <vector> + +class Error; + +/** + * Image of a MediaServer Directory Service container (directory), + * possibly containing items and subordinate containers. + */ +class UPnPDirContent { +public: + std::vector<UPnPDirObject> objects; + + ~UPnPDirContent(); + + gcc_pure + UPnPDirObject *FindObject(const char *name) { + for (auto &o : objects) + if (o.name == name) + return &o; + + return nullptr; + } + + /** + * Parse from DIDL-Lite XML data. + * + * Normally only used by ContentDirectoryService::readDir() + * This is cumulative: in general, the XML data is obtained in + * several documents corresponding to (offset,count) slices of the + * directory (container). parse() can be called repeatedly with + * the successive XML documents and will accumulate entries in the item + * and container vectors. This makes more sense if the different + * chunks are from the same container, but given that UPnP Ids are + * actually global, nothing really bad will happen if you mix + * up... + */ + bool parse(const char *didltext, Error &error); +}; + +#endif /* _UPNPDIRCONTENT_H_X_INCLUDED_ */ diff --git a/src/db/plugins/upnp/Object.cxx b/src/db/plugins/upnp/Object.cxx new file mode 100644 index 000000000..703fb0be4 --- /dev/null +++ b/src/db/plugins/upnp/Object.cxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "Object.hxx" + +UPnPDirObject::~UPnPDirObject() +{ + /* this destructor exists here just so it won't get inlined */ +} diff --git a/src/db/plugins/upnp/Object.hxx b/src/db/plugins/upnp/Object.hxx new file mode 100644 index 000000000..16a66c774 --- /dev/null +++ b/src/db/plugins/upnp/Object.hxx @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPNP_OBJECT_HXX +#define MPD_UPNP_OBJECT_HXX + +#include "tag/Tag.hxx" + +#include <string> + +/** + * UpnP Media Server directory entry, converted from XML data. + * + * This is a dumb data holder class, a struct with helpers. + */ +class UPnPDirObject { +public: + enum class Type { + UNKNOWN, + ITEM, + CONTAINER, + }; + + // There are actually several kinds of containers: + // object.container.storageFolder, object.container.person, + // object.container.playlistContainer etc., but they all seem to + // behave the same as far as we're concerned. Otoh, musicTrack + // items are special to us, and so should playlists, but I've not + // seen one of the latter yet (servers seem to use containers for + // playlists). + enum class ItemClass { + UNKNOWN, + MUSIC, + PLAYLIST, + }; + + std::string m_id; // ObjectId + std::string m_pid; // Parent ObjectId + std::string url; + + /** + * A copy of "dc:title" sanitized as a file name. + */ + std::string name; + + Type type; + ItemClass item_class; + + Tag tag; + + UPnPDirObject() = default; + UPnPDirObject(UPnPDirObject &&) = default; + + ~UPnPDirObject(); + + UPnPDirObject &operator=(UPnPDirObject &&) = default; + + void clear() + { + m_id.clear(); + m_pid.clear(); + url.clear(); + type = Type::UNKNOWN; + item_class = ItemClass::UNKNOWN; + tag.Clear(); + } +}; + +#endif /* _UPNPDIRCONTENT_H_X_INCLUDED_ */ diff --git a/src/db/plugins/upnp/Tags.cxx b/src/db/plugins/upnp/Tags.cxx new file mode 100644 index 000000000..fd65df4d0 --- /dev/null +++ b/src/db/plugins/upnp/Tags.cxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Tags.hxx" +#include "tag/TagTable.hxx" + +const struct tag_table upnp_tags[] = { + { "upnp:artist", TAG_ARTIST }, + { "upnp:album", TAG_ALBUM }, + { "upnp:originalTrackNumber", TAG_TRACK }, + { "upnp:genre", TAG_GENRE }, + { "dc:title", TAG_TITLE }, + + /* sentinel */ + { nullptr, TAG_NUM_OF_ITEM_TYPES } +}; diff --git a/src/db/plugins/upnp/Tags.hxx b/src/db/plugins/upnp/Tags.hxx new file mode 100644 index 000000000..ec6d18478 --- /dev/null +++ b/src/db/plugins/upnp/Tags.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPNP_TAGS_HXX +#define MPD_UPNP_TAGS_HXX + +/** + * Map UPnP property names to MPD tags. + */ +extern const struct tag_table upnp_tags[]; + +#endif diff --git a/src/db/update/InotifyDomain.cxx b/src/db/update/InotifyDomain.cxx new file mode 100644 index 000000000..4a3ab2d79 --- /dev/null +++ b/src/db/update/InotifyDomain.cxx @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License 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/db/update/InotifyDomain.hxx b/src/db/update/InotifyDomain.hxx new file mode 100644 index 000000000..ad6202361 --- /dev/null +++ b/src/db/update/InotifyDomain.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/db/update/InotifyQueue.cxx b/src/db/update/InotifyQueue.cxx new file mode 100644 index 000000000..f4bccf7ae --- /dev/null +++ b/src/db/update/InotifyQueue.cxx @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "Log.hxx" + +#include <string.h> + +/** + * Wait this long after the last change before calling + * update_enqueue(). This increases the probability that updates can + * be bundled. + */ +static constexpr unsigned 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/db/update/InotifyQueue.hxx b/src/db/update/InotifyQueue.hxx new file mode 100644 index 000000000..99e2635b1 --- /dev/null +++ b/src/db/update/InotifyQueue.hxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.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/db/update/InotifySource.cxx b/src/db/update/InotifySource.cxx new file mode 100644 index 000000000..c2783690e --- /dev/null +++ b/src/db/update/InotifySource.cxx @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/Error.hxx" +#include "system/fd_util.h" +#include "system/FatalError.hxx" +#include "Log.hxx" + +#include <sys/inotify.h> +#include <unistd.h> +#include <errno.h> + +bool +InotifySource::OnSocketReady(gcc_unused unsigned flags) +{ + const auto dest = buffer.Write(); + if (dest.IsEmpty()) + FatalError("buffer full"); + + ssize_t nbytes = read(Get(), dest.data, dest.size); + if (nbytes < 0) + FatalSystemError("Failed to read from inotify"); + if (nbytes == 0) + FatalError("end of file from inotify"); + + buffer.Append(nbytes); + + while (true) { + const char *name; + + auto range = buffer.Read(); + const struct inotify_event *event = + (const struct inotify_event *) + range.data; + if (range.size < sizeof(*event) || + range.size < sizeof(*event) + event->len) + break; + + if (event->len > 0 && event->name[event->len - 1] == 0) + name = event->name; + else + name = nullptr; + + callback(event->wd, event->mask, name, callback_ctx); + buffer.Consume(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) +{ + 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 nullptr; + } + + return new InotifySource(loop, callback, callback_ctx, fd); +} + +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/db/update/InotifySource.hxx b/src/db/update/InotifySource.hxx new file mode 100644 index 000000000..77c11093c --- /dev/null +++ b/src/db/update/InotifySource.hxx @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "util/FifoBuffer.hxx" + +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; + + FifoBuffer<uint8_t, 4096> buffer; + + InotifySource(EventLoop &_loop, + mpd_inotify_callback_t callback, void *ctx, int fd); + +public: + ~InotifySource() { + Close(); + } + + /** + * 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); + + /** + * 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/db/update/InotifyUpdate.cxx b/src/db/update/InotifyUpdate.cxx new file mode 100644 index 000000000..7515990d7 --- /dev/null +++ b/src/db/update/InotifyUpdate.cxx @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with 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/AllocatedPath.hxx" +#include "fs/FileSystem.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <string> +#include <map> +#include <forward_list> + +#include <assert.h> +#include <sys/inotify.h> +#include <sys/stat.h> +#include <string.h> +#include <dirent.h> + +static constexpr unsigned IN_MASK = +#ifdef IN_ONLYDIR + IN_ONLYDIR| +#endif + IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF + |IN_MOVE|IN_MOVE_SELF; + +struct WatchDirectory { + WatchDirectory *parent; + + AllocatedPath name; + + int descriptor; + + std::forward_list<WatchDirectory> children; + + template<typename N> + WatchDirectory(WatchDirectory *_parent, N &&_name, + int _descriptor) + :parent(_parent), name(std::forward<N>(_name)), + descriptor(_descriptor) {} + + WatchDirectory(const WatchDirectory &) = delete; + WatchDirectory &operator=(const WatchDirectory &) = delete; +}; + +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 != nullptr); + + if (directory->parent == nullptr) { + 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 AllocatedPath +watch_directory_get_uri_fs(const WatchDirectory *directory) +{ + if (directory->parent == nullptr) + return AllocatedPath::Null(); + + const auto uri = watch_directory_get_uri_fs(directory->parent); + if (uri.IsNull()) + return directory->name; + + return AllocatedPath::Build(uri, directory->name); +} + +/* we don't look at "." / ".." nor files with newlines in their name */ +static bool skip_path(const char *path) +{ + return (path[0] == '.' && path[1] == 0) || + (path[0] == '.' && path[1] == '.' && path[2] == 0) || + strchr(path, '\n') != nullptr; +} + +static void +recursive_watch_subdirectories(WatchDirectory *directory, + const AllocatedPath &path_fs, unsigned depth) +{ + Error error; + DIR *dir; + struct dirent *ent; + + assert(directory != nullptr); + assert(depth <= inotify_max_depth); + assert(!path_fs.IsNull()); + + ++depth; + + if (depth > inotify_max_depth) + return; + + dir = opendir(path_fs.c_str()); + if (dir == nullptr) { + FormatErrno(inotify_domain, + "Failed to open directory %s", path_fs.c_str()); + return; + } + + while ((ent = readdir(dir))) { + struct stat st; + int ret; + + if (skip_path(ent->d_name)) + continue; + + const auto child_path_fs = + AllocatedPath::Build(path_fs, ent->d_name); + ret = StatFile(child_path_fs, st); + if (ret < 0) { + FormatErrno(inotify_domain, + "Failed to stat %s", + child_path_fs.c_str()); + continue; + } + + if (!S_ISDIR(st.st_mode)) + continue; + + ret = inotify_source->Add(child_path_fs.c_str(), IN_MASK, + error); + if (ret < 0) { + FormatError(error, + "Failed to register %s", + child_path_fs.c_str()); + error.Clear(); + continue; + } + + WatchDirectory *child = tree_find_watch_directory(ret); + if (child != nullptr) + /* already being watched */ + continue; + + directory->children.emplace_front(directory, + AllocatedPath::FromFS(ent->d_name), + ret); + child = &directory->children.front(); + + tree_add_watch_directory(child); + + recursive_watch_subdirectories(child, child_path_fs, depth); + } + + closedir(dir); +} + +gcc_pure +static unsigned +watch_directory_depth(const WatchDirectory *d) +{ + assert(d != nullptr); + + unsigned depth = 0; + while ((d = d->parent) != nullptr) + ++depth; + + return depth; +} + +static void +mpd_inotify_callback(int wd, unsigned mask, + gcc_unused const char *name, gcc_unused void *ctx) +{ + WatchDirectory *directory; + + /*FormatDebug(inotify_domain, "wd=%d mask=0x%x name='%s'", wd, mask, name);*/ + + directory = tree_find_watch_directory(wd); + if (directory == nullptr) + return; + + const auto uri_fs = watch_directory_get_uri_fs(directory); + + if ((mask & (IN_DELETE_SELF|IN_MOVE_SELF)) != 0) { + 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 auto &root = mapper_get_music_directory_fs(); + + const auto path_fs = uri_fs.IsNull() + ? root + : AllocatedPath::Build(root, uri_fs.c_str()); + + recursive_watch_subdirectories(directory, path_fs, + watch_directory_depth(directory)); + } + + 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.IsNull()) { + const std::string uri_utf8 = uri_fs.ToUTF8(); + if (!uri_utf8.empty()) + inotify_queue->Enqueue(uri_utf8.c_str()); + } + else + inotify_queue->Enqueue(""); + } +} + +void +mpd_inotify_init(unsigned max_depth) +{ + LogDebug(inotify_domain, "initializing inotify"); + + const auto &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 == nullptr) { + 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 = nullptr; + return; + } + + inotify_root = new WatchDirectory(nullptr, path, descriptor); + + tree_add_watch_directory(inotify_root); + + recursive_watch_subdirectories(inotify_root, path, 0); + + inotify_queue = new InotifyQueue(*main_loop); + + LogDebug(inotify_domain, "watching music directory"); +} + +void +mpd_inotify_finish(void) +{ + if (inotify_source == nullptr) + return; + + delete inotify_queue; + delete inotify_source; + delete inotify_root; + inotify_directories.clear(); +} diff --git a/src/db/update/InotifyUpdate.hxx b/src/db/update/InotifyUpdate.hxx new file mode 100644 index 000000000..2d7d4e3b4 --- /dev/null +++ b/src/db/update/InotifyUpdate.hxx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/db/update/UpdateArchive.cxx b/src/db/update/UpdateArchive.cxx new file mode 100644 index 000000000..5e733202d --- /dev/null +++ b/src/db/update/UpdateArchive.cxx @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with 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 "db/DatabaseLock.hxx" +#include "db/Directory.hxx" +#include "db/Song.hxx" +#include "Mapper.hxx" +#include "fs/AllocatedPath.hxx" +#include "archive/ArchiveList.hxx" +#include "archive/ArchivePlugin.hxx" +#include "archive/ArchiveFile.hxx" +#include "archive/ArchiveVisitor.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <string> + +#include <string.h> + +static void +update_archive_tree(Directory &directory, const char *name) +{ + const char *tmp = strchr(name, '/'); + if (tmp) { + const std::string child_name(name, tmp); + //add dir is not there already + db_lock(); + Directory *subdir = + directory.MakeChild(child_name.c_str()); + subdir->device = DEVICE_INARCHIVE; + db_unlock(); + + //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 == nullptr) { + song = Song::LoadFile(name, directory); + if (song != nullptr) { + db_lock(); + directory.AddSong(song); + db_unlock(); + + modified = true; + FormatDefault(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 != nullptr && 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 auto 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 == nullptr) { + LogError(error); + return; + } + + FormatDebug(update_domain, "archive %s opened", path_fs.c_str()); + + if (directory == nullptr) { + 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 == nullptr) + 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/db/update/UpdateArchive.hxx b/src/db/update/UpdateArchive.hxx new file mode 100644 index 000000000..1fc9af349 --- /dev/null +++ b/src/db/update/UpdateArchive.hxx @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.h" + +#include <sys/stat.h> + +struct Directory; + +#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/db/update/UpdateContainer.cxx b/src/db/update/UpdateContainer.cxx new file mode 100644 index 000000000..c03d88748 --- /dev/null +++ b/src/db/update/UpdateContainer.cxx @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with 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 "db/DatabaseLock.hxx" +#include "db/Directory.hxx" +#include "db/Song.hxx" +#include "decoder/DecoderPlugin.hxx" +#include "decoder/DecoderList.hxx" +#include "Mapper.hxx" +#include "fs/AllocatedPath.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 nullptr 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 != nullptr) { + if (directory->mtime == st->st_mtime && !walk_discard) { + /* not modified */ + return nullptr; + } + + delete_directory(directory); + modified = true; + } + + directory = parent.MakeChild(name); + directory->mtime = st->st_mtime; + return directory; +} + +static bool +SupportsContainerSuffix(const DecoderPlugin &plugin, const char *suffix) +{ + return plugin.container_scan != nullptr && + plugin.SupportsSuffix(suffix); +} + +bool +update_container_file(Directory &directory, + const char *name, + const struct stat *st, + const char *suffix) +{ + const DecoderPlugin *_plugin = decoder_plugins_find([suffix](const DecoderPlugin &plugin){ + return SupportsContainerSuffix(plugin, suffix); + }); + if (_plugin == nullptr) + return false; + const DecoderPlugin &plugin = *_plugin; + + db_lock(); + Directory *contdir = make_directory_if_modified(directory, name, st); + if (contdir == nullptr) { + /* not modified */ + db_unlock(); + return true; + } + + contdir->device = DEVICE_CONTAINER; + db_unlock(); + + const auto 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)) != nullptr) { + Song *song = Song::NewFile(vtrack, *contdir); + + // shouldn't be necessary but it's there.. + song->mtime = st->st_mtime; + + const auto child_path_fs = + map_directory_child_fs(*contdir, vtrack); + + plugin.ScanFile(child_path_fs.c_str(), + add_tag_handler, &tag_builder); + + tag_builder.Commit(song->tag); + + db_lock(); + contdir->AddSong(song); + db_unlock(); + + modified = true; + + FormatDefault(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/db/update/UpdateContainer.hxx b/src/db/update/UpdateContainer.hxx new file mode 100644 index 000000000..8125f71ee --- /dev/null +++ b/src/db/update/UpdateContainer.hxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderPlugin; + +bool +update_container_file(Directory &directory, + const char *name, + const struct stat *st, + const char *suffix); + +#endif diff --git a/src/db/update/UpdateDatabase.cxx b/src/db/update/UpdateDatabase.cxx new file mode 100644 index 000000000..8ef0b6d82 --- /dev/null +++ b/src/db/update/UpdateDatabase.cxx @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with 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 "db/Directory.hxx" +#include "db/Song.hxx" +#include "db/DatabaseLock.hxx" + +#include <assert.h> +#include <stddef.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 != nullptr); + + 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 != nullptr) { + delete_directory(directory); + modified = true; + } + + Song *song = parent.FindSong(name); + if (song != nullptr) { + delete_song(parent, song); + modified = true; + } + + parent.playlists.erase(name); + + db_unlock(); + + return modified; +} diff --git a/src/db/update/UpdateDatabase.hxx b/src/db/update/UpdateDatabase.hxx new file mode 100644 index 000000000..bd7c395f2 --- /dev/null +++ b/src/db/update/UpdateDatabase.hxx @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/db/update/UpdateDomain.cxx b/src/db/update/UpdateDomain.cxx new file mode 100644 index 000000000..80ad4fd22 --- /dev/null +++ b/src/db/update/UpdateDomain.cxx @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License 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/db/update/UpdateDomain.hxx b/src/db/update/UpdateDomain.hxx new file mode 100644 index 000000000..a6e994390 --- /dev/null +++ b/src/db/update/UpdateDomain.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/db/update/UpdateGlue.cxx b/src/db/update/UpdateGlue.cxx new file mode 100644 index 000000000..d18747ba1 --- /dev/null +++ b/src/db/update/UpdateGlue.cxx @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "db/DatabaseSimple.hxx" +#include "Idle.hxx" +#include "GlobalEvents.hxx" +#include "util/Error.hxx" +#include "Log.hxx" +#include "Main.hxx" +#include "Instance.hxx" +#include "system/FatalError.hxx" +#include "thread/Id.hxx" +#include "thread/Thread.hxx" +#include "thread/Util.hxx" + +#include <assert.h> + +static enum update_progress { + UPDATE_PROGRESS_IDLE = 0, + UPDATE_PROGRESS_RUNNING = 1, + UPDATE_PROGRESS_DONE = 2 +} progress; + +static bool modified; + +static Thread update_thread; + +static const unsigned update_task_id_max = 1 << 15; + +static unsigned update_task_id; + +static UpdateQueueItem next; + +unsigned +isUpdatingDB(void) +{ + return next.id; +} + +static void +update_task(gcc_unused void *ctx) +{ + if (!next.path_utf8.empty()) + FormatDebug(update_domain, "starting: %s", + next.path_utf8.c_str()); + else + LogDebug(update_domain, "starting"); + + SetThreadIdlePriority(); + + modified = update_walk(next.path_utf8.c_str(), next.discard); + + if (modified || !db_exists()) { + Error error; + if (!db_save(error)) + LogError(error, "Failed to save database"); + } + + if (!next.path_utf8.empty()) + FormatDebug(update_domain, "finished: %s", + next.path_utf8.c_str()); + else + LogDebug(update_domain, "finished"); + + progress = UPDATE_PROGRESS_DONE; + GlobalEvents::Emit(GlobalEvents::UPDATE); +} + +static void +spawn_update_task(UpdateQueueItem &&i) +{ + assert(main_thread.IsInside()); + + progress = UPDATE_PROGRESS_RUNNING; + modified = false; + + next = std::move(i); + + Error error; + if (!update_thread.Start(update_task, nullptr, error)) + FatalError(error); + + FormatDebug(update_domain, + "spawned thread for update job id %i", next.id); +} + +static unsigned +generate_update_id() +{ + unsigned id = update_task_id + 1; + if (id > update_task_id_max) + id = 1; + return id; +} + +unsigned +update_enqueue(const char *path, bool discard) +{ + assert(main_thread.IsInside()); + + if (!db_is_simple() || !mapper_has_music_directory()) + return 0; + + if (progress != UPDATE_PROGRESS_IDLE) { + const unsigned id = generate_update_id(); + if (!update_queue_push(path, discard, id)) + return 0; + + update_task_id = id; + return id; + } + + const unsigned id = update_task_id = generate_update_id(); + spawn_update_task(UpdateQueueItem(path, discard, id)); + + idle_add(IDLE_UPDATE); + + return id; +} + +/** + * Called in the main thread after the database update is finished. + */ +static void update_finished_event(void) +{ + assert(progress == UPDATE_PROGRESS_DONE); + assert(next.IsDefined()); + + update_thread.Join(); + next = UpdateQueueItem(); + + idle_add(IDLE_UPDATE); + + if (modified) + /* send "idle" events */ + instance->DatabaseModified(); + + auto i = update_queue_shift(); + if (i.IsDefined()) { + /* schedule the next path */ + spawn_update_task(std::move(i)); + } else { + progress = UPDATE_PROGRESS_IDLE; + } +} + +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/db/update/UpdateGlue.hxx b/src/db/update/UpdateGlue.hxx new file mode 100644 index 000000000..6e247414e --- /dev/null +++ b/src/db/update/UpdateGlue.hxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 + +#include "Compiler.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 an empty string, + * the whole music directory is updated + * @return the job id, or 0 on error + */ +gcc_nonnull_all +unsigned +update_enqueue(const char *path, bool discard); + +#endif diff --git a/src/db/update/UpdateIO.cxx b/src/db/update/UpdateIO.cxx new file mode 100644 index 000000000..f91caf359 --- /dev/null +++ b/src/db/update/UpdateIO.cxx @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with 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 "UpdateDomain.hxx" +#include "db/Directory.hxx" +#include "Mapper.hxx" +#include "fs/AllocatedPath.hxx" +#include "fs/FileSystem.hxx" +#include "Log.hxx" + +#include <errno.h> +#include <unistd.h> + +int +stat_directory(const Directory &directory, struct stat *st) +{ + const auto 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 auto 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 auto 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 auto 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 auto 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/db/update/UpdateIO.hxx b/src/db/update/UpdateIO.hxx new file mode 100644 index 000000000..819879422 --- /dev/null +++ b/src/db/update/UpdateIO.hxx @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/db/update/UpdateInternal.hxx b/src/db/update/UpdateInternal.hxx new file mode 100644 index 000000000..2e373bd06 --- /dev/null +++ b/src/db/update/UpdateInternal.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/db/update/UpdateQueue.cxx b/src/db/update/UpdateQueue.cxx new file mode 100644 index 000000000..a6002f854 --- /dev/null +++ b/src/db/update/UpdateQueue.cxx @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 <queue> +#include <list> + +static constexpr unsigned MAX_UPDATE_QUEUE_SIZE = 32; + +static std::queue<UpdateQueueItem, std::list<UpdateQueueItem>> update_queue; + +bool +update_queue_push(const char *path, bool discard, unsigned id) +{ + if (update_queue.size() >= MAX_UPDATE_QUEUE_SIZE) + return false; + + update_queue.emplace(path, discard, id); + return true; +} + +UpdateQueueItem +update_queue_shift() +{ + if (update_queue.empty()) + return UpdateQueueItem(); + + auto i = std::move(update_queue.front()); + update_queue.pop(); + return i; +} diff --git a/src/db/update/UpdateQueue.hxx b/src/db/update/UpdateQueue.hxx new file mode 100644 index 000000000..e4228f5ed --- /dev/null +++ b/src/db/update/UpdateQueue.hxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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" + +#include <string> + +struct UpdateQueueItem { + std::string path_utf8; + unsigned id; + bool discard; + + UpdateQueueItem():id(0) {} + UpdateQueueItem(const char *_path, bool _discard, + unsigned _id) + :path_utf8(_path), id(_id), discard(_discard) {} + + bool IsDefined() const { + return id != 0; + } +}; + +bool +update_queue_push(const char *path, bool discard, unsigned id); + +UpdateQueueItem +update_queue_shift(); + +#endif diff --git a/src/db/update/UpdateRemove.cxx b/src/db/update/UpdateRemove.cxx new file mode 100644 index 000000000..c57758aef --- /dev/null +++ b/src/db/update/UpdateRemove.cxx @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with 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 "GlobalEvents.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "db/Song.hxx" +#include "db/LightSong.hxx" +#include "Main.hxx" +#include "Instance.hxx" +#include "Log.hxx" + +#ifdef ENABLE_SQLITE +#include "sticker/StickerDatabase.hxx" +#include "sticker/SongSticker.hxx" +#endif + +#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) +{ + assert(removed_song != nullptr); + + { + const auto uri = removed_song->GetURI(); + FormatDefault(update_domain, "removing %s", uri.c_str()); + } + +#ifdef ENABLE_SQLITE + /* if the song has a sticker, remove it */ + if (sticker_enabled()) + sticker_song_delete(removed_song->Export()); +#endif + + { + const auto uri = removed_song->GetURI(); + instance->DeleteSong(uri.c_str()); + } + + /* clear "removed_song" and send signal to update thread */ + remove_mutex.lock(); + removed_song = nullptr; + 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 == nullptr); + + removed_song = song; + + GlobalEvents::Emit(GlobalEvents::DELETE); + + remove_mutex.lock(); + + while (removed_song != nullptr) + remove_cond.wait(remove_mutex); + + remove_mutex.unlock(); +} diff --git a/src/db/update/UpdateRemove.hxx b/src/db/update/UpdateRemove.hxx new file mode 100644 index 000000000..d54e3aa80 --- /dev/null +++ b/src/db/update/UpdateRemove.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/db/update/UpdateSong.cxx b/src/db/update/UpdateSong.cxx new file mode 100644 index 000000000..ac2d01cd2 --- /dev/null +++ b/src/db/update/UpdateSong.cxx @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with 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 "db/DatabaseLock.hxx" +#include "db/Directory.hxx" +#include "db/Song.hxx" +#include "decoder/DecoderList.hxx" +#include "Log.hxx" + +#include <unistd.h> + +static void +update_song_file2(Directory &directory, + const char *name, const struct stat *st, + const char *suffix) +{ + 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 != nullptr) { + db_lock(); + delete_song(directory, song); + db_unlock(); + } + + return; + } + + if (!(song != nullptr && st->st_mtime == song->mtime && + !walk_discard) && + update_container_file(directory, name, st, suffix)) { + if (song != nullptr) { + db_lock(); + delete_song(directory, song); + db_unlock(); + } + + return; + } + + if (song == nullptr) { + FormatDebug(update_domain, "reading %s/%s", + directory.GetPath(), name); + song = Song::LoadFile(name, directory); + if (song == nullptr) { + FormatDebug(update_domain, + "ignoring unrecognized file %s/%s", + directory.GetPath(), name); + return; + } + + db_lock(); + directory.AddSong(song); + db_unlock(); + + modified = true; + FormatDefault(update_domain, "added %s/%s", + directory.GetPath(), name); + } else if (st->st_mtime != song->mtime || walk_discard) { + FormatDefault(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) +{ + if (!decoder_plugins_supports_suffix(suffix)) + return false; + + update_song_file2(directory, name, st, suffix); + return true; +} diff --git a/src/db/update/UpdateSong.hxx b/src/db/update/UpdateSong.hxx new file mode 100644 index 000000000..5feb01928 --- /dev/null +++ b/src/db/update/UpdateSong.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/db/update/UpdateWalk.cxx b/src/db/update/UpdateWalk.cxx new file mode 100644 index 000000000..c5a9936e9 --- /dev/null +++ b/src/db/update/UpdateWalk.cxx @@ -0,0 +1,484 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with 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 "db/DatabaseLock.hxx" +#include "db/DatabaseSimple.hxx" +#include "db/Directory.hxx" +#include "db/Song.hxx" +#include "PlaylistVector.hxx" +#include "playlist/PlaylistRegistry.hxx" +#include "Mapper.hxx" +#include "ExcludeList.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" +#include "fs/AllocatedPath.hxx" +#include "fs/Traits.hxx" +#include "fs/FileSystem.hxx" +#include "fs/DirectoryReader.hxx" +#include "util/Alloc.hxx" +#include "util/UriUtil.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <sys/stat.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> + +bool walk_discard; +bool modified; + +#ifndef WIN32 + +static constexpr bool DEFAULT_FOLLOW_INSIDE_SYMLINKS = true; +static constexpr bool 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 auto name_fs = AllocatedPath::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 auto name_fs = AllocatedPath::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 auto 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 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 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 == nullptr) + 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, '/') == nullptr); + + 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(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') != nullptr; +} + +gcc_pure +static bool +skip_symlink(const Directory *directory, const char *utf8_name) +{ +#ifndef WIN32 + const auto path_fs = map_directory_child_fs(*directory, utf8_name); + if (path_fs.IsNull()) + return true; + + const auto 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 (PathTraitsFS::IsAbsolute(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] == '.' && PathTraitsFS::IsSeparator(p[2])) { + /* "../" moves to parent directory */ + directory = directory->parent; + if (directory == nullptr) { + /* we have moved outside the music + directory - skip this symlink + if such symlinks are not allowed */ + return !follow_outside_symlinks; + } + p += 3; + } else if (PathTraitsFS::IsSeparator(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 auto 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(AllocatedPath::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 auto 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 != nullptr) + 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 nullptr; + + if (skip_symlink(&parent, name_utf8)) + return nullptr; + + /* 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 = xstrdup(uri); + char *name_utf8 = duplicated, *slash; + + while ((slash = strchr(name_utf8, '/')) != nullptr) { + *slash = 0; + + if (*name_utf8 == 0) + continue; + + directory = directory_make_child_checked(*directory, + name_utf8); + if (directory == nullptr) + break; + + name_utf8 = slash + 1; + } + + free(duplicated); + return directory; +} + +static void +update_uri(const char *uri) +{ + Directory *parent = directory_make_uri_parent_checked(uri); + if (parent == nullptr) + return; + + const char *name = PathTraitsUTF8::GetBase(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); +} + +bool +update_walk(const char *path, bool discard) +{ + walk_discard = discard; + modified = false; + + if (path != nullptr && !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/db/update/UpdateWalk.hxx b/src/db/update/UpdateWalk.hxx new file mode 100644 index 000000000..e908829e3 --- /dev/null +++ b/src/db/update/UpdateWalk.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/decoder/AdPlugDecoderPlugin.cxx b/src/decoder/AdPlugDecoderPlugin.cxx deleted file mode 100644 index c79fca5f9..000000000 --- a/src/decoder/AdPlugDecoderPlugin.cxx +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "util/Macros.hxx" -#include "Log.hxx" - -#include <adplug/adplug.h> -#include <adplug/emuopl.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(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 = ARRAY_SIZE(buffer) / 2; - DecoderCommand cmd; - - do { - if (!player->update()) - break; - - opl.update(buffer, frames_per_buffer); - cmd = decoder_data(decoder, nullptr, - buffer, sizeof(buffer), - 0); - } while (cmd == DecoderCommand::NONE); - - delete player; -} - -static void -adplug_scan_tag(TagType 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 DecoderPlugin 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 deleted file mode 100644 index a827fdc7d..000000000 --- a/src/decoder/AdPlugDecoderPlugin.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_ADPLUG_H -#define MPD_DECODER_ADPLUG_H - -extern const struct DecoderPlugin adplug_decoder_plugin; - -#endif diff --git a/src/decoder/AudiofileDecoderPlugin.cxx b/src/decoder/AudiofileDecoderPlugin.cxx deleted file mode 100644 index ab3557e52..000000000 --- a/src/decoder/AudiofileDecoderPlugin.cxx +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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) -{ - InputStream &is = *(InputStream *)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) -{ - InputStream &is = *(InputStream *)vfile->closure; - return is.GetSize(); -} - -static AFfileoffset -audiofile_file_tell(AFvirtualfile *vfile) -{ - InputStream &is = *(InputStream *)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) -{ - InputStream &is = *(InputStream *)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(InputStream &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(Decoder &decoder, InputStream &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 DecoderPlugin 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 deleted file mode 100644 index 5a17281b0..000000000 --- a/src/decoder/AudiofileDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 DecoderPlugin audiofile_decoder_plugin; - -#endif diff --git a/src/decoder/DecoderAPI.cxx b/src/decoder/DecoderAPI.cxx new file mode 100644 index 000000000..555e5c89b --- /dev/null +++ b/src/decoder/DecoderAPI.cxx @@ -0,0 +1,597 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "pcm/PcmConvert.hxx" +#include "AudioConfig.hxx" +#include "ReplayGainConfig.hxx" +#include "MusicChunk.hxx" +#include "MusicBuffer.hxx" +#include "MusicPipe.hxx" +#include "DecoderControl.hxx" +#include "DecoderInternal.hxx" +#include "DetachedSong.hxx" +#include "input/InputStream.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> +#include <math.h> + +void +decoder_initialized(Decoder &decoder, + const AudioFormat audio_format, + bool seekable, float total_time) +{ + DecoderControl &dc = decoder.dc; + struct audio_format_string af_string; + + assert(dc.state == DecoderState::START); + assert(dc.pipe != nullptr); + assert(decoder.convert == nullptr); + assert(decoder.stream_tag == nullptr); + assert(decoder.decoder_tag == nullptr); + 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; + + 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)); + + decoder.convert = new PcmConvert(); + + Error error; + if (!decoder.convert->Open(dc.in_audio_format, + dc.out_audio_format, + error)) + decoder.error = std::move(error); + } + + dc.Lock(); + dc.state = DecoderState::DECODE; + dc.client_cond.signal(); + dc.Unlock(); +} + +/** + * 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(Decoder &decoder) +{ + const DecoderControl &dc = decoder.dc; + assert(dc.pipe != nullptr); + + 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(Decoder &decoder) +{ + if (decoder.error.IsDefined()) + /* an error has occurred: stop the decoder plugin */ + return DecoderCommand::STOP; + + const DecoderControl &dc = decoder.dc; + assert(dc.pipe != nullptr); + + if (decoder_prepare_initial_seek(decoder)) + return DecoderCommand::SEEK; + + return dc.command; +} + +DecoderCommand +decoder_get_command(Decoder &decoder) +{ + return decoder_get_virtual_command(decoder); +} + +void +decoder_command_finished(Decoder &decoder) +{ + DecoderControl &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 != nullptr); + + if (decoder.initial_seek_running) { + assert(!decoder.seeking); + assert(decoder.chunk == nullptr); + 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 != nullptr) { + dc.buffer->Return(decoder.chunk); + decoder.chunk = nullptr; + } + + 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 Decoder & decoder) +{ + const DecoderControl &dc = decoder.dc; + + assert(dc.pipe != nullptr); + + 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(Decoder & decoder) +{ + DecoderControl &dc = decoder.dc; + + assert(dc.pipe != nullptr); + + 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 Decoder *decoder) +{ + if (decoder == nullptr) + return false; + + const DecoderControl &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(Decoder *decoder, + InputStream &is, + void *buffer, size_t length) +{ + /* XXX don't allow decoder==nullptr */ + + assert(decoder == nullptr || + decoder->dc.state == DecoderState::START || + decoder->dc.state == DecoderState::DECODE); + assert(buffer != nullptr); + + 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()); + + is.Unlock(); + + if (gcc_unlikely(nbytes == 0 && error.IsDefined())) + LogError(error); + + return nbytes; +} + +bool +decoder_read_full(Decoder *decoder, InputStream &is, + void *_buffer, size_t size) +{ + uint8_t *buffer = (uint8_t *)_buffer; + + while (size > 0) { + size_t nbytes = decoder_read(decoder, is, buffer, size); + if (nbytes == 0) + return false; + + buffer += nbytes; + size -= nbytes; + } + + return true; +} + +bool +decoder_skip(Decoder *decoder, InputStream &is, size_t size) +{ + while (size > 0) { + char buffer[1024]; + size_t nbytes = decoder_read(decoder, is, buffer, + std::min(sizeof(buffer), size)); + if (nbytes == 0) + return false; + + size -= nbytes; + } + + return true; +} + +void +decoder_timestamp(Decoder &decoder, double t) +{ + 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(Decoder &decoder, const Tag &tag) +{ + struct music_chunk *chunk; + + if (decoder.chunk != nullptr) { + /* there is a partial chunk - flush it, we want the + tag in a new chunk */ + decoder.FlushChunk(); + } + + assert(decoder.chunk == nullptr); + + chunk = decoder.GetChunk(); + if (chunk == nullptr) { + assert(decoder.dc.command != DecoderCommand::NONE); + return decoder.dc.command; + } + + chunk->tag = new Tag(tag); + return DecoderCommand::NONE; +} + +static bool +update_stream_tag(Decoder &decoder, InputStream *is) +{ + Tag *tag; + + tag = is != nullptr + ? is->LockReadTag() + : nullptr; + if (tag == nullptr) { + tag = decoder.song_tag; + if (tag == nullptr) + return false; + + /* no stream tag present - submit the song tag + instead */ + decoder.song_tag = nullptr; + } + + delete decoder.stream_tag; + decoder.stream_tag = tag; + return true; +} + +DecoderCommand +decoder_data(Decoder &decoder, + InputStream *is, + const void *data, size_t length, + uint16_t kbit_rate) +{ + DecoderControl &dc = decoder.dc; + DecoderCommand cmd; + + assert(dc.state == DecoderState::DECODE); + assert(dc.pipe != nullptr); + 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 != nullptr) { + /* 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 (decoder.convert != nullptr) { + assert(dc.in_audio_format != dc.out_audio_format); + + Error error; + data = decoder.convert->Convert(data, length, + &length, + error); + if (data == nullptr) { + /* the PCM conversion has failed - stop + playback, since we have no better way to + bail out */ + LogError(error); + return DecoderCommand::STOP; + } + } else { + assert(dc.in_audio_format == dc.out_audio_format); + } + + while (length > 0) { + struct music_chunk *chunk; + bool full; + + chunk = decoder.GetChunk(); + if (chunk == nullptr) { + assert(dc.command != DecoderCommand::NONE); + return dc.command; + } + + const auto dest = + chunk->Write(dc.out_audio_format, + decoder.timestamp - + dc.song->GetStartMS() / 1000.0, + kbit_rate); + if (dest.IsNull()) { + /* the chunk is full, flush it */ + decoder.FlushChunk(); + continue; + } + + size_t nbytes = dest.size; + assert(nbytes > 0); + + if (nbytes > length) + nbytes = length; + + /* copy the buffer */ + + memcpy(dest.data, data, nbytes); + + /* expand the music pipe chunk */ + + full = chunk->Expand(dc.out_audio_format, nbytes); + if (full) { + /* the chunk is full, flush it */ + decoder.FlushChunk(); + } + + 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(Decoder &decoder, InputStream *is, + Tag &&tag) +{ + gcc_unused const DecoderControl &dc = decoder.dc; + DecoderCommand cmd; + + assert(dc.state == DecoderState::DECODE); + assert(dc.pipe != nullptr); + + /* 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 != nullptr) { + /* 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(Decoder &decoder, + const ReplayGainInfo *replay_gain_info) +{ + if (replay_gain_info != nullptr) { + static unsigned serial; + if (++serial == 0) + serial = 1; + + if (REPLAY_GAIN_OFF != replay_gain_mode) { + ReplayGainMode rgm = replay_gain_mode; + if (rgm != REPLAY_GAIN_ALBUM) + rgm = REPLAY_GAIN_TRACK; + + const auto &tuple = replay_gain_info->tuples[rgm]; + const auto scale = + tuple.CalculateScale(replay_gain_preamp, + replay_gain_missing_preamp, + replay_gain_limit); + decoder.dc.replay_gain_db = 20.0 * log10f(scale); + } + + decoder.replay_gain_info = *replay_gain_info; + decoder.replay_gain_serial = serial; + + if (decoder.chunk != nullptr) { + /* flush the current chunk because the new + replay gain values affect the following + samples */ + decoder.FlushChunk(); + } + } else + decoder.replay_gain_serial = 0; +} + +void +decoder_mixramp(Decoder &decoder, MixRampInfo &&mix_ramp) +{ + DecoderControl &dc = decoder.dc; + + dc.SetMixRamp(std::move(mix_ramp)); +} diff --git a/src/decoder/DecoderAPI.hxx b/src/decoder/DecoderAPI.hxx new file mode 100644 index 000000000..0a7b84371 --- /dev/null +++ b/src/decoder/DecoderAPI.hxx @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + +// IWYU pragma: begin_exports + +#include "check.h" +#include "DecoderCommand.hxx" +#include "DecoderPlugin.hxx" +#include "ReplayGainInfo.hxx" +#include "tag/Tag.hxx" +#include "AudioFormat.hxx" +#include "MixRampInfo.hxx" +#include "config/ConfigData.hxx" + +// IWYU pragma: end_exports + +/** + * 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(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 + */ +gcc_pure +DecoderCommand +decoder_get_command(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(Decoder &decoder); + +/** + * Call this when you have received the DecoderCommand::SEEK command. + * + * @param decoder the decoder object + * @return the destination position for the week + */ +gcc_pure +double +decoder_seek_where(Decoder &decoder); + +/** + * Call this instead of decoder_command_finished() when seeking has + * failed. + * + * @param decoder the decoder object + */ +void +decoder_seek_error(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(Decoder *decoder, InputStream &is, + void *buffer, size_t length); + +static inline size_t +decoder_read(Decoder &decoder, InputStream &is, + void *buffer, size_t length) +{ + return decoder_read(&decoder, is, buffer, length); +} + +/** + * Blocking read from the input stream. Attempts to fill the buffer + * completely; there is no partial result. + * + * @return true on success, false on error or command or not enough + * data + */ +bool +decoder_read_full(Decoder *decoder, InputStream &is, + void *buffer, size_t size); + +/** + * Skip data on the #InputStream. + * + * @return true on success, false on error or command + */ +bool +decoder_skip(Decoder *decoder, InputStream &is, size_t size); + +/** + * 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(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(Decoder &decoder, InputStream *is, + const void *data, size_t length, + uint16_t kbit_rate); + +static inline DecoderCommand +decoder_data(Decoder &decoder, InputStream &is, + const void *data, size_t length, + uint16_t kbit_rate) +{ + return decoder_data(decoder, &is, data, length, 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(Decoder &decoder, InputStream *is, Tag &&tag); + +static inline DecoderCommand +decoder_tag(Decoder &decoder, InputStream &is, Tag &&tag) +{ + return decoder_tag(decoder, &is, std::move(tag)); +} + +/** + * Set replay gain values for the following chunks. + * + * @param decoder the decoder object + * @param rgi the replay_gain_info object; may be nullptr to invalidate + * the previous replay gain values + */ +void +decoder_replay_gain(Decoder &decoder, + const ReplayGainInfo *replay_gain_info); + +/** + * Store MixRamp tags. + * + * @param decoder the decoder object + * @param mixramp_start the mixramp_start tag; may be nullptr to invalidate + * @param mixramp_end the mixramp_end tag; may be nullptr to invalidate + */ +void +decoder_mixramp(Decoder &decoder, MixRampInfo &&mix_ramp); + +#endif diff --git a/src/decoder/DecoderBuffer.cxx b/src/decoder/DecoderBuffer.cxx new file mode 100644 index 000000000..47671513e --- /dev/null +++ b/src/decoder/DecoderBuffer.cxx @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "util/ConstBuffer.hxx" +#include "util/VarSize.hxx" + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + +struct DecoderBuffer { + Decoder *decoder; + InputStream *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 *_decoder, InputStream &_is, + size_t _size) + :decoder(_decoder), is(&_is), + size(_size), length(0), consumed(0) {} +}; + +DecoderBuffer * +decoder_buffer_new(Decoder *decoder, InputStream &is, + size_t size) +{ + assert(size > 0); + + return NewVarSize<DecoderBuffer>(sizeof(DecoderBuffer::data), + size, + decoder, is, size); +} + +void +decoder_buffer_free(DecoderBuffer *buffer) +{ + assert(buffer != nullptr); + + DeleteVarSize(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; +} + +void +decoder_buffer_clear(DecoderBuffer *buffer) +{ + buffer->length = buffer->consumed = 0; +} + +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; +} + +ConstBuffer<void> +decoder_buffer_read(const DecoderBuffer *buffer) +{ + return { + buffer->data + buffer->consumed, + buffer->length - 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) +{ + bool success; + + /* this could probably be optimized by seeking */ + + while (true) { + auto data = decoder_buffer_read(buffer); + if (!data.IsEmpty()) { + if (data.size > nbytes) + data.size = nbytes; + decoder_buffer_consume(buffer, data.size); + nbytes -= data.size; + if (nbytes == 0) + return true; + } + + success = decoder_buffer_fill(buffer); + if (!success) + return false; + } +} diff --git a/src/decoder/DecoderBuffer.hxx b/src/decoder/DecoderBuffer.hxx new file mode 100644 index 000000000..4cadd7740 --- /dev/null +++ b/src/decoder/DecoderBuffer.hxx @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.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 DecoderBuffer; + +struct Decoder; +struct InputStream; + +template<typename T> struct ConstBuffer; + +/** + * Creates a new buffer. + * + * @param decoder the decoder object, used for decoder_read(), may be nullptr + * @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(Decoder *decoder, InputStream &is, + size_t size); + +/** + * Frees resources used by the decoder_buffer object. + */ +void +decoder_buffer_free(DecoderBuffer *buffer); + +gcc_pure +bool +decoder_buffer_is_empty(const DecoderBuffer *buffer); + +gcc_pure +bool +decoder_buffer_is_full(const DecoderBuffer *buffer); + +void +decoder_buffer_clear(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 + */ +gcc_pure +ConstBuffer<void> +decoder_buffer_read(const DecoderBuffer *buffer); + +/** + * 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/decoder/DecoderCommand.hxx b/src/decoder/DecoderCommand.hxx new file mode 100644 index 000000000..a00519644 --- /dev/null +++ b/src/decoder/DecoderCommand.hxx @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/decoder/DecoderControl.cxx b/src/decoder/DecoderControl.cxx new file mode 100644 index 000000000..d78fc66c9 --- /dev/null +++ b/src/decoder/DecoderControl.cxx @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "DetachedSong.hxx" + +#include <assert.h> + +DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond) + :mutex(_mutex), client_cond(_client_cond), + state(DecoderState::STOP), + command(DecoderCommand::NONE), + client_is_waiting(false), + song(nullptr), + replay_gain_db(0), replay_gain_prev_db(0) {} + +DecoderControl::~DecoderControl() +{ + ClearError(); + + delete song; +} + +void +DecoderControl::WaitForDecoder() +{ + assert(!client_is_waiting); + client_is_waiting = true; + + client_cond.wait(mutex); + + assert(client_is_waiting); + client_is_waiting = false; +} + +bool +DecoderControl::IsCurrentSong(const DetachedSong &_song) const +{ + switch (state) { + case DecoderState::STOP: + case DecoderState::ERROR: + return false; + + case DecoderState::START: + case DecoderState::DECODE: + return song->IsSame(_song); + } + + assert(false); + gcc_unreachable(); +} + +void +DecoderControl::Start(DetachedSong *_song, + unsigned _start_ms, unsigned _end_ms, + MusicBuffer &_buffer, MusicPipe &_pipe) +{ + assert(_song != nullptr); + assert(_pipe.IsEmpty()); + + delete song; + song = _song; + start_ms = _start_ms; + end_ms = _end_ms; + buffer = &_buffer; + pipe = &_pipe; + + LockSynchronousCommand(DecoderCommand::START); +} + +void +DecoderControl::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 +DecoderControl::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 +DecoderControl::Quit() +{ + assert(thread.IsDefined()); + + quit = true; + LockAsynchronousCommand(DecoderCommand::STOP); + + thread.Join(); +} + +void +DecoderControl::CycleMixRamp() +{ + previous_mix_ramp = std::move(mix_ramp); + mix_ramp.Clear(); +} diff --git a/src/decoder/DecoderControl.hxx b/src/decoder/DecoderControl.hxx new file mode 100644 index 000000000..4e5c43b5a --- /dev/null +++ b/src/decoder/DecoderControl.hxx @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "MixRampInfo.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "thread/Thread.hxx" +#include "util/Error.hxx" + +#include <assert.h> +#include <stdint.h> + +/* damn you, windows.h! */ +#ifdef ERROR +#undef ERROR +#endif + +class DetachedSong; +class MusicBuffer; +class MusicPipe; + +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 DecoderControl { + /** + * The handle of the decoder thread. + */ + Thread thread; + + /** + * This lock protects #state and #command. + * + * This is usually a reference to PlayerControl::mutex, so + * that both player thread and decoder thread share a mutex. + * This simplifies synchronization with #cond and + * #client_cond. + */ + 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. + * + * This is usually a reference to PlayerControl::cond. + */ + 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; + + /** + * Is the client currently waiting for the DecoderThread? If + * false, the DecoderThread may omit invoking Cond::signal(), + * reducing the number of system calls. + */ + bool client_is_waiting; + + 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. + */ + DetachedSong *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; + + MixRampInfo mix_ramp, previous_mix_ramp; + + /** + * @param _mutex see #mutex + * @param _client_cond see #client_cond + */ + DecoderControl(Mutex &_mutex, Cond &_client_cond); + ~DecoderControl(); + + /** + * 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 #DecoderControl 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. + * + * Caller must hold the lock. + */ + void WaitForDecoder(); + + 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 DetachedSong &_song) const; + + gcc_pure + bool LockIsCurrentSong(const DetachedSong &_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 #DecoderControl + * @param end_ms see #DecoderControl + * @param pipe the pipe which receives the decoded chunks (owned by + * the caller) + */ + void Start(DetachedSong *song, unsigned start_ms, unsigned end_ms, + MusicBuffer &buffer, MusicPipe &pipe); + + void Stop(); + + bool Seek(double where); + + void Quit(); + + const char *GetMixRampStart() const { + return mix_ramp.GetStart(); + } + + const char *GetMixRampEnd() const { + return mix_ramp.GetEnd(); + } + + const char *GetMixRampPreviousEnd() const { + return previous_mix_ramp.GetEnd(); + } + + void SetMixRamp(MixRampInfo &&new_value) { + mix_ramp = std::move(new_value); + } + + /** + * Move mixramp_end to mixramp_prev_end and clear + * mixramp_start/mixramp_end. + */ + void CycleMixRamp(); +}; + +#endif diff --git a/src/decoder/DecoderError.cxx b/src/decoder/DecoderError.cxx new file mode 100644 index 000000000..bd3842837 --- /dev/null +++ b/src/decoder/DecoderError.cxx @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License 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/decoder/DecoderError.hxx b/src/decoder/DecoderError.hxx new file mode 100644 index 000000000..83cf98204 --- /dev/null +++ b/src/decoder/DecoderError.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/decoder/DecoderInternal.cxx b/src/decoder/DecoderInternal.cxx new file mode 100644 index 000000000..b50fee185 --- /dev/null +++ b/src/decoder/DecoderInternal.cxx @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "pcm/PcmConvert.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); + + if (convert != nullptr) { + convert->Close(); + delete convert; + } + + 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(DecoderControl &dc) +{ + if (dc.command == DecoderCommand::NONE) + dc.Wait(); + + return dc.command; +} + +struct music_chunk * +Decoder::GetChunk() +{ + DecoderCommand cmd; + + if (chunk != nullptr) + return chunk; + + do { + chunk = dc.buffer->Allocate(); + if (chunk != nullptr) { + chunk->replay_gain_serial = replay_gain_serial; + if (replay_gain_serial != 0) + chunk->replay_gain_info = replay_gain_info; + + return chunk; + } + + dc.Lock(); + cmd = need_chunks(dc); + dc.Unlock(); + } while (cmd == DecoderCommand::NONE); + + return nullptr; +} + +void +Decoder::FlushChunk() +{ + assert(chunk != nullptr); + + if (chunk->IsEmpty()) + dc.buffer->Return(chunk); + else + dc.pipe->Push(chunk); + + chunk = nullptr; + + dc.Lock(); + if (dc.client_is_waiting) + dc.client_cond.signal(); + dc.Unlock(); +} diff --git a/src/decoder/DecoderInternal.hxx b/src/decoder/DecoderInternal.hxx new file mode 100644 index 000000000..bef6f6c13 --- /dev/null +++ b/src/decoder/DecoderInternal.hxx @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "ReplayGainInfo.hxx" +#include "util/Error.hxx" + +class PcmConvert; +struct DecoderControl; +struct Tag; + +struct Decoder { + DecoderControl &dc; + + /** + * For converting input data to the configured audio format. + * nullptr means no conversion necessary. + */ + PcmConvert *convert; + + /** + * 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; + + ReplayGainInfo replay_gain_info; + + /** + * A positive serial number for checking if replay gain info + * has changed since the last check. + */ + unsigned replay_gain_serial; + + /** + * An error has occurred (in DecoderAPI.cxx), and the plugin + * will be asked to stop. + */ + Error error; + + Decoder(DecoderControl &_dc, bool _initial_seek_pending, Tag *_tag) + :dc(_dc), + convert(nullptr), + 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 + */ + music_chunk *GetChunk(); + + /** + * Flushes the current chunk. + * + * Caller must not lock the #DecoderControl object. + */ + void FlushChunk(); +}; + +#endif diff --git a/src/decoder/DecoderList.cxx b/src/decoder/DecoderList.cxx new file mode 100644 index 000000000..5d9d44d34 --- /dev/null +++ b/src/decoder/DecoderList.cxx @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "config/ConfigGlobal.hxx" +#include "config/ConfigData.hxx" +#include "plugins/AudiofileDecoderPlugin.hxx" +#include "plugins/PcmDecoderPlugin.hxx" +#include "plugins/DsdiffDecoderPlugin.hxx" +#include "plugins/DsfDecoderPlugin.hxx" +#include "plugins/FlacDecoderPlugin.h" +#include "plugins/OpusDecoderPlugin.h" +#include "plugins/VorbisDecoderPlugin.h" +#include "plugins/AdPlugDecoderPlugin.h" +#include "plugins/WavpackDecoderPlugin.hxx" +#include "plugins/FfmpegDecoderPlugin.hxx" +#include "plugins/GmeDecoderPlugin.hxx" +#include "plugins/FaadDecoderPlugin.hxx" +#include "plugins/MadDecoderPlugin.hxx" +#include "plugins/SndfileDecoderPlugin.hxx" +#include "plugins/Mpg123DecoderPlugin.hxx" +#include "plugins/WildmidiDecoderPlugin.hxx" +#include "plugins/MikmodDecoderPlugin.hxx" +#include "plugins/ModplugDecoderPlugin.hxx" +#include "plugins/MpcdecDecoderPlugin.hxx" +#include "plugins/FluidsynthDecoderPlugin.hxx" +#include "plugins/SidplayDecoderPlugin.hxx" +#include "util/Macros.hxx" + +#include <string.h> + +const struct DecoderPlugin *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, + nullptr +}; + +static constexpr unsigned num_decoder_plugins = + ARRAY_SIZE(decoder_plugins) - 1; + +/** which plugins have been initialized successfully? */ +bool decoder_plugins_enabled[num_decoder_plugins]; + +const struct DecoderPlugin * +decoder_plugin_from_name(const char *name) +{ + return decoder_plugins_find([=](const DecoderPlugin &plugin){ + return strcmp(plugin.name, name) == 0; + }); +} + +void decoder_plugin_init_all(void) +{ + struct config_param empty; + + for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) { + const DecoderPlugin &plugin = *decoder_plugins[i]; + const struct config_param *param = + config_find_block(CONF_DECODER, "plugin", plugin.name); + + if (param == nullptr) + param = ∅ + else if (!param->GetBlockValue("enabled", true)) + /* the plugin is disabled in mpd.conf */ + continue; + + if (plugin.Init(*param)) + decoder_plugins_enabled[i] = true; + } +} + +void decoder_plugin_deinit_all(void) +{ + decoder_plugins_for_each_enabled([=](const DecoderPlugin &plugin){ + plugin.Finish(); + }); +} + +bool +decoder_plugins_supports_suffix(const char *suffix) +{ + return decoder_plugins_try([suffix](const DecoderPlugin &plugin){ + return plugin.SupportsSuffix(suffix); + }); +} diff --git a/src/decoder/DecoderList.hxx b/src/decoder/DecoderList.hxx new file mode 100644 index 000000000..47085d4ae --- /dev/null +++ b/src/decoder/DecoderList.hxx @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 + +#include "Compiler.h" + +struct DecoderPlugin; + +extern const struct DecoderPlugin *const decoder_plugins[]; +extern bool decoder_plugins_enabled[]; + +/* interface for using plugins */ + +gcc_pure +const struct DecoderPlugin * +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); + +template<typename F> +static inline const DecoderPlugin * +decoder_plugins_find(F f) +{ + for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) + if (decoder_plugins_enabled[i] && f(*decoder_plugins[i])) + return decoder_plugins[i]; + + return nullptr; +} + +template<typename F> +static inline bool +decoder_plugins_try(F f) +{ + for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) + if (decoder_plugins_enabled[i] && f(*decoder_plugins[i])) + return true; + + return false; +} + +template<typename F> +static inline void +decoder_plugins_for_each(F f) +{ + for (auto i = decoder_plugins; *i != nullptr; ++i) + f(**i); +} + +template<typename F> +static inline void +decoder_plugins_for_each_enabled(F f) +{ + for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) + if (decoder_plugins_enabled[i]) + f(*decoder_plugins[i]); +} + +/** + * Is there at least once #DecoderPlugin that supports the specified + * file name suffix? + */ +gcc_pure gcc_nonnull_all +bool +decoder_plugins_supports_suffix(const char *suffix); + +#endif diff --git a/src/decoder/DecoderPlugin.cxx b/src/decoder/DecoderPlugin.cxx new file mode 100644 index 000000000..3be812c3b --- /dev/null +++ b/src/decoder/DecoderPlugin.cxx @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 +DecoderPlugin::SupportsSuffix(const char *suffix) const +{ + assert(suffix != nullptr); + + return suffixes != nullptr && string_array_contains(suffixes, suffix); + +} + +bool +DecoderPlugin::SupportsMimeType(const char *mime_type) const +{ + assert(mime_type != nullptr); + + return mime_types != nullptr && + string_array_contains(mime_types, mime_type); +} diff --git a/src/decoder/DecoderPlugin.hxx b/src/decoder/DecoderPlugin.hxx new file mode 100644 index 000000000..3155be04b --- /dev/null +++ b/src/decoder/DecoderPlugin.hxx @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 + +#include "Compiler.h" + +struct config_param; +struct InputStream; +struct tag_handler; + +/** + * Opaque handle which the decoder plugin passes to the functions in + * this header. + */ +struct Decoder; + +struct DecoderPlugin { + 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)(Decoder &decoder, InputStream &is); + + /** + * Decode a local file. + * + * Either implement this method or stream_decode(). + */ + void (*file_decode)(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)(InputStream &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 + */ + bool Init(const config_param ¶m) const { + return init != nullptr + ? init(param) + : true; + } + + /** + * Deinitialize a decoder plugin which was initialized successfully. + */ + void Finish() const { + if (finish != nullptr) + finish(); + } + + /** + * Decode a stream. + */ + void StreamDecode(Decoder &decoder, InputStream &is) const { + stream_decode(decoder, is); + } + + /** + * Decode a file. + */ + void FileDecode(Decoder &decoder, const char *path_fs) const { + file_decode(decoder, path_fs); + } + + /** + * Read the tag of a file. + */ + bool ScanFile(const char *path_fs, + const tag_handler &handler, void *handler_ctx) const { + return scan_file != nullptr + ? scan_file(path_fs, &handler, handler_ctx) + : false; + } + + /** + * Read the tag of a stream. + */ + bool ScanStream(InputStream &is, + const tag_handler &handler, void *handler_ctx) const { + return scan_stream != nullptr + ? scan_stream(is, &handler, handler_ctx) + : false; + } + + /** + * return "virtual" tracks in a container + */ + char *ContainerScan(const char *path, const unsigned int tnum) const { + return container_scan(path, tnum); + } + + /** + * Does the plugin announce the specified file name suffix? + */ + gcc_pure gcc_nonnull_all + bool SupportsSuffix(const char *suffix) const; + + /** + * Does the plugin announce the specified MIME type? + */ + gcc_pure gcc_nonnull_all + bool SupportsMimeType(const char *mime_type) const; +}; + +#endif diff --git a/src/decoder/DecoderPrint.cxx b/src/decoder/DecoderPrint.cxx new file mode 100644 index 000000000..54b89c36c --- /dev/null +++ b/src/decoder/DecoderPrint.cxx @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/Client.hxx" + +#include <functional> + +#include <assert.h> + +static void +decoder_plugin_print(Client &client, + const DecoderPlugin &plugin) +{ + const char *const*p; + + assert(plugin.name != nullptr); + + client_printf(client, "plugin: %s\n", plugin.name); + + if (plugin.suffixes != nullptr) + for (p = plugin.suffixes; *p != nullptr; ++p) + client_printf(client, "suffix: %s\n", *p); + + if (plugin.mime_types != nullptr) + for (p = plugin.mime_types; *p != nullptr; ++p) + client_printf(client, "mime_type: %s\n", *p); +} + +void +decoder_list_print(Client &client) +{ + using namespace std::placeholders; + const auto f = std::bind(decoder_plugin_print, std::ref(client), _1); + decoder_plugins_for_each_enabled(f); +} diff --git a/src/decoder/DecoderPrint.hxx b/src/decoder/DecoderPrint.hxx new file mode 100644 index 000000000..695bd099d --- /dev/null +++ b/src/decoder/DecoderPrint.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/decoder/DecoderThread.cxx b/src/decoder/DecoderThread.cxx new file mode 100644 index 000000000..3301a2564 --- /dev/null +++ b/src/decoder/DecoderThread.cxx @@ -0,0 +1,476 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "DetachedSong.hxx" +#include "system/FatalError.hxx" +#include "Mapper.hxx" +#include "fs/Traits.hxx" +#include "fs/AllocatedPath.hxx" +#include "DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "DecoderList.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "thread/Name.hxx" +#include "tag/ApeReplayGain.hxx" +#include "Log.hxx" + +#include <functional> + +static constexpr Domain decoder_thread_domain("decoder_thread"); + +/** + * Marks the current decoder command as "finished" and notifies the + * player thread. + * + * @param dc the #DecoderControl object; must be locked + */ +static void +decoder_command_finished_locked(DecoderControl &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, nullptr on error + */ +static InputStream * +decoder_input_stream_open(DecoderControl &dc, const char *uri) +{ + Error error; + + InputStream *is = InputStream::Open(uri, dc.mutex, dc.cond, error); + if (is == nullptr) { + if (error.IsDefined()) + LogError(error); + + return nullptr; + } + + /* 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 nullptr; + } + + dc.Unlock(); + + return is; +} + +static bool +decoder_stream_decode(const DecoderPlugin &plugin, + Decoder &decoder, + InputStream &input_stream) +{ + assert(plugin.stream_decode != nullptr); + assert(decoder.stream_tag == nullptr); + assert(decoder.decoder_tag == nullptr); + 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.Rewind(IgnoreError()); + + decoder.dc.Unlock(); + + FormatThreadName("decoder:%s", plugin.name); + + plugin.StreamDecode(decoder, input_stream); + + SetThreadName("decoder"); + + 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 DecoderPlugin &plugin, + Decoder &decoder, const char *path) +{ + assert(plugin.file_decode != nullptr); + assert(decoder.stream_tag == nullptr); + assert(decoder.decoder_tag == nullptr); + assert(path != nullptr); + assert(PathTraitsFS::IsAbsolute(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(); + + FormatThreadName("decoder:%s", plugin.name); + + plugin.FileDecode(decoder, path); + + SetThreadName("decoder"); + + decoder.dc.Lock(); + + assert(decoder.dc.state == DecoderState::START || + decoder.dc.state == DecoderState::DECODE); + + return decoder.dc.state != DecoderState::START; +} + +gcc_pure +static bool +decoder_check_plugin_mime(const DecoderPlugin &plugin, const InputStream &is) +{ + assert(plugin.stream_decode != nullptr); + + return !is.mime.empty() && plugin.SupportsMimeType(is.mime.c_str()); +} + +gcc_pure +static bool +decoder_check_plugin_suffix(const DecoderPlugin &plugin, const char *suffix) +{ + assert(plugin.stream_decode != nullptr); + + return suffix != nullptr && plugin.SupportsSuffix(suffix); +} + +gcc_pure +static bool +decoder_check_plugin(const DecoderPlugin &plugin, const InputStream &is, + const char *suffix) +{ + return plugin.stream_decode != nullptr && + (decoder_check_plugin_mime(plugin, is) || + decoder_check_plugin_suffix(plugin, suffix)); +} + +static bool +decoder_run_stream_plugin(Decoder &decoder, InputStream &is, + const char *suffix, + const DecoderPlugin &plugin, + bool &tried_r) +{ + if (!decoder_check_plugin(plugin, is, suffix)) + return false; + + tried_r = true; + return decoder_stream_decode(plugin, decoder, is); +} + +static bool +decoder_run_stream_locked(Decoder &decoder, InputStream &is, + const char *uri, bool &tried_r) +{ + const char *const suffix = uri_get_suffix(uri); + + using namespace std::placeholders; + const auto f = std::bind(decoder_run_stream_plugin, + std::ref(decoder), std::ref(is), suffix, + _1, std::ref(tried_r)); + return decoder_plugins_try(f); +} + +/** + * Try decoding a stream, using the fallback plugin. + */ +static bool +decoder_run_stream_fallback(Decoder &decoder, InputStream &is) +{ + const struct DecoderPlugin *plugin; + + plugin = decoder_plugin_from_name("mad"); + return plugin != nullptr && plugin->stream_decode != nullptr && + decoder_stream_decode(*plugin, decoder, is); +} + +/** + * Try decoding a stream. + */ +static bool +decoder_run_stream(Decoder &decoder, const char *uri) +{ + DecoderControl &dc = decoder.dc; + InputStream *input_stream; + bool success; + + dc.Unlock(); + + input_stream = decoder_input_stream_open(dc, uri); + if (input_stream == nullptr) { + dc.Lock(); + return false; + } + + dc.Lock(); + + bool tried = false; + success = dc.command == DecoderCommand::STOP || + decoder_run_stream_locked(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 && + decoder_run_stream_fallback(decoder, *input_stream)); + + 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(Decoder &decoder, const char *path_fs) +{ + ReplayGainInfo info; + if (replay_gain_ape_read(Path::FromFS(path_fs), info)) + decoder_replay_gain(decoder, &info); +} + +static bool +TryDecoderFile(Decoder &decoder, const char *path_fs, const char *suffix, + const DecoderPlugin &plugin) +{ + if (!plugin.SupportsSuffix(suffix)) + return false; + + DecoderControl &dc = decoder.dc; + + if (plugin.file_decode != nullptr) { + dc.Lock(); + + if (decoder_file_decode(plugin, decoder, path_fs)) + return true; + + dc.Unlock(); + } else if (plugin.stream_decode != nullptr) { + InputStream *input_stream = + decoder_input_stream_open(dc, path_fs); + if (input_stream == nullptr) + return false; + + dc.Lock(); + + bool success = decoder_stream_decode(plugin, decoder, + *input_stream); + + dc.Unlock(); + + input_stream->Close(); + + if (success) { + dc.Lock(); + return true; + } + } + + return false; +} + +/** + * Try decoding a file. + */ +static bool +decoder_run_file(Decoder &decoder, const char *path_fs) +{ + const char *suffix = uri_get_suffix(path_fs); + if (suffix == nullptr) + return false; + + DecoderControl &dc = decoder.dc; + dc.Unlock(); + + decoder_load_replay_gain(decoder, path_fs); + + if (decoder_plugins_try([&decoder, path_fs, suffix](const DecoderPlugin &plugin){ + return TryDecoderFile(decoder, path_fs, suffix, + plugin); + })) + return true; + + dc.Lock(); + return false; +} + +static void +decoder_run_song(DecoderControl &dc, + const DetachedSong &song, const char *uri) +{ + Decoder decoder(dc, dc.start_ms > 0, + new Tag(song.GetTag())); + 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 != nullptr) + decoder.FlushChunk(); + + dc.Lock(); + + if (decoder.error.IsDefined()) { + /* copy the Error from sruct Decoder to + DecoderControl */ + dc.state = DecoderState::ERROR; + dc.error = std::move(decoder.error); + } else if (ret) + dc.state = DecoderState::STOP; + else { + dc.state = DecoderState::ERROR; + + const char *error_uri = song.GetURI(); + const std::string allocated = uri_remove_auth(error_uri); + if (!allocated.empty()) + error_uri = allocated.c_str(); + + dc.error.Format(decoder_domain, + "Failed to decode %s", error_uri); + } + + dc.client_cond.signal(); +} + +static void +decoder_run(DecoderControl &dc) +{ + dc.ClearError(); + + assert(dc.song != nullptr); + const DetachedSong &song = *dc.song; + + const std::string uri = song.IsFile() + ? map_song_fs(song).c_str() + : song.GetRealURI(); + + if (uri.empty()) { + 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.c_str()); + +} + +static void +decoder_task(void *arg) +{ + DecoderControl &dc = *(DecoderControl *)arg; + + SetThreadName("decoder"); + + dc.Lock(); + + do { + assert(dc.state == DecoderState::STOP || + dc.state == DecoderState::ERROR); + + switch (dc.command) { + case DecoderCommand::START: + dc.CycleMixRamp(); + 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(); +} + +void +decoder_thread_start(DecoderControl &dc) +{ + assert(!dc.thread.IsDefined()); + + dc.quit = false; + + Error error; + if (!dc.thread.Start(decoder_task, &dc, error)) + FatalError(error); +} diff --git a/src/decoder/DecoderThread.hxx b/src/decoder/DecoderThread.hxx new file mode 100644 index 000000000..d5fde281c --- /dev/null +++ b/src/decoder/DecoderThread.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderControl; + +void +decoder_thread_start(DecoderControl &dc); + +#endif diff --git a/src/decoder/DsdLib.cxx b/src/decoder/DsdLib.cxx deleted file mode 100644 index 67cc7e945..000000000 --- a/src/decoder/DsdLib.cxx +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 -DsdId::Equals(const char *s) const -{ - assert(s != nullptr); - assert(strlen(s) == sizeof(value)); - - return memcmp(value, s, sizeof(value)) == 0; -} - -bool -dsdlib_read(Decoder *decoder, InputStream &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(Decoder *decoder, InputStream &is, - int64_t 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() < (int64_t)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(Decoder *decoder, InputStream &is, - int64_t 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 ((int64_t)length > delta) - length = delta; - - size_t nbytes = decoder_read(decoder, is, buffer, length); - if (nbytes == 0) - return false; - - delta -= nbytes; - } - - return true; -} - -#ifdef HAVE_ID3TAG -void -dsdlib_tag_id3(InputStream &is, - const struct tag_handler *handler, - void *handler_ctx, int64_t 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 auto size = is.GetSize(); - const auto 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 deleted file mode 100644 index 53160cf1e..000000000 --- a/src/decoder/DsdLib.hxx +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "system/ByteOrder.hxx" -#include "Compiler.h" - -#include <stddef.h> -#include <stdint.h> - -struct Decoder; -struct InputStream; - -struct DsdId { - char value[4]; - - gcc_pure - bool Equals(const char *s) const; -}; - -class DsdUint64 { - uint32_t lo; - uint32_t hi; - -public: - constexpr uint64_t Read() const { - return (uint64_t(FromLE32(hi)) << 32) | - uint64_t(FromLE32(lo)); - } -}; - -class DffDsdUint64 { - uint32_t hi; - uint32_t lo; - -public: - constexpr uint64_t Read() const { - return (uint64_t(FromBE32(hi)) << 32) | - uint64_t(FromBE32(lo)); - } -}; - -bool -dsdlib_read(Decoder *decoder, InputStream &is, - void *data, size_t length); - -bool -dsdlib_skip_to(Decoder *decoder, InputStream &is, - int64_t offset); - -bool -dsdlib_skip(Decoder *decoder, InputStream &is, - int64_t delta); - -/** - * Add tags from ID3 tag. All tags commonly found in the ID3 tags of - * DSF and DSDIFF files are imported - */ -void -dsdlib_tag_id3(InputStream &is, - const struct tag_handler *handler, - void *handler_ctx, int64_t tagoffset); - -#endif diff --git a/src/decoder/DsdiffDecoderPlugin.cxx b/src/decoder/DsdiffDecoderPlugin.cxx deleted file mode 100644 index a3c0149b9..000000000 --- a/src/decoder/DsdiffDecoderPlugin.cxx +++ /dev/null @@ -1,526 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 "system/ByteOrder.hxx" -#include "tag/TagHandler.hxx" -#include "DsdLib.hxx" -#include "Log.hxx" - -#include <unistd.h> -#include <stdio.h> /* for SEEK_SET, SEEK_CUR */ - -struct DsdiffHeader { - DsdId id; - DffDsdUint64 size; - DsdId format; -}; - -struct DsdiffChunkHeader { - DsdId id; - DffDsdUint64 size; - - /** - * Read the "size" attribute from the specified header, converting it - * to the host byte order if needed. - */ - constexpr - uint64_t GetSize() const { - return size.Read(); - } -}; - -/** 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 - InputStream::offset_type id3_offset; - uint64_t id3_size; -#endif - /** offset for artist tag */ - InputStream::offset_type diar_offset; - /** offset for title tag */ - InputStream::offset_type 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(Decoder *decoder, InputStream &is, - DsdId *id) -{ - return dsdlib_read(decoder, is, id, sizeof(*id)); -} - -static bool -dsdiff_read_chunk_header(Decoder *decoder, InputStream &is, - DsdiffChunkHeader *header) -{ - return dsdlib_read(decoder, is, header, sizeof(*header)); -} - -static bool -dsdiff_read_payload(Decoder *decoder, InputStream &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(Decoder *decoder, InputStream &is, - DsdiffMetaData *metadata, - InputStream::offset_type end_offset) -{ - DsdiffChunkHeader header; - while ((InputStream::offset_type)(is.GetOffset() + sizeof(header)) <= end_offset) { - if (!dsdiff_read_chunk_header(decoder, is, &header)) - return false; - - InputStream::offset_type chunk_end_offset = is.GetOffset() - + header.GetSize(); - if (chunk_end_offset > end_offset) - return false; - - if (header.id.Equals("FS ")) { - uint32_t sample_rate; - if (!dsdiff_read_payload(decoder, is, &header, - &sample_rate, - sizeof(sample_rate))) - return false; - - metadata->sample_rate = FromBE32(sample_rate); - } else if (header.id.Equals("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 = FromBE16(channels); - } else if (header.id.Equals("CMPR")) { - DsdId type; - if (header.GetSize() < sizeof(type) || - !dsdlib_read(decoder, is, - &type, sizeof(type)) || - !dsdlib_skip_to(decoder, is, chunk_end_offset)) - return false; - - if (!type.Equals("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(Decoder *decoder, InputStream &is, - DsdiffMetaData *metadata, - const DsdiffChunkHeader *prop_header) -{ - uint64_t prop_size = prop_header->GetSize(); - InputStream::offset_type end_offset = is.GetOffset() + prop_size; - - DsdId prop_id; - if (prop_size < sizeof(prop_id) || - !dsdiff_read_id(decoder, is, &prop_id)) - return false; - - if (prop_id.Equals("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(InputStream &is, - const struct tag_handler *handler, - void *handler_ctx, InputStream::offset_type tagoffset, - TagType 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 = FromBE32(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(Decoder *decoder, InputStream &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 auto size = is.GetSize(); - while (is.GetOffset() < size) { - uint64_t chunk_size = chunk_header->GetSize(); - - /* DIIN chunk, is directly followed by other chunks */ - if (chunk_header->id.Equals("DIIN")) - chunk_size = 0; - - /* DIAR chunk - DSDIFF native tag for Artist */ - if (chunk_header->id.Equals("DIAR")) { - chunk_size = chunk_header->GetSize(); - metadata->diar_offset = is.GetOffset(); - } - - /* DITI chunk - DSDIFF native tag for Title */ - if (chunk_header->id.Equals("DITI")) { - chunk_size = chunk_header->GetSize(); - metadata->diti_offset = is.GetOffset(); - } -#ifdef HAVE_ID3TAG - /* 'ID3 ' chunk, offspec. Used by sacdextract */ - if (chunk_header->id.Equals("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; - } - } - /* 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(Decoder *decoder, InputStream &is, - DsdiffMetaData *metadata, - DsdiffChunkHeader *chunk_header) -{ - DsdiffHeader header; - if (!dsdlib_read(decoder, is, &header, sizeof(header)) || - !header.id.Equals("FRM8") || - !header.format.Equals("DSD ")) - return false; - - while (true) { - if (!dsdiff_read_chunk_header(decoder, is, - chunk_header)) - return false; - - if (chunk_header->id.Equals("PROP")) { - if (!dsdiff_read_prop(decoder, is, metadata, - chunk_header)) - return false; - } else if (chunk_header->id.Equals("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(); - InputStream::offset_type 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(Decoder &decoder, InputStream &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 */ - size_t now_size = buffer_size; - if (chunk_size < (uint64_t)now_size) { - unsigned 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(Decoder &decoder, InputStream &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 (chunk_header.id.Equals("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(InputStream &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 DecoderPlugin 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 deleted file mode 100644 index be14fc9cd..000000000 --- a/src/decoder/DsdiffDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 DecoderPlugin dsdiff_decoder_plugin; - -#endif diff --git a/src/decoder/DsfDecoderPlugin.cxx b/src/decoder/DsfDecoderPlugin.cxx deleted file mode 100644 index 5ef94e647..000000000 --- a/src/decoder/DsfDecoderPlugin.cxx +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 "system/ByteOrder.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 - InputStream::offset_type id3_offset; - uint64_t id3_size; -#endif -}; - -struct DsfHeader { - /** DSF header id: "DSD " */ - DsdId id; - /** DSD chunk size, including id = 28 */ - DsdUint64 size; - /** total file size */ - DsdUint64 fsize; - /** pointer to id3v2 metadata, should be at the end of the file */ - DsdUint64 pmeta; -}; - -/** DSF file fmt chunk */ -struct DsfFmtChunk { - /** id: "fmt " */ - DsdId id; - /** fmt chunk size, including id, normally 52 */ - DsdUint64 size; - /** 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 */ - DsdUint64 scnt; - /** block size per channel = 4096 */ - uint32_t block_size; - /** reserved, should be all zero */ - uint32_t reserved; -}; - -struct DsfDataChunk { - DsdId id; - /** "data" chunk size, includes header (id+size) */ - DsdUint64 size; -}; - -/** - * Read and parse all needed metadata chunks for DSF files. - */ -static bool -dsf_read_metadata(Decoder *decoder, InputStream &is, - DsfMetaData *metadata) -{ - DsfHeader dsf_header; - if (!dsdlib_read(decoder, is, &dsf_header, sizeof(dsf_header)) || - !dsf_header.id.Equals("DSD ")) - return false; - - const uint64_t chunk_size = dsf_header.size.Read(); - if (sizeof(dsf_header) != chunk_size) - return false; - -#ifdef HAVE_ID3TAG - const uint64_t metadata_offset = dsf_header.pmeta.Read(); -#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)) || - !dsf_fmt_chunk.id.Equals("fmt ")) - return false; - - const uint64_t fmt_chunk_size = dsf_fmt_chunk.size.Read(); - if (fmt_chunk_size != sizeof(dsf_fmt_chunk)) - return false; - - uint32_t samplefreq = FromLE32(dsf_fmt_chunk.sample_freq); - - /* for now, only support version 1 of the standard, DSD raw stereo - files with a sample freq of 2822400 or 5644800 Hz */ - - if (dsf_fmt_chunk.version != 1 || dsf_fmt_chunk.formatid != 0 - || dsf_fmt_chunk.channeltype != 2 - || dsf_fmt_chunk.channelnum != 2 - || (samplefreq != 2822400 && samplefreq != 5644800)) - return false; - - uint32_t chblksize = FromLE32(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)) || - !data_chunk.id.Equals("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_chunk.size.Read(); - if (data_size < sizeof(data_chunk)) - return false; - - data_size -= sizeof(data_chunk); - - /* 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; - - /* use the sample count from the DSF header as the upper - bound, because some DSF files contain junk at the end of - the "data" chunk */ - const uint64_t samplecnt = dsf_fmt_chunk.scnt.Read(); - const uint64_t playable_size = samplecnt * 2 / 8; - if (data_size > playable_size) - data_size = playable_size; - - metadata->chunk_size = data_size; - 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 = (InputStream::offset_type)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(Decoder &decoder, InputStream &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 */ - size_t now_size = buffer_size; - if (chunk_size < (uint64_t)now_size) { - unsigned 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(Decoder &decoder, InputStream &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(InputStream &is, - gcc_unused const struct tag_handler *handler, - gcc_unused void *handler_ctx) -{ - /* check DSF metadata */ - DsfMetaData metadata; - if (!dsf_read_metadata(nullptr, 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", - nullptr -}; - -static const char *const dsf_mime_types[] = { - "application/x-dsf", - nullptr -}; - -const struct DecoderPlugin 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 deleted file mode 100644 index 921c94698..000000000 --- a/src/decoder/DsfDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 DecoderPlugin dsf_decoder_plugin; - -#endif diff --git a/src/decoder/FaadDecoderPlugin.cxx b/src/decoder/FaadDecoderPlugin.cxx deleted file mode 100644 index 9fd20167d..000000000 --- a/src/decoder/FaadDecoderPlugin.cxx +++ /dev/null @@ -1,497 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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, InputStream &is) -{ - size_t fileread; - size_t tagsize; - size_t length; - bool success; - - const auto 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(InputStream &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(InputStream &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(Decoder &mpd_decoder, InputStream &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); - decoder_buffer_free(buffer); - 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) { - FormatDefault(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) { - FormatDefault(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); - decoder_buffer_free(buffer); -} - -static bool -faad_scan_stream(InputStream &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 DecoderPlugin 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 deleted file mode 100644 index 817927d5e..000000000 --- a/src/decoder/FaadDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 DecoderPlugin faad_decoder_plugin; - -#endif diff --git a/src/decoder/FfmpegDecoderPlugin.cxx b/src/decoder/FfmpegDecoderPlugin.cxx deleted file mode 100644 index 593f42d39..000000000 --- a/src/decoder/FfmpegDecoderPlugin.cxx +++ /dev/null @@ -1,686 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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" - -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> - -#if LIBAVUTIL_VERSION_MAJOR >= 53 -#include <libavutil/frame.h> -#endif -} - -#include <assert.h> -#include <string.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 = nullptr; - - if (ptr != nullptr) - cls = *(const AVClass *const*)ptr; - - if (cls != nullptr) { - char domain[64]; - snprintf(domain, sizeof(domain), "%s/%s", - ffmpeg_domain.GetName(), cls->item_name(ptr)); - const Domain d(domain); - LogFormatV(d, import_ffmpeg_level(level), fmt, vl); - } -} - -struct AvioStream { - Decoder *const decoder; - InputStream &input; - - AVIOContext *io; - - unsigned char buffer[8192]; - - AvioStream(Decoder *_decoder, InputStream &_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; - - if (!stream->input.LockSeek(pos, whence, IgnoreError())) - 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 == nullptr) - return AVERROR(ENOMEM); - - context->pb = pb; - *ic_ptr = context; - return avformat_open_input(ic_ptr, filename, fmt, nullptr); -} - -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 (data_size <= 0) - return data_size; - - 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(Decoder &decoder, InputStream &is, - const AVPacket *packet, - AVCodecContext *codec_context, - const AVStream *stream, - 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 - stream->start_time, - stream->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 */ - LogDefault(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 != nullptr) - 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(Decoder *decoder, InputStream &is) -{ - enum { - BUFFER_SIZE = 16384, - PADDING = 16, - }; - - unsigned char buffer[BUFFER_SIZE]; - size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE); - if (nbytes <= PADDING || !is.LockRewind(IgnoreError())) - return nullptr; - - /* 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(); - - return av_probe_input_format(&avpd, true); -} - -static void -ffmpeg_decode(Decoder &decoder, InputStream &input) -{ - AVInputFormat *input_format = ffmpeg_probe(&decoder, input); - if (input_format == nullptr) - 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 = nullptr; - 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, nullptr); - 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, nullptr); - 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); - -#if LIBAVUTIL_VERSION_MAJOR >= 53 - AVFrame *frame = av_frame_alloc(); -#else - AVFrame *frame = avcodec_alloc_frame(); -#endif - if (!frame) { - LogError(ffmpeg_domain, "Could not allocate frame"); - avformat_close_input(&format_context); - return; - } - - uint8_t *interleaved_buffer = nullptr; - 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, - 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) + - av_stream->start_time; - - 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 LIBAVUTIL_VERSION_MAJOR >= 53 - av_frame_free(&frame); -#elif 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(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - AVInputFormat *input_format = ffmpeg_probe(nullptr, is); - if (input_format == nullptr) - return false; - - AvioStream stream(nullptr, is); - if (!stream.Open()) - return false; - - AVFormatContext *f = nullptr; - 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, nullptr); - 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", - nullptr -}; - -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", - - nullptr -}; - -const struct DecoderPlugin 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 deleted file mode 100644 index 23bf74fce..000000000 --- a/src/decoder/FfmpegDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 DecoderPlugin ffmpeg_decoder_plugin; - -#endif diff --git a/src/decoder/FfmpegMetaData.cxx b/src/decoder/FfmpegMetaData.cxx deleted file mode 100644 index 6e92b4a13..000000000 --- a/src/decoder/FfmpegMetaData.cxx +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 */ - { nullptr, TAG_NUM_OF_ITEM_TYPES } -}; - -static void -ffmpeg_copy_metadata(TagType type, - AVDictionary *m, const char *name, - const struct tag_handler *handler, void *handler_ctx) -{ - AVDictionaryEntry *mt = nullptr; - - while ((mt = av_dict_get(m, name, mt, 0)) != nullptr) - 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 = nullptr; - - while ((i = av_dict_get(dict, "", i, AV_DICT_IGNORE_SUFFIX)) != nullptr) - 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(TagType(i), dict, tag_item_names[i], - handler, handler_ctx); - - for (const struct tag_table *i = ffmpeg_tags; - i->name != nullptr; ++i) - ffmpeg_copy_metadata(i->type, dict, i->name, - handler, handler_ctx); - - if (handler->pair != nullptr) - ffmpeg_scan_pairs(dict, handler, handler_ctx); -} diff --git a/src/decoder/FfmpegMetaData.hxx b/src/decoder/FfmpegMetaData.hxx deleted file mode 100644 index 998cdf5a8..000000000 --- a/src/decoder/FfmpegMetaData.hxx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 <libavutil/dict.h> -} - -/* suppress the ffmpeg compatibility macro */ -#ifdef SampleFormat -#undef SampleFormat -#endif - -struct tag_handler; - -void -ffmpeg_scan_dictionary(AVDictionary *dict, - const tag_handler *handler, void *handler_ctx); - -#endif diff --git a/src/decoder/FlacCommon.cxx b/src/decoder/FlacCommon.cxx deleted file mode 100644 index e4b906c12..000000000 --- a/src/decoder/FlacCommon.cxx +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 "MixRampInfo.hxx" -#include "CheckAudioFormat.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <assert.h> - -flac_data::flac_data(Decoder &_decoder, - InputStream &_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; - - ReplayGainInfo rgi; - - 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); - - decoder_mixramp(data->decoder, flac_parse_mixramp(block)); - - 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 deleted file mode 100644 index de000dfa1..000000000 --- a/src/decoder/FlacCommon.hxx +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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; - - Decoder &decoder; - InputStream &input_stream; - - Tag tag; - - flac_data(Decoder &decoder, InputStream &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 deleted file mode 100644 index 1b5734434..000000000 --- a/src/decoder/FlacDecoderPlugin.cxx +++ /dev/null @@ -1,387 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with 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> - -#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(InputStream &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) -{ - 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(Decoder &decoder, - InputStream &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(Decoder &decoder, InputStream &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(InputStream &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(Decoder &decoder, InputStream &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.LockRewind(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 DecoderPlugin 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 DecoderPlugin 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 deleted file mode 100644 index 936423fbf..000000000 --- a/src/decoder/FlacDecoderPlugin.h +++ /dev/null @@ -1,26 +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_FLAC_H -#define MPD_DECODER_FLAC_H - -extern const struct DecoderPlugin flac_decoder_plugin; -extern const struct DecoderPlugin oggflac_decoder_plugin; - -#endif diff --git a/src/decoder/FlacDomain.cxx b/src/decoder/FlacDomain.cxx deleted file mode 100644 index 5858004de..000000000 --- a/src/decoder/FlacDomain.cxx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index cf357332f..000000000 --- a/src/decoder/FlacDomain.hxx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index b471ecf64..000000000 --- a/src/decoder/FlacIOHandle.cxx +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "Compiler.h" - -#include <errno.h> - -static size_t -FlacIORead(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle) -{ - InputStream *is = (InputStream *)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) -{ - InputStream *is = (InputStream *)handle; - - Error error; - return is->LockSeek(offset, whence, error) ? 0 : -1; -} - -static FLAC__int64 -FlacIOTell(FLAC__IOHandle handle) -{ - InputStream *is = (InputStream *)handle; - - return is->offset; -} - -static int -FlacIOEof(FLAC__IOHandle handle) -{ - InputStream *is = (InputStream *)handle; - - return is->LockIsEOF(); -} - -static int -FlacIOClose(gcc_unused FLAC__IOHandle handle) -{ - /* no-op because the libFLAC caller is repsonsible for closing - the #InputStream */ - - 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 deleted file mode 100644 index b6e563fa3..000000000 --- a/src/decoder/FlacIOHandle.hxx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.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(InputStream &is) -{ - return (FLAC__IOHandle)&is; -} - -static inline const FLAC__IOCallbacks & -GetFlacIOCallbacks(const InputStream &is) -{ - return is.seekable - ? flac_io_callbacks_seekable - : flac_io_callbacks; -} - -#endif diff --git a/src/decoder/FlacInput.cxx b/src/decoder/FlacInput.cxx deleted file mode 100644 index ce193101d..000000000 --- a/src/decoder/FlacInput.cxx +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "Compiler.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 deleted file mode 100644 index ddd5649f8..000000000 --- a/src/decoder/FlacInput.hxx +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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> - -struct Decoder; -struct InputStream; - -/** - * This class wraps an #InputStream in libFLAC stream decoder - * callbacks. - */ -class FlacInput { - Decoder *const decoder; - - InputStream &input_stream; - -public: - FlacInput(InputStream &_input_stream, - 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 deleted file mode 100644 index 17cc4cd8d..000000000 --- a/src/decoder/FlacMetadata.cxx +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "MixRampInfo.hxx" -#include "tag/Tag.hxx" -#include "tag/TagHandler.hxx" -#include "tag/TagTable.hxx" -#include "tag/TagBuilder.hxx" -#include "ReplayGainInfo.hxx" -#include "util/ASCII.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(ReplayGainInfo &rgi, - const FLAC__StreamMetadata *block) -{ - rgi.Clear(); - - bool found = false; - 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; -} - -gcc_pure -static std::string -flac_find_string_comment(const FLAC__StreamMetadata *block, const char *cmnt) -{ - int offset; - size_t pos; - int len; - const unsigned char *p; - - offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, - cmnt); - if (offset < 0) - return std::string(); - - pos = strlen(cmnt) + 1; /* 1 is for '=' */ - len = block->data.vorbis_comment.comments[offset].length - pos; - if (len <= 0) - return std::string(); - - p = &block->data.vorbis_comment.comments[offset].entry[pos]; - return std::string((const char *)p, len); -} - -MixRampInfo -flac_parse_mixramp(const FLAC__StreamMetadata *block) -{ - MixRampInfo mix_ramp; - mix_ramp.SetStart(flac_find_string_comment(block, "mixramp_start")); - mix_ramp.SetEnd(flac_find_string_comment(block, "mixramp_end")); - return mix_ramp; -} - -/** - * 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 || - !StringEqualsCaseASCII(comment, name, name_length)) - 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, TagType 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], (TagType)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 deleted file mode 100644 index 96c61b8e6..000000000 --- a/src/decoder/FlacMetadata.hxx +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.h" -#include "FlacIOHandle.hxx" - -#include <FLAC/metadata.h> - -#include <assert.h> - -class MixRampInfo; - -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(InputStream &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(InputStream &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 ReplayGainInfo; - -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(ReplayGainInfo &rgi, - const FLAC__StreamMetadata *block); - -MixRampInfo -flac_parse_mixramp(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 deleted file mode 100644 index 569879371..000000000 --- a/src/decoder/FlacPcm.cxx +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index 6cb2d5062..000000000 --- a/src/decoder/FlacPcm.hxx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index fa946f219..000000000 --- a/src/decoder/FluidsynthDecoderPlugin.cxx +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "util/Macros.hxx" -#include "Log.hxx" - -#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(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 = ARRAY_SIZE(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 DecoderPlugin 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 deleted file mode 100644 index 9771898a5..000000000 --- a/src/decoder/FluidsynthDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 DecoderPlugin fluidsynth_decoder_plugin; - -#endif diff --git a/src/decoder/GmeDecoderPlugin.cxx b/src/decoder/GmeDecoderPlugin.cxx deleted file mode 100644 index 815fd8d69..000000000 --- a/src/decoder/GmeDecoderPlugin.cxx +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/FormatString.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[64]; - snprintf(pat, sizeof(pat), "%s%s", - "*/" SUBTUNE_PREFIX "???.", - subtune_suffix); - GPatternSpec *path_with_subtune = g_pattern_spec_new(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[64]; - snprintf(pat, sizeof(pat), "%s%s", - "*/" SUBTUNE_PREFIX "???.", - subtune_suffix); - GPatternSpec *path_with_subtune = g_pattern_spec_new(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){ - return FormatNew(SUBTUNE_PREFIX "%03u.%s", - tnum, subtune_suffix); - } else - return nullptr; -} - -static void -gme_file_decode(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[1024]; - snprintf(tag_title, sizeof(tag_title), - "%s (%d/%d)", - ti->song, song_num + 1, - gme_track_count(emu)); - tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, 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 DecoderPlugin gme_decoder_plugin; -const struct DecoderPlugin 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 deleted file mode 100644 index e46dc766a..000000000 --- a/src/decoder/GmeDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 DecoderPlugin gme_decoder_plugin; - -#endif diff --git a/src/decoder/MadDecoderPlugin.cxx b/src/decoder/MadDecoderPlugin.cxx deleted file mode 100644 index 9dd86c55f..000000000 --- a/src/decoder/MadDecoderPlugin.cxx +++ /dev/null @@ -1,1178 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/ASCII.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; - Decoder *const decoder; - InputStream &input_stream; - enum mad_layer layer; - - MadDecoder(Decoder *decoder, InputStream &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 - InputStream::offset_type ThisFrameOffset() const; - - gcc_pure - InputStream::offset_type 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(Decoder *_decoder, - InputStream &_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(ReplayGainInfo &rgi, - struct id3_tag *tag) -{ - int i; - char *key; - char *value; - struct id3_frame *frame; - bool found = false; - - rgi.Clear(); - - 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 (StringEqualsCaseASCII(key, "replaygain_track_gain")) { - rgi.tuples[REPLAY_GAIN_TRACK].gain = atof(value); - found = true; - } else if (StringEqualsCaseASCII(key, "replaygain_album_gain")) { - rgi.tuples[REPLAY_GAIN_ALBUM].gain = atof(value); - found = true; - } else if (StringEqualsCaseASCII(key, "replaygain_track_peak")) { - rgi.tuples[REPLAY_GAIN_TRACK].peak = atof(value); - found = true; - } else if (StringEqualsCaseASCII(key, "replaygain_album_peak")) { - rgi.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, rgi); -} -#endif - -#ifdef HAVE_ID3TAG -gcc_pure -static MixRampInfo -parse_id3_mixramp(struct id3_tag *tag) -{ - int i; - char *key; - char *value; - struct id3_frame *frame; - - MixRampInfo result; - - 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 (StringEqualsCaseASCII(key, "mixramp_start")) { - result.SetStart(value); - } else if (StringEqualsCaseASCII(key, "mixramp_end")) { - result.SetEnd(value); - } - - free(key); - free(value); - } - - return result; -} -#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) { - ReplayGainInfo rgi; - - if (parse_id3_replay_gain_info(rgi, id3_tag)) { - decoder_replay_gain(*decoder, &rgi); - found_replay_gain = true; - } - - decoder_mixramp(*decoder, parse_id3_mixramp(id3_tag)); - } - - 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 InputStream::offset_type -MadDecoder::ThisFrameOffset() const -{ - auto offset = input_stream.GetOffset(); - - if (stream.this_frame != nullptr) - offset -= stream.bufend - stream.this_frame; - else - offset -= stream.bufend - stream.buffer; - - return offset; -} - -inline InputStream::offset_type -MadDecoder::RestIncludingThisFrame() const -{ - return input_stream.GetSize() - ThisFrameOffset(); -} - -inline void -MadDecoder::FileSizeToSongLength() -{ - InputStream::offset_type 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) { - ReplayGainInfo rgi; - rgi.Clear(); - 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(InputStream &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(Decoder &decoder, InputStream &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(InputStream &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 DecoderPlugin 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 deleted file mode 100644 index 450323670..000000000 --- a/src/decoder/MadDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 DecoderPlugin mad_decoder_plugin; - -#endif diff --git a/src/decoder/MikmodDecoderPlugin.cxx b/src/decoder/MikmodDecoderPlugin.cxx deleted file mode 100644 index 34381aafa..000000000 --- a/src/decoder/MikmodDecoderPlugin.cxx +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 bool mikmod_loop; -static unsigned mikmod_sample_rate; - -static bool -mikmod_decoder_init(const config_param ¶m) -{ - static char params[] = ""; - - mikmod_loop = param.GetBlockValue("loop", false); - 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(Decoder &decoder, const char *path_fs) -{ - /* deconstify the path because libmikmod wants a non-const - string pointer */ - char *const path2 = const_cast<char *>(path_fs); - - MODULE *handle; - int ret; - SBYTE buffer[MIKMOD_FRAME_SIZE]; - - handle = Player_Load(path2, 128, 0); - - if (handle == nullptr) { - FormatError(mikmod_domain, - "failed to open mod: %s", path_fs); - return; - } - - handle->loop = mikmod_loop; - - 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) -{ - /* deconstify the path because libmikmod wants a non-const - string pointer */ - char *const path2 = const_cast<char *>(path_fs); - - MODULE *handle = Player_Load(path2, 128, 0); - - if (handle == nullptr) { - FormatDebug(mikmod_domain, - "Failed to open file: %s", path_fs); - return false; - } - - Player_Free(handle); - - char *title = Player_LoadTitle(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 DecoderPlugin 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 deleted file mode 100644 index d25c5f6e7..000000000 --- a/src/decoder/MikmodDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 DecoderPlugin mikmod_decoder_plugin; - -#endif diff --git a/src/decoder/ModplugDecoderPlugin.cxx b/src/decoder/ModplugDecoderPlugin.cxx deleted file mode 100644 index e75f5479c..000000000 --- a/src/decoder/ModplugDecoderPlugin.cxx +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "system/FatalError.hxx" -#include "util/WritableBuffer.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <libmodplug/modplug.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 InputStream::offset_type MODPLUG_FILE_LIMIT = 100 * 1024 * 1024; - -static int modplug_loop_count; - -static bool -modplug_decoder_init(const config_param ¶m) -{ - modplug_loop_count = param.GetBlockValue("loop_count", 0); - if (modplug_loop_count < -1) - FormatFatalError("Invalid loop count in line %d: %i", - param.line, modplug_loop_count); - - return true; -} - -static WritableBuffer<uint8_t> -mod_loadfile(Decoder *decoder, InputStream &is) -{ - const InputStream::offset_type size = is.GetSize(); - - if (size == 0) { - LogWarning(modplug_domain, "file is empty"); - return { nullptr, 0 }; - } - - if (size > MODPLUG_FILE_LIMIT) { - LogWarning(modplug_domain, "file too large"); - return { nullptr, 0 }; - } - - //known/unknown size, preallocate array, lets read in chunks - - const bool is_stream = size < 0; - - WritableBuffer<uint8_t> buffer; - buffer.size = is_stream ? MODPLUG_PREALLOC_BLOCK : size; - buffer.data = new uint8_t[buffer.size]; - - uint8_t *const end = buffer.end(); - uint8_t *p = buffer.begin(); - - while (true) { - size_t ret = decoder_read(decoder, is, p, end - p); - if (ret == 0) { - if (is.LockIsEOF()) - /* end of file */ - break; - - /* I/O error - skip this song */ - delete[] buffer.data; - buffer.data = nullptr; - return buffer; - } - - p += ret; - if (p == end) { - if (!is_stream) - break; - - LogWarning(modplug_domain, "stream too large"); - delete[] buffer.data; - buffer.data = nullptr; - return buffer; - } - } - - buffer.size = p - buffer.data; - return buffer; -} - -static ModPlugFile * -LoadModPlugFile(Decoder *decoder, InputStream &is) -{ - const auto buffer = mod_loadfile(decoder, is); - if (buffer.IsNull()) { - LogWarning(modplug_domain, "could not load stream"); - return nullptr; - } - - ModPlugFile *f = ModPlug_Load(buffer.data, buffer.size); - delete[] buffer.data; - return f; -} - -static void -mod_decode(Decoder &decoder, InputStream &is) -{ - ModPlug_Settings settings; - int ret; - char audio_buffer[MODPLUG_FRAME_SIZE]; - - ModPlug_GetSettings(&settings); - /* alter setting */ - settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; /* RESAMP */ - settings.mChannels = 2; - settings.mBits = 16; - settings.mFrequency = 44100; - settings.mLoopCount = modplug_loop_count; - /* insert more setting changes here */ - ModPlug_SetSettings(&settings); - - ModPlugFile *f = LoadModPlugFile(&decoder, is); - if (f == nullptr) { - 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(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - ModPlugFile *f = LoadModPlugFile(nullptr, is); - 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 DecoderPlugin modplug_decoder_plugin = { - "modplug", - modplug_decoder_init, - nullptr, - mod_decode, - nullptr, - nullptr, - modplug_scan_stream, - nullptr, - mod_suffixes, - nullptr, -}; diff --git a/src/decoder/ModplugDecoderPlugin.hxx b/src/decoder/ModplugDecoderPlugin.hxx deleted file mode 100644 index 4cd9f5b25..000000000 --- a/src/decoder/ModplugDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 DecoderPlugin modplug_decoder_plugin; - -#endif diff --git a/src/decoder/MpcdecDecoderPlugin.cxx b/src/decoder/MpcdecDecoderPlugin.cxx deleted file mode 100644 index dc258623c..000000000 --- a/src/decoder/MpcdecDecoderPlugin.cxx +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "util/Macros.hxx" -#include "Log.hxx" - -#include <mpc/mpcdec.h> - -#include <assert.h> -#include <unistd.h> -#include <math.h> - -struct mpc_decoder_data { - InputStream &is; - Decoder *decoder; - - mpc_decoder_data(InputStream &_is, Decoder *_decoder) - :is(_is), 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(Decoder &mpd_decoder, InputStream &is) -{ - MPC_SAMPLE_FORMAT sample_buffer[MPC_DECODER_BUFFER_LENGTH]; - - mpc_decoder_data data(is, &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; - } - - ReplayGainInfo rgi; - rgi.Clear(); - rgi.tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.); - rgi.tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767; - rgi.tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.); - rgi.tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767; - - decoder_replay_gain(mpd_decoder, &rgi); - - 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[ARRAY_SIZE(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(InputStream &is) -{ - mpc_decoder_data data(is, 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(InputStream &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 DecoderPlugin 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 deleted file mode 100644 index 23ecc801e..000000000 --- a/src/decoder/MpcdecDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 DecoderPlugin mpcdec_decoder_plugin; - -#endif diff --git a/src/decoder/Mpg123DecoderPlugin.cxx b/src/decoder/Mpg123DecoderPlugin.cxx deleted file mode 100644 index df23f7847..000000000 --- a/src/decoder/Mpg123DecoderPlugin.cxx +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with 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) -{ - int error; - int channels, encoding; - long rate; - - /* mpg123_open() wants a writable string :-( */ - char *const path2 = const_cast<char *>(path_fs); - - error = mpg123_open(handle, path2); - 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(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 DecoderPlugin 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 deleted file mode 100644 index 10f7c37f5..000000000 --- a/src/decoder/Mpg123DecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 DecoderPlugin mpg123_decoder_plugin; - -#endif diff --git a/src/decoder/OggCodec.cxx b/src/decoder/OggCodec.cxx deleted file mode 100644 index 565dbafcf..000000000 --- a/src/decoder/OggCodec.cxx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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(Decoder *decoder, InputStream &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 deleted file mode 100644 index 857871607..000000000 --- a/src/decoder/OggCodec.hxx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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(Decoder *decoder, InputStream &is); - -#endif /* _OGG_COMMON_H */ diff --git a/src/decoder/OggFind.cxx b/src/decoder/OggFind.cxx deleted file mode 100644 index 65c7fa3ce..000000000 --- a/src/decoder/OggFind.cxx +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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" -#include "util/Error.hxx" - -#include <stdio.h> - -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; - } -} - -bool -OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is, - InputStream::offset_type offset, int whence) -{ - oy.Reset(); - - /* reset the stream to clear any previous partial packet - data */ - ogg_stream_reset(&os); - - return is.LockSeek(offset, whence, IgnoreError()) && - oy.ExpectPageSeekIn(os); -} - -bool -OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet, - InputStream &is) -{ - if (is.size > 0 && is.size - is.offset < 65536) - return OggFindEOS(oy, os, packet); - - if (!is.CheapSeeking()) - return false; - - return OggSeekPageAtOffset(oy, os, is, -65536, SEEK_END) && - OggFindEOS(oy, os, packet); -} diff --git a/src/decoder/OggFind.hxx b/src/decoder/OggFind.hxx deleted file mode 100644 index ad51ccdf3..000000000 --- a/src/decoder/OggFind.hxx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "InputStream.hxx" - -#include <ogg/ogg.h> - -struct InputStream; -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); - -/** - * Seek the #InputStream and find the next Ogg page. - */ -bool -OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is, - InputStream::offset_type offset, int whence); - -/** - * Try to find the end-of-stream (EOS) packet. Seek to the end of the - * file if necessary. - * - * @return true if the EOS packet was found - */ -bool -OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet, - InputStream &is); - -#endif diff --git a/src/decoder/OggSyncState.hxx b/src/decoder/OggSyncState.hxx deleted file mode 100644 index 5235c1bd8..000000000 --- a/src/decoder/OggSyncState.hxx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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; - - InputStream &is; - Decoder *const decoder; - -public: - OggSyncState(InputStream &_is, 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 deleted file mode 100644 index fd137f110..000000000 --- a/src/decoder/OggUtil.cxx +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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, Decoder *decoder, - InputStream &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, InputStream &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, InputStream &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, InputStream &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, InputStream &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, InputStream &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 deleted file mode 100644 index 41fc755ba..000000000 --- a/src/decoder/OggUtil.hxx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 InputStream; -struct Decoder; - -/** - * Feed data from the #InputStream into the #ogg_sync_state. - * - * @return false on error or end-of-file - */ -bool -OggFeed(ogg_sync_state &oy, Decoder *decoder, InputStream &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, InputStream &is); - -/** - * 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, InputStream &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, InputStream &is); - -/** - * Like OggExpectPage(), but allow skipping garbage (after seeking). - */ -bool -OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page, - Decoder *decoder, InputStream &is); - -/** - * 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, InputStream &is); - -#endif diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/OpusDecoderPlugin.cxx deleted file mode 100644 index f3d7b342e..000000000 --- a/src/decoder/OpusDecoderPlugin.cxx +++ /dev/null @@ -1,488 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with 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 <string.h> - -static constexpr 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 { - Decoder &decoder; - InputStream &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; - - ogg_int64_t eos_granulepos; - - size_t frame_size; - -public: - MPDOpusDecoder(Decoder &_decoder, - InputStream &_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); - - bool Seek(OggSyncState &oy, double where); -}; - -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); -} - -/** - * Load the end-of-stream packet and restore the previous file - * position. - */ -static bool -LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno, - ogg_packet &packet) -{ - if (!is.CheapSeeking()) - /* we do this for local files only, because seeking - around remote files is expensive and not worth the - troubl */ - return -1; - - const auto old_offset = is.offset; - if (old_offset < 0) - return -1; - - /* create temporary Ogg objects for seeking and parsing the - EOS packet */ - OggSyncState oy(is, decoder); - ogg_stream_state os; - ogg_stream_init(&os, serialno); - - bool result = OggSeekFindEOS(oy, os, packet, is); - ogg_stream_clear(&os); - - /* restore the previous file position */ - is.Seek(old_offset, SEEK_SET, IgnoreError()); - - return result; -} - -/** - * Load the end-of-stream granulepos and restore the previous file - * position. - * - * @return -1 on error - */ -gcc_pure -static ogg_int64_t -LoadEOSGranulePos(InputStream &is, Decoder *decoder, int serialno) -{ - ogg_packet packet; - if (!LoadEOSPacket(is, decoder, serialno, packet)) - return -1; - - return packet.granulepos; -} - -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; - } - - eos_granulepos = LoadEOSGranulePos(input_stream, &decoder, - opus_serialno); - const double duration = eos_granulepos >= 0 - ? double(eos_granulepos) / opus_sample_rate - : -1.0; - - const AudioFormat audio_format(opus_sample_rate, - SampleFormat::S16, channels); - decoder_initialized(decoder, audio_format, - eos_granulepos > 0, duration); - 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) -{ - ReplayGainInfo rgi; - rgi.Clear(); - - TagBuilder tag_builder; - - DecoderCommand cmd; - if (ScanOpusTags(packet.packet, packet.bytes, - &rgi, - &add_tag_handler, &tag_builder) && - !tag_builder.IsEmpty()) { - decoder_replay_gain(decoder, &rgi); - - 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; - - if (packet.granulepos > 0) - decoder_timestamp(decoder, - double(packet.granulepos) - / opus_sample_rate); - } - - return DecoderCommand::NONE; -} - -bool -MPDOpusDecoder::Seek(OggSyncState &oy, double where_s) -{ - assert(eos_granulepos > 0); - assert(input_stream.seekable); - assert(input_stream.size > 0); - assert(input_stream.offset >= 0); - - const ogg_int64_t where_granulepos(where_s * opus_sample_rate); - - /* interpolate the file offset where we expect to find the - given granule position */ - /* TODO: implement binary search */ - InputStream::offset_type offset(where_granulepos * input_stream.size - / eos_granulepos); - - if (!OggSeekPageAtOffset(oy, os, input_stream, offset, SEEK_SET)) - return false; - - decoder_timestamp(decoder, where_s); - return true; -} - -static void -mpd_opus_stream_decode(Decoder &decoder, - InputStream &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.LockRewind(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::SEEK) { - if (d.Seek(oy, decoder_seek_where(decoder))) - decoder_command_finished(decoder); - else - decoder_seek_error(decoder); - - continue; - } - - if (cmd != DecoderCommand::NONE) - break; - - if (!d.ReadNextPage(oy)) - break; - } -} - -static bool -mpd_opus_scan_stream(InputStream &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, - nullptr, - handler, handler_ctx)) - result = false; - - break; - } - } - - if (packet.e_o_s || OggSeekFindEOS(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 DecoderPlugin 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 deleted file mode 100644 index 263ac6e2d..000000000 --- a/src/decoder/OpusDecoderPlugin.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_OPUS_H -#define MPD_DECODER_OPUS_H - -extern const struct DecoderPlugin opus_decoder_plugin; - -#endif diff --git a/src/decoder/OpusDomain.cxx b/src/decoder/OpusDomain.cxx deleted file mode 100644 index b00e2a553..000000000 --- a/src/decoder/OpusDomain.cxx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index 2b56c427c..000000000 --- a/src/decoder/OpusDomain.hxx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 0417d3905..000000000 --- a/src/decoder/OpusHead.cxx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index fa6a2b666..000000000 --- a/src/decoder/OpusHead.hxx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 2bb39b748..000000000 --- a/src/decoder/OpusReader.hxx +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 <algorithm> - -#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]; - *std::copy_n(src, length, dest) = 0; - return dest; - } -}; - -#endif diff --git a/src/decoder/OpusTags.cxx b/src/decoder/OpusTags.cxx deleted file mode 100644 index f7729e5ad..000000000 --- a/src/decoder/OpusTags.cxx +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "tag/Tag.hxx" -#include "ReplayGainInfo.hxx" - -#include <stdint.h> -#include <string.h> -#include <stdlib.h> - -gcc_pure -static TagType -ParseOpusTagName(const char *name) -{ - TagType type = tag_name_parse_i(name); - if (type != TAG_NUM_OF_ITEM_TYPES) - return type; - - return tag_table_lookup_i(xiph_tags, name); -} - -static void -ScanOneOpusTag(const char *name, const char *value, - ReplayGainInfo *rgi, - const struct tag_handler *handler, void *ctx) -{ - if (rgi != nullptr && strcmp(name, "R128_TRACK_GAIN") == 0) { - /* R128_TRACK_GAIN is a Q7.8 fixed point number in - dB */ - - char *endptr; - long l = strtol(value, &endptr, 10); - if (endptr > value && *endptr == 0) - rgi->tuples[REPLAY_GAIN_TRACK].gain = double(l) / 256.; - } - - tag_handler_invoke_pair(handler, ctx, name, value); - - if (handler->tag != nullptr) { - TagType t = ParseOpusTagName(name); - if (t != TAG_NUM_OF_ITEM_TYPES) - tag_handler_invoke_tag(handler, ctx, t, value); - } -} - -bool -ScanOpusTags(const void *data, size_t size, - ReplayGainInfo *rgi, - 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, rgi, handler, ctx); - } - - delete[] p; - } - - return true; -} diff --git a/src/decoder/OpusTags.hxx b/src/decoder/OpusTags.hxx deleted file mode 100644 index e1f1a1ff1..000000000 --- a/src/decoder/OpusTags.hxx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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> - -struct ReplayGainInfo; - -bool -ScanOpusTags(const void *data, size_t size, - ReplayGainInfo *rgi, - const struct tag_handler *handler, void *ctx); - -#endif diff --git a/src/decoder/PcmDecoderPlugin.cxx b/src/decoder/PcmDecoderPlugin.cxx deleted file mode 100644 index dbc38fb76..000000000 --- a/src/decoder/PcmDecoderPlugin.cxx +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "util/ByteReverse.hxx" -#include "Log.hxx" - -#include <glib.h> -#include <unistd.h> -#include <string.h> -#include <stdio.h> /* for SEEK_SET */ - -static void -pcm_stream_decode(Decoder &decoder, InputStream &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 auto 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) { - InputStream::offset_type offset(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 DecoderPlugin 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 deleted file mode 100644 index 38e4a5020..000000000 --- a/src/decoder/PcmDecoderPlugin.hxx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 DecoderPlugin pcm_decoder_plugin; - -#endif diff --git a/src/decoder/SidplayDecoderPlugin.cxx b/src/decoder/SidplayDecoderPlugin.cxx deleted file mode 100644 index 160337594..000000000 --- a/src/decoder/SidplayDecoderPlugin.cxx +++ /dev/null @@ -1,435 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "SidplayDecoderPlugin.hxx" -#include "../DecoderAPI.hxx" -#include "tag/TagHandler.hxx" -#include "util/Domain.hxx" -#include "system/ByteOrder.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> - -#define SUBTUNE_PREFIX "tune_" - -static constexpr Domain sidplay_domain("sidplay"); - -static GPatternSpec *path_with_subtune; -static const char *songlength_file; -static GKeyFile *songlength_database; - -static bool all_files_are_containers; -static unsigned default_songlength; - -static bool filter_setting; - -static GKeyFile * -sidplay_load_songlength_db(const char *path) -{ - GError *error = nullptr; - gchar *data; - gsize size; - - if (!g_file_get_contents(path, &data, &size, &error)) { - FormatError(sidplay_domain, - "unable to read songlengths file %s: %s", - path, error->message); - g_error_free(error); - return nullptr; - } - - /* replace any ; comment characters with # */ - for (gsize i = 0; i < size; i++) - if (data[i] == ';') - data[i] = '#'; - - GKeyFile *db = g_key_file_new(); - bool success = g_key_file_load_from_data(db, data, size, - G_KEY_FILE_NONE, &error); - g_free(data); - if (!success) { - FormatError(sidplay_domain, - "unable to parse songlengths file %s: %s", - path, error->message); - g_error_free(error); - g_key_file_free(db); - return nullptr; - } - - g_key_file_set_list_separator(db, ' '); - return db; -} - -static bool -sidplay_init(const config_param ¶m) -{ - /* read the songlengths database file */ - songlength_file = param.GetBlockValue("songlength_database"); - if (songlength_file != nullptr) - songlength_database = sidplay_load_songlength_db(songlength_file); - - default_songlength = param.GetBlockValue("default_songlength", 0u); - - all_files_are_containers = - param.GetBlockValue("all_files_are_containers", true); - - path_with_subtune=g_pattern_spec_new( - "*/" SUBTUNE_PREFIX "???.sid"); - - filter_setting = param.GetBlockValue("filter", true); - - return true; -} - -static void -sidplay_finish() -{ - g_pattern_spec_free(path_with_subtune); - - if(songlength_database) - g_key_file_free(songlength_database); -} - -/** - * returns the file path stripped of any /tune_xxx.sid subtune - * suffix - */ -static char * -get_container_name(const char *path_fs) -{ - char *path_container=g_strdup(path_fs); - - if(!g_pattern_match(path_with_subtune, - strlen(path_container), path_container, nullptr)) - return path_container; - - char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX); - if(ptr) *ptr='\0'; - - return path_container; -} - -/** - * returns tune number from file.sid/tune_xxx.sid style path or 1 if - * no subtune is appended - */ -static unsigned -get_song_num(const char *path_fs) -{ - if(g_pattern_match(path_with_subtune, - strlen(path_fs), path_fs, nullptr)) { - char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX); - if(!sub) return 1; - - sub+=strlen("/" SUBTUNE_PREFIX); - int song_num=strtol(sub, nullptr, 10); - - if (errno == EINVAL) - return 1; - else - return song_num; - } else - return 1; -} - -/* get the song length in seconds */ -static int -get_song_length(const char *path_fs) -{ - if (songlength_database == nullptr) - return -1; - - gchar *sid_file=get_container_name(path_fs); - SidTuneMod tune(sid_file); - g_free(sid_file); - if(!tune) { - LogWarning(sidplay_domain, - "failed to load file for calculating md5 sum"); - return -1; - } - char md5sum[SIDTUNE_MD5_LENGTH+1]; - tune.createMD5(md5sum); - - const unsigned song_num = get_song_num(path_fs); - - gsize num_items; - gchar **values=g_key_file_get_string_list(songlength_database, - "Database", md5sum, &num_items, nullptr); - if(!values || song_num>num_items) { - g_strfreev(values); - return -1; - } - - int minutes=strtol(values[song_num-1], nullptr, 10); - if(errno==EINVAL) minutes=0; - - int seconds; - char *ptr=strchr(values[song_num-1], ':'); - if(ptr) { - seconds=strtol(ptr+1, nullptr, 10); - if(errno==EINVAL) seconds=0; - } else - seconds=0; - - g_strfreev(values); - - return (minutes*60)+seconds; -} - -static void -sidplay_file_decode(Decoder &decoder, const char *path_fs) -{ - int channels; - - /* load the tune */ - - char *path_container=get_container_name(path_fs); - SidTune tune(path_container, nullptr, true); - g_free(path_container); - if (!tune) { - LogWarning(sidplay_domain, "failed to load file"); - return; - } - - int song_num=get_song_num(path_fs); - tune.selectSong(song_num); - - int song_len=get_song_length(path_fs); - if(song_len==-1) song_len=default_songlength; - - /* initialize the player */ - - sidplay2 player; - int iret = player.load(&tune); - if (iret != 0) { - FormatWarning(sidplay_domain, - "sidplay2.load() failed: %s", player.error()); - return; - } - - /* initialize the builder */ - - ReSIDBuilder builder("ReSID"); - if (!builder) { - LogWarning(sidplay_domain, - "failed to initialize ReSIDBuilder"); - return; - } - - builder.create(player.info().maxsids); - if (!builder) { - LogWarning(sidplay_domain, "ReSIDBuilder.create() failed"); - return; - } - - builder.filter(filter_setting); - if (!builder) { - LogWarning(sidplay_domain, "ReSIDBuilder.filter() failed"); - return; - } - - /* configure the player */ - - sid2_config_t config = player.config(); - - config.clockDefault = SID2_CLOCK_PAL; - config.clockForced = true; - config.clockSpeed = SID2_CLOCK_CORRECT; - config.frequency = 48000; - config.optimisation = SID2_DEFAULT_OPTIMISATION; - - config.precision = 16; - config.sidDefault = SID2_MOS6581; - config.sidEmulation = &builder; - config.sidModel = SID2_MODEL_CORRECT; - config.sidSamples = true; - config.sampleFormat = IsLittleEndian() - ? SID2_LITTLE_SIGNED - : SID2_BIG_SIGNED; - if (tune.isStereo()) { - config.playback = sid2_stereo; - channels = 2; - } else { - config.playback = sid2_mono; - channels = 1; - } - - iret = player.config(config); - if (iret != 0) { - FormatWarning(sidplay_domain, - "sidplay2.config() failed: %s", player.error()); - return; - } - - /* initialize the MPD decoder */ - - const AudioFormat audio_format(48000, SampleFormat::S16, channels); - assert(audio_format.IsValid()); - - decoder_initialized(decoder, audio_format, true, (float)song_len); - - /* .. and play */ - - const unsigned timebase = player.timebase(); - song_len *= timebase; - - DecoderCommand cmd; - do { - char buffer[4096]; - size_t nbytes; - - nbytes = player.play(buffer, sizeof(buffer)); - if (nbytes == 0) - break; - - decoder_timestamp(decoder, (double)player.time() / timebase); - - cmd = decoder_data(decoder, nullptr, buffer, nbytes, 0); - - if (cmd == DecoderCommand::SEEK) { - unsigned data_time = player.time(); - unsigned target_time = (unsigned) - (decoder_seek_where(decoder) * timebase); - - /* can't rewind so return to zero and seek forward */ - if(target_time<data_time) { - player.stop(); - data_time=0; - } - - /* ignore data until target time is reached */ - while(data_time<target_time) { - nbytes=player.play(buffer, sizeof(buffer)); - if(nbytes==0) - break; - data_time = player.time(); - } - - decoder_command_finished(decoder); - } - - if (song_len > 0 && player.time() >= (unsigned)song_len) - break; - - } while (cmd != DecoderCommand::STOP); -} - -static bool -sidplay_scan_file(const char *path_fs, - const struct tag_handler *handler, void *handler_ctx) -{ - int song_num=get_song_num(path_fs); - char *path_container=get_container_name(path_fs); - - SidTune tune(path_container, nullptr, true); - g_free(path_container); - if (!tune) - return false; - - const SidTuneInfo &info = tune.getInfo(); - - /* title */ - const char *title; - if (info.numberOfInfoStrings > 0 && info.infoString[0] != nullptr) - title=info.infoString[0]; - else - title=""; - - if(info.songs>1) { - char tag_title[1024]; - snprintf(tag_title, sizeof(tag_title), - "%s (%d/%d)", - title, song_num, info.songs); - tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, tag_title); - } else - tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, title); - - /* artist */ - if (info.numberOfInfoStrings > 1 && info.infoString[1] != nullptr) - tag_handler_invoke_tag(handler, handler_ctx, TAG_ARTIST, - info.infoString[1]); - - /* track */ - char track[16]; - sprintf(track, "%d", song_num); - tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track); - - /* time */ - int song_len=get_song_length(path_fs); - if (song_len >= 0) - tag_handler_invoke_duration(handler, handler_ctx, song_len); - - return true; -} - -static char * -sidplay_container_scan(const char *path_fs, const unsigned int tnum) -{ - SidTune tune(path_fs, nullptr, true); - if (!tune) - return nullptr; - - const SidTuneInfo &info=tune.getInfo(); - - /* Don't treat sids containing a single tune - as containers */ - if(!all_files_are_containers && info.songs<2) - return nullptr; - - /* Construct container/tune path names, eg. - Delta.sid/tune_001.sid */ - if(tnum<=info.songs) { - char *subtune= g_strdup_printf( - SUBTUNE_PREFIX "%03u.sid", tnum); - return subtune; - } else - return nullptr; -} - -static const char *const sidplay_suffixes[] = { - "sid", - "mus", - "str", - "prg", - "P00", - nullptr -}; - -extern const struct DecoderPlugin sidplay_decoder_plugin; -const struct DecoderPlugin sidplay_decoder_plugin = { - "sidplay", - sidplay_init, - sidplay_finish, - nullptr, /* stream_decode() */ - sidplay_file_decode, - sidplay_scan_file, - nullptr, /* stream_tag() */ - sidplay_container_scan, - sidplay_suffixes, - nullptr, /* mime_types */ -}; diff --git a/src/decoder/SidplayDecoderPlugin.hxx b/src/decoder/SidplayDecoderPlugin.hxx deleted file mode 100644 index 16544801f..000000000 --- a/src/decoder/SidplayDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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_SIDPLAY_HXX -#define MPD_DECODER_SIDPLAY_HXX - -extern const struct DecoderPlugin sidplay_decoder_plugin; - -#endif diff --git a/src/decoder/SndfileDecoderPlugin.cxx b/src/decoder/SndfileDecoderPlugin.cxx deleted file mode 100644 index 3360cd1bf..000000000 --- a/src/decoder/SndfileDecoderPlugin.cxx +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 InputStream &is = *(const InputStream *)user_data; - - return is.GetSize(); -} - -static sf_count_t -sndfile_vio_seek(sf_count_t offset, int whence, void *user_data) -{ - InputStream &is = *(InputStream *)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) -{ - InputStream &is = *(InputStream *)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 InputStream &is = *(const InputStream *)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(Decoder &decoder, InputStream &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 DecoderPlugin 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 deleted file mode 100644 index f8aa65680..000000000 --- a/src/decoder/SndfileDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 DecoderPlugin sndfile_decoder_plugin; - -#endif diff --git a/src/decoder/VorbisComments.cxx b/src/decoder/VorbisComments.cxx deleted file mode 100644 index d4f019b58..000000000 --- a/src/decoder/VorbisComments.cxx +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "util/ASCII.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 (StringEqualsCaseASCII(comment, needle, len) && - comment[len] == '=') - return comment + len + 1; - - return nullptr; -} - -bool -vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments) -{ - rgi.Clear(); - - const char *temp; - bool found = false; - - 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, TagType tag_type, - const struct tag_handler *handler, void *handler_ctx) -{ - const char *value; - - value = vorbis_comment_value(comment, name); - if (value != nullptr) { - 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 != nullptr) { - char *name = g_strdup((const char*)comment); - 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 (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], TagType(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 deleted file mode 100644 index e5a48ef6b..000000000 --- a/src/decoder/VorbisComments.hxx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 ReplayGainInfo; -struct tag_handler; -struct Tag; - -bool -vorbis_comments_to_replay_gain(ReplayGainInfo &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 deleted file mode 100644 index 4d3e48528..000000000 --- a/src/decoder/VorbisDecoderPlugin.cxx +++ /dev/null @@ -1,352 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "util/Macros.hxx" -#include "system/ByteOrder.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 <assert.h> -#include <errno.h> - -struct vorbis_input_stream { - Decoder *decoder; - - InputStream *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 == nullptr || - 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, - Decoder *decoder, InputStream &input_stream) -{ - vis->decoder = decoder; - vis->input_stream = &input_stream; - vis->seekable = input_stream.CheapSeeking(); - - int ret = ov_open_callbacks(vis, vf, nullptr, 0, vorbis_is_callbacks); - if (ret < 0) { - if (decoder == nullptr || - 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(Decoder &decoder, InputStream &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(Decoder &decoder, - InputStream &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.LockRewind(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 == nullptr) { - 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 = - ARRAY_SIZE(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), - IsBigEndian(), 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 == nullptr) { - 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); - - ReplayGainInfo 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(InputStream &is, - const struct tag_handler *handler, void *handler_ctx) -{ - struct vorbis_input_stream vis; - OggVorbis_File vf; - - if (!vorbis_is_open(&vis, &vf, nullptr, 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", nullptr -}; - -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", - nullptr -}; - -const struct DecoderPlugin 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 deleted file mode 100644 index 54953d83a..000000000 --- a/src/decoder/VorbisDecoderPlugin.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_VORBIS_H -#define MPD_DECODER_VORBIS_H - -extern const struct DecoderPlugin vorbis_decoder_plugin; - -#endif diff --git a/src/decoder/VorbisDomain.cxx b/src/decoder/VorbisDomain.cxx deleted file mode 100644 index 32ff4d6b7..000000000 --- a/src/decoder/VorbisDomain.cxx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index a35edd041..000000000 --- a/src/decoder/VorbisDomain.hxx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 98555c5e8..000000000 --- a/src/decoder/WavpackDecoderPlugin.cxx +++ /dev/null @@ -1,571 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "util/Macros.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(Decoder &decoder, WavpackContext *wpc, bool can_seek) -{ - bool is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0; - SampleFormat sample_format = - wavpack_bits_to_sample_format(is_float, - WavpackGetBytesPerSample(wpc)); - - Error error; - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, - WavpackGetSampleRate(wpc), - sample_format, - WavpackGetNumChannels(wpc), error)) { - LogError(error); - return; - } - - const format_samples_t format_samples = is_float - ? format_samples_float - : format_samples_int; - - const float total_time = float(WavpackGetNumSamples(wpc)) - / audio_format.sample_rate; - - const int bytes_per_sample = WavpackGetBytesPerSample(wpc); - const int 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 = ARRAY_SIZE(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, nullptr, 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]; - if (WavpackGetTagItem(wpc, key, buffer, sizeof(buffer)) <= 0) - return false; - - *value_r = atof(buffer); - return true; -} - -static bool -wavpack_replaygain(ReplayGainInfo &rgi, - WavpackContext *wpc) -{ - rgi.Clear(); - - bool found = false; - found |= wavpack_tag_float(wpc, "replaygain_track_gain", - &rgi.tuples[REPLAY_GAIN_TRACK].gain); - found |= wavpack_tag_float(wpc, "replaygain_track_peak", - &rgi.tuples[REPLAY_GAIN_TRACK].peak); - found |= wavpack_tag_float(wpc, "replaygain_album_gain", - &rgi.tuples[REPLAY_GAIN_ALBUM].gain); - found |= wavpack_tag_float(wpc, "replaygain_album_peak", - &rgi.tuples[REPLAY_GAIN_ALBUM].peak); - - return found; -} - -static void -wavpack_scan_tag_item(WavpackContext *wpc, const char *name, - TagType 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) -{ - char error[ERRORLEN]; - WavpackContext *wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0); - if (wpc == nullptr) { - 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 != nullptr) - wavpack_scan_tag_item(wpc, name, (TagType)i, - handler, handler_ctx); - } - - for (const struct tag_table *i = ape_tags; i->name != nullptr; ++i) - wavpack_scan_tag_item(wpc, i->name, i->type, - handler, handler_ctx); - - if (handler->pair != nullptr) { - 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 { - Decoder *decoder; - InputStream *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, Decoder &decoder, - InputStream &is) -{ - isp->decoder = &decoder; - isp->is = &is; - isp->last_byte = EOF; -} - -static InputStream * -wavpack_open_wvc(Decoder &decoder, const char *uri, - Mutex &mutex, Cond &cond, - struct wavpack_input *wpi) -{ - /* - * As we use dc->utf8url, this function will be bad for - * single files. utf8url is not absolute file path :/ - */ - if (uri == nullptr) - return nullptr; - - char *wvc_url = g_strconcat(uri, "c", nullptr); - - InputStream *is_wvc = InputStream::Open(wvc_url, mutex, cond, - IgnoreError()); - g_free(wvc_url); - - if (is_wvc == nullptr) - return nullptr; - - /* - * And we try to buffer in order to get know - * about a possible 404 error. - */ - char first_byte; - size_t nbytes = decoder_read(decoder, *is_wvc, - &first_byte, sizeof(first_byte)); - if (nbytes == 0) { - is_wvc->Close(); - return nullptr; - } - - /* 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(Decoder &decoder, InputStream &is) -{ - int open_flags = OPEN_NORMALIZE; - bool can_seek = is.seekable; - - wavpack_input isp_wvc; - InputStream *is_wvc = wavpack_open_wvc(decoder, is.uri.c_str(), - is.mutex, is.cond, - &isp_wvc); - if (is_wvc != nullptr) { - open_flags |= OPEN_WVC; - can_seek &= is_wvc->seekable; - } - - if (!can_seek) { - open_flags |= OPEN_STREAMING; - } - - wavpack_input isp; - wavpack_input_init(&isp, decoder, is); - - char error[ERRORLEN]; - WavpackContext *wpc = - WavpackOpenFileInputEx(&mpd_is_reader, &isp, - open_flags & OPEN_WVC - ? &isp_wvc : nullptr, - error, open_flags, 23); - - if (wpc == nullptr) { - 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(Decoder &decoder, const char *fname) -{ - char error[ERRORLEN]; - WavpackContext *wpc = WavpackOpenFileInput(fname, error, - OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE, - 23); - if (wpc == nullptr) { - FormatWarning(wavpack_domain, - "failed to open WavPack file \"%s\": %s", - fname, error); - return; - } - - ReplayGainInfo rgi; - if (wavpack_replaygain(rgi, wpc)) - decoder_replay_gain(decoder, &rgi); - - wavpack_decode(decoder, wpc, true); - - WavpackCloseFile(wpc); -} - -static char const *const wavpack_suffixes[] = { - "wv", - nullptr -}; - -static char const *const wavpack_mime_types[] = { - "audio/x-wavpack", - nullptr -}; - -const struct DecoderPlugin 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 deleted file mode 100644 index 3a2d94532..000000000 --- a/src/decoder/WavpackDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 DecoderPlugin wavpack_decoder_plugin; - -#endif diff --git a/src/decoder/WildmidiDecoderPlugin.cxx b/src/decoder/WildmidiDecoderPlugin.cxx deleted file mode 100644 index 3da3f1387..000000000 --- a/src/decoder/WildmidiDecoderPlugin.cxx +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/AllocatedPath.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 AllocatedPath 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(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); - - WildMidi_FastSeek(wm, &seek_where); - 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 DecoderPlugin 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 deleted file mode 100644 index a6289612e..000000000 --- a/src/decoder/WildmidiDecoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 DecoderPlugin wildmidi_decoder_plugin; - -#endif diff --git a/src/decoder/XiphTags.cxx b/src/decoder/XiphTags.cxx deleted file mode 100644 index b9958a19a..000000000 --- a/src/decoder/XiphTags.cxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index 606dfef10..000000000 --- a/src/decoder/XiphTags.hxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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/plugins/AdPlugDecoderPlugin.cxx b/src/decoder/plugins/AdPlugDecoderPlugin.cxx new file mode 100644 index 000000000..c288d8cf4 --- /dev/null +++ b/src/decoder/plugins/AdPlugDecoderPlugin.cxx @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "util/Macros.hxx" +#include "Log.hxx" + +#include <adplug/adplug.h> +#include <adplug/emuopl.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(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 = ARRAY_SIZE(buffer) / 2; + DecoderCommand cmd; + + do { + if (!player->update()) + break; + + opl.update(buffer, frames_per_buffer); + cmd = decoder_data(decoder, nullptr, + buffer, sizeof(buffer), + 0); + } while (cmd == DecoderCommand::NONE); + + delete player; +} + +static void +adplug_scan_tag(TagType 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 DecoderPlugin adplug_decoder_plugin = { + "adplug", + adplug_init, + nullptr, + nullptr, + adplug_file_decode, + adplug_scan_file, + nullptr, + nullptr, + adplug_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/AdPlugDecoderPlugin.h b/src/decoder/plugins/AdPlugDecoderPlugin.h new file mode 100644 index 000000000..539dbbf0a --- /dev/null +++ b/src/decoder/plugins/AdPlugDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderPlugin adplug_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/AudiofileDecoderPlugin.cxx b/src/decoder/plugins/AudiofileDecoderPlugin.cxx new file mode 100644 index 000000000..8a7f34dfe --- /dev/null +++ b/src/decoder/plugins/AudiofileDecoderPlugin.cxx @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "input/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) +{ + InputStream &is = *(InputStream *)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) +{ + InputStream &is = *(InputStream *)vfile->closure; + return is.GetSize(); +} + +static AFfileoffset +audiofile_file_tell(AFvirtualfile *vfile) +{ + InputStream &is = *(InputStream *)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) +{ + InputStream &is = *(InputStream *)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(InputStream &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(Decoder &decoder, InputStream &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 DecoderPlugin 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/plugins/AudiofileDecoderPlugin.hxx b/src/decoder/plugins/AudiofileDecoderPlugin.hxx new file mode 100644 index 000000000..61129076d --- /dev/null +++ b/src/decoder/plugins/AudiofileDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderPlugin audiofile_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/DsdLib.cxx b/src/decoder/plugins/DsdLib.cxx new file mode 100644 index 000000000..f77d8f59a --- /dev/null +++ b/src/decoder/plugins/DsdLib.cxx @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "input/InputStream.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 +DsdId::Equals(const char *s) const +{ + assert(s != nullptr); + assert(strlen(s) == sizeof(value)); + + return memcmp(value, s, sizeof(value)) == 0; +} + +bool +dsdlib_read(Decoder *decoder, InputStream &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(Decoder *decoder, InputStream &is, + int64_t 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() < (int64_t)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(Decoder *decoder, InputStream &is, + int64_t 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 ((int64_t)length > delta) + length = delta; + + size_t nbytes = decoder_read(decoder, is, buffer, length); + if (nbytes == 0) + return false; + + delta -= nbytes; + } + + return true; +} + +#ifdef HAVE_ID3TAG +void +dsdlib_tag_id3(InputStream &is, + const struct tag_handler *handler, + void *handler_ctx, int64_t 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 auto size = is.GetSize(); + const auto 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/plugins/DsdLib.hxx b/src/decoder/plugins/DsdLib.hxx new file mode 100644 index 000000000..4c5e83114 --- /dev/null +++ b/src/decoder/plugins/DsdLib.hxx @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "system/ByteOrder.hxx" +#include "Compiler.h" + +#include <stddef.h> +#include <stdint.h> + +struct Decoder; +struct InputStream; + +struct DsdId { + char value[4]; + + gcc_pure + bool Equals(const char *s) const; +}; + +class DsdUint64 { + uint32_t lo; + uint32_t hi; + +public: + constexpr uint64_t Read() const { + return (uint64_t(FromLE32(hi)) << 32) | + uint64_t(FromLE32(lo)); + } +}; + +class DffDsdUint64 { + uint32_t hi; + uint32_t lo; + +public: + constexpr uint64_t Read() const { + return (uint64_t(FromBE32(hi)) << 32) | + uint64_t(FromBE32(lo)); + } +}; + +bool +dsdlib_read(Decoder *decoder, InputStream &is, + void *data, size_t length); + +bool +dsdlib_skip_to(Decoder *decoder, InputStream &is, + int64_t offset); + +bool +dsdlib_skip(Decoder *decoder, InputStream &is, + int64_t delta); + +/** + * Add tags from ID3 tag. All tags commonly found in the ID3 tags of + * DSF and DSDIFF files are imported + */ +void +dsdlib_tag_id3(InputStream &is, + const struct tag_handler *handler, + void *handler_ctx, int64_t tagoffset); + +#endif diff --git a/src/decoder/plugins/DsdiffDecoderPlugin.cxx b/src/decoder/plugins/DsdiffDecoderPlugin.cxx new file mode 100644 index 000000000..92f7ecdaa --- /dev/null +++ b/src/decoder/plugins/DsdiffDecoderPlugin.cxx @@ -0,0 +1,523 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "util/bit_reverse.h" +#include "util/Error.hxx" +#include "system/ByteOrder.hxx" +#include "tag/TagHandler.hxx" +#include "DsdLib.hxx" +#include "Log.hxx" + +struct DsdiffHeader { + DsdId id; + DffDsdUint64 size; + DsdId format; +}; + +struct DsdiffChunkHeader { + DsdId id; + DffDsdUint64 size; + + /** + * Read the "size" attribute from the specified header, converting it + * to the host byte order if needed. + */ + constexpr + uint64_t GetSize() const { + return size.Read(); + } +}; + +/** 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 + InputStream::offset_type id3_offset; + uint64_t id3_size; +#endif + /** offset for artist tag */ + InputStream::offset_type diar_offset; + /** offset for title tag */ + InputStream::offset_type 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(Decoder *decoder, InputStream &is, + DsdId *id) +{ + return dsdlib_read(decoder, is, id, sizeof(*id)); +} + +static bool +dsdiff_read_chunk_header(Decoder *decoder, InputStream &is, + DsdiffChunkHeader *header) +{ + return dsdlib_read(decoder, is, header, sizeof(*header)); +} + +static bool +dsdiff_read_payload(Decoder *decoder, InputStream &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(Decoder *decoder, InputStream &is, + DsdiffMetaData *metadata, + InputStream::offset_type end_offset) +{ + DsdiffChunkHeader header; + while ((InputStream::offset_type)(is.GetOffset() + sizeof(header)) <= end_offset) { + if (!dsdiff_read_chunk_header(decoder, is, &header)) + return false; + + InputStream::offset_type chunk_end_offset = is.GetOffset() + + header.GetSize(); + if (chunk_end_offset > end_offset) + return false; + + if (header.id.Equals("FS ")) { + uint32_t sample_rate; + if (!dsdiff_read_payload(decoder, is, &header, + &sample_rate, + sizeof(sample_rate))) + return false; + + metadata->sample_rate = FromBE32(sample_rate); + } else if (header.id.Equals("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 = FromBE16(channels); + } else if (header.id.Equals("CMPR")) { + DsdId type; + if (header.GetSize() < sizeof(type) || + !dsdlib_read(decoder, is, + &type, sizeof(type)) || + !dsdlib_skip_to(decoder, is, chunk_end_offset)) + return false; + + if (!type.Equals("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(Decoder *decoder, InputStream &is, + DsdiffMetaData *metadata, + const DsdiffChunkHeader *prop_header) +{ + uint64_t prop_size = prop_header->GetSize(); + InputStream::offset_type end_offset = is.GetOffset() + prop_size; + + DsdId prop_id; + if (prop_size < sizeof(prop_id) || + !dsdiff_read_id(decoder, is, &prop_id)) + return false; + + if (prop_id.Equals("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(InputStream &is, + const struct tag_handler *handler, + void *handler_ctx, InputStream::offset_type tagoffset, + TagType 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 = FromBE32(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(Decoder *decoder, InputStream &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 auto size = is.GetSize(); + while (is.GetOffset() < size) { + uint64_t chunk_size = chunk_header->GetSize(); + + /* DIIN chunk, is directly followed by other chunks */ + if (chunk_header->id.Equals("DIIN")) + chunk_size = 0; + + /* DIAR chunk - DSDIFF native tag for Artist */ + if (chunk_header->id.Equals("DIAR")) { + chunk_size = chunk_header->GetSize(); + metadata->diar_offset = is.GetOffset(); + } + + /* DITI chunk - DSDIFF native tag for Title */ + if (chunk_header->id.Equals("DITI")) { + chunk_size = chunk_header->GetSize(); + metadata->diti_offset = is.GetOffset(); + } +#ifdef HAVE_ID3TAG + /* 'ID3 ' chunk, offspec. Used by sacdextract */ + if (chunk_header->id.Equals("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; + } + } + /* 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(Decoder *decoder, InputStream &is, + DsdiffMetaData *metadata, + DsdiffChunkHeader *chunk_header) +{ + DsdiffHeader header; + if (!dsdlib_read(decoder, is, &header, sizeof(header)) || + !header.id.Equals("FRM8") || + !header.format.Equals("DSD ")) + return false; + + while (true) { + if (!dsdiff_read_chunk_header(decoder, is, + chunk_header)) + return false; + + if (chunk_header->id.Equals("PROP")) { + if (!dsdiff_read_prop(decoder, is, metadata, + chunk_header)) + return false; + } else if (chunk_header->id.Equals("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(); + InputStream::offset_type 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(Decoder &decoder, InputStream &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 */ + size_t now_size = buffer_size; + if (chunk_size < (uint64_t)now_size) { + unsigned 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(Decoder &decoder, InputStream &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 (chunk_header.id.Equals("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(InputStream &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 DecoderPlugin 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/plugins/DsdiffDecoderPlugin.hxx b/src/decoder/plugins/DsdiffDecoderPlugin.hxx new file mode 100644 index 000000000..7aa36752b --- /dev/null +++ b/src/decoder/plugins/DsdiffDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderPlugin dsdiff_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/DsfDecoderPlugin.cxx b/src/decoder/plugins/DsfDecoderPlugin.cxx new file mode 100644 index 000000000..6fbaf6bf6 --- /dev/null +++ b/src/decoder/plugins/DsfDecoderPlugin.cxx @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "util/bit_reverse.h" +#include "util/Error.hxx" +#include "system/ByteOrder.hxx" +#include "DsdLib.hxx" +#include "tag/TagHandler.hxx" +#include "Log.hxx" + +struct DsfMetaData { + unsigned sample_rate, channels; + bool bitreverse; + uint64_t chunk_size; +#ifdef HAVE_ID3TAG + InputStream::offset_type id3_offset; + uint64_t id3_size; +#endif +}; + +struct DsfHeader { + /** DSF header id: "DSD " */ + DsdId id; + /** DSD chunk size, including id = 28 */ + DsdUint64 size; + /** total file size */ + DsdUint64 fsize; + /** pointer to id3v2 metadata, should be at the end of the file */ + DsdUint64 pmeta; +}; + +/** DSF file fmt chunk */ +struct DsfFmtChunk { + /** id: "fmt " */ + DsdId id; + /** fmt chunk size, including id, normally 52 */ + DsdUint64 size; + /** 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 */ + DsdUint64 scnt; + /** block size per channel = 4096 */ + uint32_t block_size; + /** reserved, should be all zero */ + uint32_t reserved; +}; + +struct DsfDataChunk { + DsdId id; + /** "data" chunk size, includes header (id+size) */ + DsdUint64 size; +}; + +/** + * Read and parse all needed metadata chunks for DSF files. + */ +static bool +dsf_read_metadata(Decoder *decoder, InputStream &is, + DsfMetaData *metadata) +{ + DsfHeader dsf_header; + if (!dsdlib_read(decoder, is, &dsf_header, sizeof(dsf_header)) || + !dsf_header.id.Equals("DSD ")) + return false; + + const uint64_t chunk_size = dsf_header.size.Read(); + if (sizeof(dsf_header) != chunk_size) + return false; + +#ifdef HAVE_ID3TAG + const uint64_t metadata_offset = dsf_header.pmeta.Read(); +#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)) || + !dsf_fmt_chunk.id.Equals("fmt ")) + return false; + + const uint64_t fmt_chunk_size = dsf_fmt_chunk.size.Read(); + if (fmt_chunk_size != sizeof(dsf_fmt_chunk)) + return false; + + uint32_t samplefreq = FromLE32(dsf_fmt_chunk.sample_freq); + + /* for now, only support version 1 of the standard, DSD raw stereo + files with a sample freq of 2822400 or 5644800 Hz */ + + if (dsf_fmt_chunk.version != 1 || dsf_fmt_chunk.formatid != 0 + || dsf_fmt_chunk.channeltype != 2 + || dsf_fmt_chunk.channelnum != 2 + || (samplefreq != 2822400 && samplefreq != 5644800)) + return false; + + uint32_t chblksize = FromLE32(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)) || + !data_chunk.id.Equals("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_chunk.size.Read(); + if (data_size < sizeof(data_chunk)) + return false; + + data_size -= sizeof(data_chunk); + + /* 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; + + /* use the sample count from the DSF header as the upper + bound, because some DSF files contain junk at the end of + the "data" chunk */ + const uint64_t samplecnt = dsf_fmt_chunk.scnt.Read(); + const uint64_t playable_size = samplecnt * 2 / 8; + if (data_size > playable_size) + data_size = playable_size; + + metadata->chunk_size = data_size; + 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 = (InputStream::offset_type)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(Decoder &decoder, InputStream &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 */ + size_t now_size = buffer_size; + if (chunk_size < (uint64_t)now_size) { + unsigned 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(Decoder &decoder, InputStream &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(InputStream &is, + gcc_unused const struct tag_handler *handler, + gcc_unused void *handler_ctx) +{ + /* check DSF metadata */ + DsfMetaData metadata; + if (!dsf_read_metadata(nullptr, 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", + nullptr +}; + +static const char *const dsf_mime_types[] = { + "application/x-dsf", + nullptr +}; + +const struct DecoderPlugin 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/plugins/DsfDecoderPlugin.hxx b/src/decoder/plugins/DsfDecoderPlugin.hxx new file mode 100644 index 000000000..02bea0b5c --- /dev/null +++ b/src/decoder/plugins/DsfDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderPlugin dsf_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/FaadDecoderPlugin.cxx b/src/decoder/plugins/FaadDecoderPlugin.cxx new file mode 100644 index 000000000..e80665d24 --- /dev/null +++ b/src/decoder/plugins/FaadDecoderPlugin.cxx @@ -0,0 +1,464 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "util/ConstBuffer.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) +{ + while (true) { + auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer)); + if (data.size < 8) { + /* not enough data yet */ + if (!decoder_buffer_fill(buffer)) + /* failed */ + return 0; + + continue; + } + + /* find the 0xff marker */ + const uint8_t *p = (const uint8_t *) + memchr(data.data, 0xff, data.size); + if (p == nullptr) { + /* no marker - discard the buffer */ + decoder_buffer_clear(buffer); + continue; + } + + if (p > data.data) { + /* discard data before 0xff */ + decoder_buffer_consume(buffer, p - data.data); + continue; + } + + /* is it a frame? */ + size_t frame_length = adts_check_frame(data.data); + if (frame_length == 0) { + /* it's just some random 0xff byte; discard it + and continue searching */ + decoder_buffer_consume(buffer, 1); + continue; + } + + if (data.size < frame_length) { + /* available buffer size is smaller than the + frame will be - attempt to read more + data */ + if (!decoder_buffer_fill(buffer)) { + /* not enough data; discard this frame + to prevent a possible buffer + overflow */ + decoder_buffer_clear(buffer); + } + + continue; + } + + /* found a full frame! */ + return frame_length; + } +} + +static float +adts_song_duration(DecoderBuffer *buffer) +{ + unsigned sample_rate = 0; + + /* Read all frames to ensure correct time and bitrate */ + unsigned frames = 0; + for (;; frames++) { + unsigned frame_length = adts_find_frame(buffer); + if (frame_length == 0) + break; + + if (frames == 0) { + auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer)); + assert(!data.IsEmpty()); + assert(frame_length <= data.size); + + sample_rate = adts_sample_rates[(data.data[2] & 0x3c) >> 2]; + } + + decoder_buffer_consume(buffer, frame_length); + } + + float 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, InputStream &is) +{ + const auto size = is.GetSize(); + const size_t fileread = size >= 0 ? size : 0; + + decoder_buffer_fill(buffer); + auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer)); + if (data.IsEmpty()) + return -1; + + size_t tagsize = 0; + if (data.size >= 10 && !memcmp(data.data, "ID3", 3)) { + /* skip the ID3 tag */ + + tagsize = (data.data[6] << 21) | (data.data[7] << 14) | + (data.data[8] << 7) | (data.data[9] << 0); + + tagsize += 10; + + bool success = decoder_buffer_skip(buffer, tagsize) && + decoder_buffer_fill(buffer); + if (!success) + return -1; + + data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer)); + if (data.IsEmpty()) + return -1; + } + + if (is.IsSeekable() && data.size >= 2 && + data.data[0] == 0xFF && ((data.data[1] & 0xF6) == 0xF0)) { + /* obtain the duration from the ADTS header */ + float song_length = adts_song_duration(buffer); + + is.LockSeek(tagsize, SEEK_SET, IgnoreError()); + + decoder_buffer_clear(buffer); + decoder_buffer_fill(buffer); + + return song_length; + } else if (data.size >= 5 && memcmp(data.data, "ADIF", 4) == 0) { + /* obtain the duration from the ADIF header */ + unsigned bit_rate; + size_t skip_size = (data.data[4] & 0x80) ? 9 : 0; + + if (8 + skip_size > data.size) + /* not enough data yet; skip parsing this + header */ + return -1; + + bit_rate = ((data.data[4 + skip_size] & 0x0F) << 19) | + (data.data[5 + skip_size] << 11) | + (data.data[6 + skip_size] << 3) | + (data.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) +{ + uint32_t sample_rate; +#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 + + auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer)); + if (data.IsEmpty()) { + error.Set(faad_decoder_domain, "Empty file"); + return false; + } + + uint8_t channels; + int32_t nbytes = NeAACDecInit(decoder, + /* deconst hack, libfaad requires this */ + const_cast<uint8_t *>(data.data), + data.size, + 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) +{ + auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer)); + if (data.IsEmpty()) + return nullptr; + + return NeAACDecDecode(decoder, frame_info, + /* deconst hack, libfaad requires this */ + const_cast<uint8_t *>(data.data), + data.size); +} + +/** + * 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(InputStream &is) +{ + DecoderBuffer *buffer = + decoder_buffer_new(nullptr, is, + FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); + float length = faad_song_duration(buffer, is); + + if (length < 0) { + NeAACDecHandle decoder = NeAACDecOpen(); + + NeAACDecConfigurationPtr config = + NeAACDecGetCurrentConfiguration(decoder); + config->outputFormat = FAAD_FMT_16BIT; + NeAACDecSetConfiguration(decoder, config); + + decoder_buffer_fill(buffer); + + AudioFormat audio_format; + if (faad_decoder_init(decoder, buffer, audio_format, + IgnoreError())) + 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(InputStream &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(Decoder &mpd_decoder, InputStream &is) +{ + DecoderBuffer *buffer = + decoder_buffer_new(&mpd_decoder, is, + FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); + const float 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; + AudioFormat audio_format; + if (!faad_decoder_init(decoder, buffer, audio_format, error)) { + LogError(error); + NeAACDecClose(decoder); + decoder_buffer_free(buffer); + return; + } + + /* initialize the MPD core */ + + decoder_initialized(mpd_decoder, audio_format, false, total_time); + + /* the decoder loop */ + + DecoderCommand cmd; + unsigned bit_rate = 0; + 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) { + FormatDefault(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) { + FormatDefault(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); + decoder_buffer_free(buffer); +} + +static bool +faad_scan_stream(InputStream &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 DecoderPlugin 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/plugins/FaadDecoderPlugin.hxx b/src/decoder/plugins/FaadDecoderPlugin.hxx new file mode 100644 index 000000000..968433e9b --- /dev/null +++ b/src/decoder/plugins/FaadDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderPlugin faad_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx new file mode 100644 index 000000000..66b3ec0c1 --- /dev/null +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -0,0 +1,686 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "LogV.hxx" + +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> + +#if LIBAVUTIL_VERSION_MAJOR >= 53 +#include <libavutil/frame.h> +#endif +} + +#include <assert.h> +#include <string.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 = nullptr; + + if (ptr != nullptr) + cls = *(const AVClass *const*)ptr; + + if (cls != nullptr) { + char domain[64]; + snprintf(domain, sizeof(domain), "%s/%s", + ffmpeg_domain.GetName(), cls->item_name(ptr)); + const Domain d(domain); + LogFormatV(d, import_ffmpeg_level(level), fmt, vl); + } +} + +struct AvioStream { + Decoder *const decoder; + InputStream &input; + + AVIOContext *io; + + unsigned char buffer[8192]; + + AvioStream(Decoder *_decoder, InputStream &_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; + + if (!stream->input.LockSeek(pos, whence, IgnoreError())) + 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 == nullptr) + return AVERROR(ENOMEM); + + context->pb = pb; + *ic_ptr = context; + return avformat_open_input(ic_ptr, filename, fmt, nullptr); +} + +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 (data_size <= 0) + return data_size; + + 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(Decoder &decoder, InputStream &is, + const AVPacket *packet, + AVCodecContext *codec_context, + const AVStream *stream, + 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 - stream->start_time, + stream->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 */ + LogDefault(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 != nullptr) + 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(Decoder *decoder, InputStream &is) +{ + enum { + BUFFER_SIZE = 16384, + PADDING = 16, + }; + + unsigned char buffer[BUFFER_SIZE]; + size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE); + if (nbytes <= PADDING || !is.LockRewind(IgnoreError())) + return nullptr; + + /* 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(); + + return av_probe_input_format(&avpd, true); +} + +static void +ffmpeg_decode(Decoder &decoder, InputStream &input) +{ + AVInputFormat *input_format = ffmpeg_probe(&decoder, input); + if (input_format == nullptr) + 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 = nullptr; + 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, nullptr); + 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, nullptr); + 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); + +#if LIBAVUTIL_VERSION_MAJOR >= 53 + AVFrame *frame = av_frame_alloc(); +#else + AVFrame *frame = avcodec_alloc_frame(); +#endif + if (!frame) { + LogError(ffmpeg_domain, "Could not allocate frame"); + avformat_close_input(&format_context); + return; + } + + uint8_t *interleaved_buffer = nullptr; + 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, + 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) + + av_stream->start_time; + + 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 LIBAVUTIL_VERSION_MAJOR >= 53 + av_frame_free(&frame); +#elif 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(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + AVInputFormat *input_format = ffmpeg_probe(nullptr, is); + if (input_format == nullptr) + return false; + + AvioStream stream(nullptr, is); + if (!stream.Open()) + return false; + + AVFormatContext *f = nullptr; + 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, nullptr); + 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", + nullptr +}; + +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", + + nullptr +}; + +const struct DecoderPlugin 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/plugins/FfmpegDecoderPlugin.hxx b/src/decoder/plugins/FfmpegDecoderPlugin.hxx new file mode 100644 index 000000000..0a3e78e4b --- /dev/null +++ b/src/decoder/plugins/FfmpegDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderPlugin ffmpeg_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/FfmpegMetaData.cxx b/src/decoder/plugins/FfmpegMetaData.cxx new file mode 100644 index 000000000..a39466945 --- /dev/null +++ b/src/decoder/plugins/FfmpegMetaData.cxx @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 */ + { nullptr, TAG_NUM_OF_ITEM_TYPES } +}; + +static void +ffmpeg_copy_metadata(TagType type, + AVDictionary *m, const char *name, + const struct tag_handler *handler, void *handler_ctx) +{ + AVDictionaryEntry *mt = nullptr; + + while ((mt = av_dict_get(m, name, mt, 0)) != nullptr) + 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 = nullptr; + + while ((i = av_dict_get(dict, "", i, AV_DICT_IGNORE_SUFFIX)) != nullptr) + 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(TagType(i), dict, tag_item_names[i], + handler, handler_ctx); + + for (const struct tag_table *i = ffmpeg_tags; + i->name != nullptr; ++i) + ffmpeg_copy_metadata(i->type, dict, i->name, + handler, handler_ctx); + + if (handler->pair != nullptr) + ffmpeg_scan_pairs(dict, handler, handler_ctx); +} diff --git a/src/decoder/plugins/FfmpegMetaData.hxx b/src/decoder/plugins/FfmpegMetaData.hxx new file mode 100644 index 000000000..5eb41db68 --- /dev/null +++ b/src/decoder/plugins/FfmpegMetaData.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 <libavutil/dict.h> +} + +/* suppress the ffmpeg compatibility macro */ +#ifdef SampleFormat +#undef SampleFormat +#endif + +struct tag_handler; + +void +ffmpeg_scan_dictionary(AVDictionary *dict, + const tag_handler *handler, void *handler_ctx); + +#endif diff --git a/src/decoder/plugins/FlacCommon.cxx b/src/decoder/plugins/FlacCommon.cxx new file mode 100644 index 000000000..7b67585a0 --- /dev/null +++ b/src/decoder/plugins/FlacCommon.cxx @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "Log.hxx" + +flac_data::flac_data(Decoder &_decoder, + InputStream &_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; + + ReplayGainInfo rgi; + + 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); + + decoder_mixramp(data->decoder, flac_parse_mixramp(block)); + + data->tag = flac_vorbis_comments_to_tag(&block->data.vorbis_comment); + break; + + 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/plugins/FlacCommon.hxx b/src/decoder/plugins/FlacCommon.hxx new file mode 100644 index 000000000..34ce0a3fc --- /dev/null +++ b/src/decoder/plugins/FlacCommon.hxx @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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> + +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; + + Decoder &decoder; + InputStream &input_stream; + + Tag tag; + + flac_data(Decoder &decoder, InputStream &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/plugins/FlacDecoderPlugin.cxx b/src/decoder/plugins/FlacDecoderPlugin.cxx new file mode 100644 index 000000000..17949f8c5 --- /dev/null +++ b/src/decoder/plugins/FlacDecoderPlugin.cxx @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with 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" + +#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(InputStream &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) +{ + 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(Decoder &decoder, + InputStream &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(Decoder &decoder, InputStream &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(InputStream &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(Decoder &decoder, InputStream &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.LockRewind(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 DecoderPlugin 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 DecoderPlugin 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/plugins/FlacDecoderPlugin.h b/src/decoder/plugins/FlacDecoderPlugin.h new file mode 100644 index 000000000..fcdecf869 --- /dev/null +++ b/src/decoder/plugins/FlacDecoderPlugin.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderPlugin flac_decoder_plugin; +extern const struct DecoderPlugin oggflac_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/FlacDomain.cxx b/src/decoder/plugins/FlacDomain.cxx new file mode 100644 index 000000000..fc5cc5498 --- /dev/null +++ b/src/decoder/plugins/FlacDomain.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/plugins/FlacDomain.hxx b/src/decoder/plugins/FlacDomain.hxx new file mode 100644 index 000000000..a06c6c6b4 --- /dev/null +++ b/src/decoder/plugins/FlacDomain.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/FlacIOHandle.cxx b/src/decoder/plugins/FlacIOHandle.cxx new file mode 100644 index 000000000..aecee638b --- /dev/null +++ b/src/decoder/plugins/FlacIOHandle.cxx @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "Compiler.h" + +#include <errno.h> + +static size_t +FlacIORead(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle) +{ + InputStream *is = (InputStream *)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) +{ + InputStream *is = (InputStream *)handle; + + Error error; + return is->LockSeek(offset, whence, error) ? 0 : -1; +} + +static FLAC__int64 +FlacIOTell(FLAC__IOHandle handle) +{ + InputStream *is = (InputStream *)handle; + + return is->offset; +} + +static int +FlacIOEof(FLAC__IOHandle handle) +{ + InputStream *is = (InputStream *)handle; + + return is->LockIsEOF(); +} + +static int +FlacIOClose(gcc_unused FLAC__IOHandle handle) +{ + /* no-op because the libFLAC caller is repsonsible for closing + the #InputStream */ + + 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/plugins/FlacIOHandle.hxx b/src/decoder/plugins/FlacIOHandle.hxx new file mode 100644 index 000000000..a4c3ab86d --- /dev/null +++ b/src/decoder/plugins/FlacIOHandle.hxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.h" +#include "input/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(InputStream &is) +{ + return (FLAC__IOHandle)&is; +} + +static inline const FLAC__IOCallbacks & +GetFlacIOCallbacks(const InputStream &is) +{ + return is.seekable + ? flac_io_callbacks_seekable + : flac_io_callbacks; +} + +#endif diff --git a/src/decoder/plugins/FlacInput.cxx b/src/decoder/plugins/FlacInput.cxx new file mode 100644 index 000000000..720b115f7 --- /dev/null +++ b/src/decoder/plugins/FlacInput.cxx @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "input/InputStream.hxx" +#include "util/Error.hxx" +#include "Log.hxx" +#include "Compiler.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/plugins/FlacInput.hxx b/src/decoder/plugins/FlacInput.hxx new file mode 100644 index 000000000..30ed55fd0 --- /dev/null +++ b/src/decoder/plugins/FlacInput.hxx @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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> + +struct Decoder; +struct InputStream; + +/** + * This class wraps an #InputStream in libFLAC stream decoder + * callbacks. + */ +class FlacInput { + Decoder *const decoder; + + InputStream &input_stream; + +public: + FlacInput(InputStream &_input_stream, + 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/plugins/FlacMetadata.cxx b/src/decoder/plugins/FlacMetadata.cxx new file mode 100644 index 000000000..b921e8481 --- /dev/null +++ b/src/decoder/plugins/FlacMetadata.cxx @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "MixRampInfo.hxx" +#include "tag/TagHandler.hxx" +#include "tag/TagTable.hxx" +#include "tag/TagBuilder.hxx" +#include "tag/Tag.hxx" +#include "ReplayGainInfo.hxx" +#include "util/ASCII.hxx" +#include "util/SplitString.hxx" + +#include <string.h> + +static const char * +vorbis_comment_value(const FLAC__StreamMetadata *block, + const char *name) +{ + int offset = + FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, + name); + if (offset < 0) + return nullptr; + + size_t name_length = strlen(name); + + const FLAC__StreamMetadata_VorbisComment_Entry &vc = + block->data.vorbis_comment.comments[offset]; + const char *comment = (const char *)vc.entry; + + /* 1 is for '=' */ + return comment + name_length + 1; +} + +static bool +flac_find_float_comment(const FLAC__StreamMetadata *block, + const char *cmnt, float *fl) +{ + const char *value = vorbis_comment_value(block, cmnt); + if (value == nullptr) + return false; + + *fl = (float)atof(value); + return true; +} + +bool +flac_parse_replay_gain(ReplayGainInfo &rgi, + const FLAC__StreamMetadata *block) +{ + rgi.Clear(); + + bool found = false; + 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; +} + +gcc_pure +static std::string +flac_find_string_comment(const FLAC__StreamMetadata *block, const char *cmnt) +{ + const char *value = vorbis_comment_value(block, cmnt); + if (value == nullptr) + return std::string(); + + return std::string(value); +} + +MixRampInfo +flac_parse_mixramp(const FLAC__StreamMetadata *block) +{ + MixRampInfo mix_ramp; + mix_ramp.SetStart(flac_find_string_comment(block, "mixramp_start")); + mix_ramp.SetEnd(flac_find_string_comment(block, "mixramp_end")); + return mix_ramp; +} + +/** + * Checks if the specified name matches the entry's name, and if yes, + * returns the comment value; + */ +static const char * +flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, + const char *name) +{ + size_t name_length = strlen(name); + const char *comment = (const char*)entry->entry; + + if (!StringEqualsCaseASCII(comment, name, name_length)) + return nullptr; + + if (comment[name_length] == '=') { + 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, TagType tag_type, + const struct tag_handler *handler, void *handler_ctx) +{ + const char *value = flac_comment_value(entry, name); + if (value != nullptr) { + tag_handler_invoke_tag(handler, handler_ctx, tag_type, value); + 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) { + const char *comment = (const char *)entry->entry; + const SplitString split(comment, '='); + if (split.IsDefined() && !split.IsEmpty()) + tag_handler_invoke_pair(handler, handler_ctx, + split.GetFirst(), + split.GetSecond()); + } + + 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], (TagType)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; + } +} + +Tag +flac_vorbis_comments_to_tag(const FLAC__StreamMetadata_VorbisComment *comment) +{ + TagBuilder tag_builder; + flac_scan_comments(comment, &add_tag_handler, &tag_builder); + return tag_builder.Commit(); +} + +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/plugins/FlacMetadata.hxx b/src/decoder/plugins/FlacMetadata.hxx new file mode 100644 index 000000000..e0449b2a2 --- /dev/null +++ b/src/decoder/plugins/FlacMetadata.hxx @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.h" +#include "FlacIOHandle.hxx" + +#include <FLAC/metadata.h> + +#include <assert.h> + +struct tag_handler; +class MixRampInfo; + +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(InputStream &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(InputStream &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 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; +struct ReplayGainInfo; + +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(ReplayGainInfo &rgi, + const FLAC__StreamMetadata *block); + +MixRampInfo +flac_parse_mixramp(const FLAC__StreamMetadata *block); + +Tag +flac_vorbis_comments_to_tag(const FLAC__StreamMetadata_VorbisComment *comment); + +void +flac_scan_metadata(const FLAC__StreamMetadata *block, + const tag_handler *handler, void *handler_ctx); + +#endif diff --git a/src/decoder/plugins/FlacPcm.cxx b/src/decoder/plugins/FlacPcm.cxx new file mode 100644 index 000000000..311500f26 --- /dev/null +++ b/src/decoder/plugins/FlacPcm.cxx @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/plugins/FlacPcm.hxx b/src/decoder/plugins/FlacPcm.hxx new file mode 100644 index 000000000..30c318725 --- /dev/null +++ b/src/decoder/plugins/FlacPcm.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/FluidsynthDecoderPlugin.cxx b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx new file mode 100644 index 000000000..3b4703e97 --- /dev/null +++ b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "util/Macros.hxx" +#include "Log.hxx" + +#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(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 = ARRAY_SIZE(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 DecoderPlugin fluidsynth_decoder_plugin = { + "fluidsynth", + fluidsynth_init, + nullptr, + nullptr, + fluidsynth_file_decode, + fluidsynth_scan_file, + nullptr, + nullptr, + fluidsynth_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/FluidsynthDecoderPlugin.hxx b/src/decoder/plugins/FluidsynthDecoderPlugin.hxx new file mode 100644 index 000000000..cd8ec2d62 --- /dev/null +++ b/src/decoder/plugins/FluidsynthDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderPlugin fluidsynth_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/GmeDecoderPlugin.cxx b/src/decoder/plugins/GmeDecoderPlugin.cxx new file mode 100644 index 000000000..53015facf --- /dev/null +++ b/src/decoder/plugins/GmeDecoderPlugin.cxx @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/Alloc.hxx" +#include "util/FormatString.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 = xstrdup(path_fs); + + char pat[64]; + snprintf(pat, sizeof(pat), "%s%s", + "*/" SUBTUNE_PREFIX "???.", + subtune_suffix); + GPatternSpec *path_with_subtune = g_pattern_spec_new(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[64]; + snprintf(pat, sizeof(pat), "%s%s", + "*/" SUBTUNE_PREFIX "???.", + subtune_suffix); + GPatternSpec *path_with_subtune = g_pattern_spec_new(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){ + return FormatNew(SUBTUNE_PREFIX "%03u.%s", + tnum, subtune_suffix); + } else + return nullptr; +} + +static void +gme_file_decode(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); + 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[1024]; + snprintf(tag_title, sizeof(tag_title), + "%s (%d/%d)", + ti->song, song_num + 1, + gme_track_count(emu)); + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, 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 DecoderPlugin gme_decoder_plugin; +const struct DecoderPlugin 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/plugins/GmeDecoderPlugin.hxx b/src/decoder/plugins/GmeDecoderPlugin.hxx new file mode 100644 index 000000000..f4885b6e4 --- /dev/null +++ b/src/decoder/plugins/GmeDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderPlugin gme_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/MadDecoderPlugin.cxx b/src/decoder/plugins/MadDecoderPlugin.cxx new file mode 100644 index 000000000..ffd63a2ac --- /dev/null +++ b/src/decoder/plugins/MadDecoderPlugin.cxx @@ -0,0 +1,1155 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "input/InputStream.hxx" +#include "config/ConfigGlobal.hxx" +#include "tag/TagId3.hxx" +#include "tag/TagRva2.hxx" +#include "tag/TagHandler.hxx" +#include "CheckAudioFormat.hxx" +#include "util/StringUtil.hxx" +#include "util/ASCII.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <mad.h> + +#ifdef HAVE_ID3TAG +#include <id3tag.h> +#endif + +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#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; + Decoder *const decoder; + InputStream &input_stream; + enum mad_layer layer; + + MadDecoder(Decoder *decoder, InputStream &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 + InputStream::offset_type ThisFrameOffset() const; + + gcc_pure + InputStream::offset_type 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(Decoder *_decoder, + InputStream &_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(ReplayGainInfo &rgi, + struct id3_tag *tag) +{ + int i; + char *key; + char *value; + struct id3_frame *frame; + bool found = false; + + rgi.Clear(); + + 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 (StringEqualsCaseASCII(key, "replaygain_track_gain")) { + rgi.tuples[REPLAY_GAIN_TRACK].gain = atof(value); + found = true; + } else if (StringEqualsCaseASCII(key, "replaygain_album_gain")) { + rgi.tuples[REPLAY_GAIN_ALBUM].gain = atof(value); + found = true; + } else if (StringEqualsCaseASCII(key, "replaygain_track_peak")) { + rgi.tuples[REPLAY_GAIN_TRACK].peak = atof(value); + found = true; + } else if (StringEqualsCaseASCII(key, "replaygain_album_peak")) { + rgi.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, rgi); +} +#endif + +#ifdef HAVE_ID3TAG +gcc_pure +static MixRampInfo +parse_id3_mixramp(struct id3_tag *tag) +{ + int i; + char *key; + char *value; + struct id3_frame *frame; + + MixRampInfo result; + + 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 (StringEqualsCaseASCII(key, "mixramp_start")) { + result.SetStart(value); + } else if (StringEqualsCaseASCII(key, "mixramp_end")) { + result.SetEnd(value); + } + + free(key); + free(value); + } + + return result; +} +#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 = new id3_byte_t[tagsize]; + memcpy(allocated, stream.this_frame, count); + mad_stream_skip(&(stream), count); + + if (!decoder_read_full(decoder, input_stream, + allocated + count, tagsize - count)) { + LogDebug(mad_domain, "error parsing ID3 tag"); + delete[] allocated; + return; + } + + id3_data = allocated; + } + + id3_tag = id3_tag_parse(id3_data, tagsize); + if (id3_tag == nullptr) { + delete[] 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) { + ReplayGainInfo rgi; + + if (parse_id3_replay_gain_info(rgi, id3_tag)) { + decoder_replay_gain(*decoder, &rgi); + found_replay_gain = true; + } + + decoder_mixramp(*decoder, parse_id3_mixramp(id3_tag)); + } + + id3_tag_delete(id3_tag); + + delete[] 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); + decoder_skip(decoder, input_stream, tagsize - count); + } +#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 (!StringStartsWith(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 InputStream::offset_type +MadDecoder::ThisFrameOffset() const +{ + auto offset = input_stream.GetOffset(); + + if (stream.this_frame != nullptr) + offset -= stream.bufend - stream.this_frame; + else + offset -= stream.bufend - stream.buffer; + + return offset; +} + +inline InputStream::offset_type +MadDecoder::RestIncludingThisFrame() const +{ + return input_stream.GetSize() - ThisFrameOffset(); +} + +inline void +MadDecoder::FileSizeToSongLength() +{ + InputStream::offset_type 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) { + ReplayGainInfo rgi; + rgi.Clear(); + 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(InputStream &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(Decoder &decoder, InputStream &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/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(InputStream &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 DecoderPlugin 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/plugins/MadDecoderPlugin.hxx b/src/decoder/plugins/MadDecoderPlugin.hxx new file mode 100644 index 000000000..eb2a10d6f --- /dev/null +++ b/src/decoder/plugins/MadDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderPlugin mad_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/MikmodDecoderPlugin.cxx b/src/decoder/plugins/MikmodDecoderPlugin.cxx new file mode 100644 index 000000000..aacb5922d --- /dev/null +++ b/src/decoder/plugins/MikmodDecoderPlugin.cxx @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 <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 bool mikmod_loop; +static unsigned mikmod_sample_rate; + +static bool +mikmod_decoder_init(const config_param ¶m) +{ + static char params[] = ""; + + mikmod_loop = param.GetBlockValue("loop", false); + 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(Decoder &decoder, const char *path_fs) +{ + /* deconstify the path because libmikmod wants a non-const + string pointer */ + char *const path2 = const_cast<char *>(path_fs); + + MODULE *handle; + int ret; + SBYTE buffer[MIKMOD_FRAME_SIZE]; + + handle = Player_Load(path2, 128, 0); + + if (handle == nullptr) { + FormatError(mikmod_domain, + "failed to open mod: %s", path_fs); + return; + } + + handle->loop = mikmod_loop; + + 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) +{ + /* deconstify the path because libmikmod wants a non-const + string pointer */ + char *const path2 = const_cast<char *>(path_fs); + + MODULE *handle = Player_Load(path2, 128, 0); + + if (handle == nullptr) { + FormatDebug(mikmod_domain, + "Failed to open file: %s", path_fs); + return false; + } + + Player_Free(handle); + + char *title = Player_LoadTitle(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 DecoderPlugin 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/plugins/MikmodDecoderPlugin.hxx b/src/decoder/plugins/MikmodDecoderPlugin.hxx new file mode 100644 index 000000000..27ba2a823 --- /dev/null +++ b/src/decoder/plugins/MikmodDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderPlugin mikmod_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/ModplugDecoderPlugin.cxx b/src/decoder/plugins/ModplugDecoderPlugin.cxx new file mode 100644 index 000000000..336870817 --- /dev/null +++ b/src/decoder/plugins/ModplugDecoderPlugin.cxx @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "input/InputStream.hxx" +#include "tag/TagHandler.hxx" +#include "system/FatalError.hxx" +#include "util/WritableBuffer.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <libmodplug/modplug.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 InputStream::offset_type MODPLUG_FILE_LIMIT = 100 * 1024 * 1024; + +static int modplug_loop_count; + +static bool +modplug_decoder_init(const config_param ¶m) +{ + modplug_loop_count = param.GetBlockValue("loop_count", 0); + if (modplug_loop_count < -1) + FormatFatalError("Invalid loop count in line %d: %i", + param.line, modplug_loop_count); + + return true; +} + +static WritableBuffer<uint8_t> +mod_loadfile(Decoder *decoder, InputStream &is) +{ + const InputStream::offset_type size = is.GetSize(); + + if (size == 0) { + LogWarning(modplug_domain, "file is empty"); + return { nullptr, 0 }; + } + + if (size > MODPLUG_FILE_LIMIT) { + LogWarning(modplug_domain, "file too large"); + return { nullptr, 0 }; + } + + //known/unknown size, preallocate array, lets read in chunks + + const bool is_stream = size < 0; + + WritableBuffer<uint8_t> buffer; + buffer.size = is_stream ? MODPLUG_PREALLOC_BLOCK : size; + buffer.data = new uint8_t[buffer.size]; + + uint8_t *const end = buffer.end(); + uint8_t *p = buffer.begin(); + + while (true) { + size_t ret = decoder_read(decoder, is, p, end - p); + if (ret == 0) { + if (is.LockIsEOF()) + /* end of file */ + break; + + /* I/O error - skip this song */ + delete[] buffer.data; + buffer.data = nullptr; + return buffer; + } + + p += ret; + if (p == end) { + if (!is_stream) + break; + + LogWarning(modplug_domain, "stream too large"); + delete[] buffer.data; + buffer.data = nullptr; + return buffer; + } + } + + buffer.size = p - buffer.data; + return buffer; +} + +static ModPlugFile * +LoadModPlugFile(Decoder *decoder, InputStream &is) +{ + const auto buffer = mod_loadfile(decoder, is); + if (buffer.IsNull()) { + LogWarning(modplug_domain, "could not load stream"); + return nullptr; + } + + ModPlugFile *f = ModPlug_Load(buffer.data, buffer.size); + delete[] buffer.data; + return f; +} + +static void +mod_decode(Decoder &decoder, InputStream &is) +{ + ModPlug_Settings settings; + int ret; + char audio_buffer[MODPLUG_FRAME_SIZE]; + + ModPlug_GetSettings(&settings); + /* alter setting */ + settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; /* RESAMP */ + settings.mChannels = 2; + settings.mBits = 16; + settings.mFrequency = 44100; + settings.mLoopCount = modplug_loop_count; + /* insert more setting changes here */ + ModPlug_SetSettings(&settings); + + ModPlugFile *f = LoadModPlugFile(&decoder, is); + if (f == nullptr) { + 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(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + ModPlugFile *f = LoadModPlugFile(nullptr, is); + 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 DecoderPlugin modplug_decoder_plugin = { + "modplug", + modplug_decoder_init, + nullptr, + mod_decode, + nullptr, + nullptr, + modplug_scan_stream, + nullptr, + mod_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/ModplugDecoderPlugin.hxx b/src/decoder/plugins/ModplugDecoderPlugin.hxx new file mode 100644 index 000000000..08f2ecb12 --- /dev/null +++ b/src/decoder/plugins/ModplugDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderPlugin modplug_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/MpcdecDecoderPlugin.cxx b/src/decoder/plugins/MpcdecDecoderPlugin.cxx new file mode 100644 index 000000000..18f6e7673 --- /dev/null +++ b/src/decoder/plugins/MpcdecDecoderPlugin.cxx @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "util/Macros.hxx" +#include "Log.hxx" + +#include <mpc/mpcdec.h> + +#include <math.h> + +struct mpc_decoder_data { + InputStream &is; + Decoder *decoder; + + mpc_decoder_data(InputStream &_is, Decoder *_decoder) + :is(_is), 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(Decoder &mpd_decoder, InputStream &is) +{ + MPC_SAMPLE_FORMAT sample_buffer[MPC_DECODER_BUFFER_LENGTH]; + + mpc_decoder_data data(is, &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; + } + + ReplayGainInfo rgi; + rgi.Clear(); + rgi.tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.); + rgi.tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767; + rgi.tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.); + rgi.tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767; + + decoder_replay_gain(mpd_decoder, &rgi); + + 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[ARRAY_SIZE(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(InputStream &is) +{ + mpc_decoder_data data(is, 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(InputStream &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 DecoderPlugin mpcdec_decoder_plugin = { + "mpcdec", + nullptr, + nullptr, + mpcdec_decode, + nullptr, + nullptr, + mpcdec_scan_stream, + nullptr, + mpcdec_suffixes, + nullptr, +}; diff --git a/src/decoder/plugins/MpcdecDecoderPlugin.hxx b/src/decoder/plugins/MpcdecDecoderPlugin.hxx new file mode 100644 index 000000000..7f71311fa --- /dev/null +++ b/src/decoder/plugins/MpcdecDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderPlugin mpcdec_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/Mpg123DecoderPlugin.cxx b/src/decoder/plugins/Mpg123DecoderPlugin.cxx new file mode 100644 index 000000000..cf8573c85 --- /dev/null +++ b/src/decoder/plugins/Mpg123DecoderPlugin.cxx @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with 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 <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) +{ + int error; + int channels, encoding; + long rate; + + /* mpg123_open() wants a writable string :-( */ + char *const path2 = const_cast<char *>(path_fs); + + error = mpg123_open(handle, path2); + 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(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 DecoderPlugin 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/plugins/Mpg123DecoderPlugin.hxx b/src/decoder/plugins/Mpg123DecoderPlugin.hxx new file mode 100644 index 000000000..fd089c6a4 --- /dev/null +++ b/src/decoder/plugins/Mpg123DecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderPlugin mpg123_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/OggCodec.cxx b/src/decoder/plugins/OggCodec.cxx new file mode 100644 index 000000000..c7f39586e --- /dev/null +++ b/src/decoder/plugins/OggCodec.cxx @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "../DecoderAPI.hxx" + +#include <string.h> + +enum ogg_codec +ogg_codec_detect(Decoder *decoder, InputStream &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/plugins/OggCodec.hxx b/src/decoder/plugins/OggCodec.hxx new file mode 100644 index 000000000..6643d85ad --- /dev/null +++ b/src/decoder/plugins/OggCodec.hxx @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + +struct Decoder; +struct InputStream; + +enum ogg_codec { + OGG_CODEC_UNKNOWN, + OGG_CODEC_VORBIS, + OGG_CODEC_FLAC, + OGG_CODEC_OPUS, +}; + +enum ogg_codec +ogg_codec_detect(Decoder *decoder, InputStream &is); + +#endif /* _OGG_COMMON_H */ diff --git a/src/decoder/plugins/OggFind.cxx b/src/decoder/plugins/OggFind.cxx new file mode 100644 index 000000000..df4318da2 --- /dev/null +++ b/src/decoder/plugins/OggFind.cxx @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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" +#include "util/Error.hxx" + +#include <stdio.h> + +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; + } +} + +bool +OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is, + InputStream::offset_type offset, int whence) +{ + oy.Reset(); + + /* reset the stream to clear any previous partial packet + data */ + ogg_stream_reset(&os); + + return is.LockSeek(offset, whence, IgnoreError()) && + oy.ExpectPageSeekIn(os); +} + +bool +OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet, + InputStream &is) +{ + if (is.size > 0 && is.size - is.offset < 65536) + return OggFindEOS(oy, os, packet); + + if (!is.CheapSeeking()) + return false; + + return OggSeekPageAtOffset(oy, os, is, -65536, SEEK_END) && + OggFindEOS(oy, os, packet); +} diff --git a/src/decoder/plugins/OggFind.hxx b/src/decoder/plugins/OggFind.hxx new file mode 100644 index 000000000..d93b87505 --- /dev/null +++ b/src/decoder/plugins/OggFind.hxx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "input/InputStream.hxx" + +#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); + +/** + * Seek the #InputStream and find the next Ogg page. + */ +bool +OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is, + InputStream::offset_type offset, int whence); + +/** + * Try to find the end-of-stream (EOS) packet. Seek to the end of the + * file if necessary. + * + * @return true if the EOS packet was found + */ +bool +OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet, + InputStream &is); + +#endif diff --git a/src/decoder/plugins/OggSyncState.hxx b/src/decoder/plugins/OggSyncState.hxx new file mode 100644 index 000000000..024902fff --- /dev/null +++ b/src/decoder/plugins/OggSyncState.hxx @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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; + + InputStream &is; + Decoder *const decoder; + +public: + OggSyncState(InputStream &_is, 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/plugins/OggUtil.cxx b/src/decoder/plugins/OggUtil.cxx new file mode 100644 index 000000000..3d8a26f0b --- /dev/null +++ b/src/decoder/plugins/OggUtil.cxx @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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, Decoder *decoder, + InputStream &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, InputStream &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, InputStream &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, InputStream &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, InputStream &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, InputStream &is) +{ + ogg_page page; + if (!OggExpectPageSeek(oy, page, decoder, is)) + return false; + + ogg_stream_pagein(&os, &page); + return true; +} diff --git a/src/decoder/plugins/OggUtil.hxx b/src/decoder/plugins/OggUtil.hxx new file mode 100644 index 000000000..3289702a3 --- /dev/null +++ b/src/decoder/plugins/OggUtil.hxx @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 InputStream; +struct Decoder; + +/** + * Feed data from the #InputStream into the #ogg_sync_state. + * + * @return false on error or end-of-file + */ +bool +OggFeed(ogg_sync_state &oy, Decoder *decoder, InputStream &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, InputStream &is); + +/** + * 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, InputStream &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, InputStream &is); + +/** + * Like OggExpectPage(), but allow skipping garbage (after seeking). + */ +bool +OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page, + Decoder *decoder, InputStream &is); + +/** + * 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, InputStream &is); + +#endif diff --git a/src/decoder/plugins/OpusDecoderPlugin.cxx b/src/decoder/plugins/OpusDecoderPlugin.cxx new file mode 100644 index 000000000..d480d1261 --- /dev/null +++ b/src/decoder/plugins/OpusDecoderPlugin.cxx @@ -0,0 +1,485 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with 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 "OggFind.hxx" +#include "OggSyncState.hxx" +#include "../DecoderAPI.hxx" +#include "OggCodec.hxx" +#include "tag/TagHandler.hxx" +#include "tag/TagBuilder.hxx" +#include "input/InputStream.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <opus.h> +#include <ogg/ogg.h> + +#include <glib.h> + +#include <string.h> + +static constexpr 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 { + Decoder &decoder; + InputStream &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; + + ogg_int64_t eos_granulepos; + + size_t frame_size; + +public: + MPDOpusDecoder(Decoder &_decoder, + InputStream &_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); + + bool Seek(OggSyncState &oy, double where); +}; + +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); +} + +/** + * Load the end-of-stream packet and restore the previous file + * position. + */ +static bool +LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno, + ogg_packet &packet) +{ + if (!is.CheapSeeking()) + /* we do this for local files only, because seeking + around remote files is expensive and not worth the + troubl */ + return -1; + + const auto old_offset = is.offset; + if (old_offset < 0) + return -1; + + /* create temporary Ogg objects for seeking and parsing the + EOS packet */ + OggSyncState oy(is, decoder); + ogg_stream_state os; + ogg_stream_init(&os, serialno); + + bool result = OggSeekFindEOS(oy, os, packet, is); + ogg_stream_clear(&os); + + /* restore the previous file position */ + is.Seek(old_offset, SEEK_SET, IgnoreError()); + + return result; +} + +/** + * Load the end-of-stream granulepos and restore the previous file + * position. + * + * @return -1 on error + */ +gcc_pure +static ogg_int64_t +LoadEOSGranulePos(InputStream &is, Decoder *decoder, int serialno) +{ + ogg_packet packet; + if (!LoadEOSPacket(is, decoder, serialno, packet)) + return -1; + + return packet.granulepos; +} + +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; + } + + eos_granulepos = LoadEOSGranulePos(input_stream, &decoder, + opus_serialno); + const double duration = eos_granulepos >= 0 + ? double(eos_granulepos) / opus_sample_rate + : -1.0; + + const AudioFormat audio_format(opus_sample_rate, + SampleFormat::S16, channels); + decoder_initialized(decoder, audio_format, + eos_granulepos > 0, duration); + 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) +{ + ReplayGainInfo rgi; + rgi.Clear(); + + TagBuilder tag_builder; + + DecoderCommand cmd; + if (ScanOpusTags(packet.packet, packet.bytes, + &rgi, + &add_tag_handler, &tag_builder) && + !tag_builder.IsEmpty()) { + decoder_replay_gain(decoder, &rgi); + + Tag tag = tag_builder.Commit(); + 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; + + if (packet.granulepos > 0) + decoder_timestamp(decoder, + double(packet.granulepos) + / opus_sample_rate); + } + + return DecoderCommand::NONE; +} + +bool +MPDOpusDecoder::Seek(OggSyncState &oy, double where_s) +{ + assert(eos_granulepos > 0); + assert(input_stream.seekable); + assert(input_stream.size > 0); + assert(input_stream.offset >= 0); + + const ogg_int64_t where_granulepos(where_s * opus_sample_rate); + + /* interpolate the file offset where we expect to find the + given granule position */ + /* TODO: implement binary search */ + InputStream::offset_type offset(where_granulepos * input_stream.size + / eos_granulepos); + + if (!OggSeekPageAtOffset(oy, os, input_stream, offset, SEEK_SET)) + return false; + + decoder_timestamp(decoder, where_s); + return true; +} + +static void +mpd_opus_stream_decode(Decoder &decoder, + InputStream &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.LockRewind(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::SEEK) { + if (d.Seek(oy, decoder_seek_where(decoder))) + decoder_command_finished(decoder); + else + decoder_seek_error(decoder); + + continue; + } + + if (cmd != DecoderCommand::NONE) + break; + + if (!d.ReadNextPage(oy)) + break; + } +} + +static bool +mpd_opus_scan_stream(InputStream &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, + nullptr, + handler, handler_ctx)) + result = false; + + break; + } + } + + if (packet.e_o_s || OggSeekFindEOS(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 DecoderPlugin 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/plugins/OpusDecoderPlugin.h b/src/decoder/plugins/OpusDecoderPlugin.h new file mode 100644 index 000000000..260dab99a --- /dev/null +++ b/src/decoder/plugins/OpusDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderPlugin opus_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/OpusDomain.cxx b/src/decoder/plugins/OpusDomain.cxx new file mode 100644 index 000000000..1efd64a48 --- /dev/null +++ b/src/decoder/plugins/OpusDomain.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/plugins/OpusDomain.hxx b/src/decoder/plugins/OpusDomain.hxx new file mode 100644 index 000000000..fb19e0301 --- /dev/null +++ b/src/decoder/plugins/OpusDomain.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/OpusHead.cxx b/src/decoder/plugins/OpusHead.cxx new file mode 100644 index 000000000..bfa41d618 --- /dev/null +++ b/src/decoder/plugins/OpusHead.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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> + +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/plugins/OpusHead.hxx b/src/decoder/plugins/OpusHead.hxx new file mode 100644 index 000000000..c478d8d90 --- /dev/null +++ b/src/decoder/plugins/OpusHead.hxx @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/OpusReader.hxx b/src/decoder/plugins/OpusReader.hxx new file mode 100644 index 000000000..c5b8e9107 --- /dev/null +++ b/src/decoder/plugins/OpusReader.hxx @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 <algorithm> + +#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]; + *std::copy_n(src, length, dest) = 0; + return dest; + } +}; + +#endif diff --git a/src/decoder/plugins/OpusTags.cxx b/src/decoder/plugins/OpusTags.cxx new file mode 100644 index 000000000..aff5479c0 --- /dev/null +++ b/src/decoder/plugins/OpusTags.cxx @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "tag/Tag.hxx" +#include "ReplayGainInfo.hxx" + +#include <stdint.h> +#include <string.h> +#include <stdlib.h> + +gcc_pure +static TagType +ParseOpusTagName(const char *name) +{ + TagType type = tag_name_parse_i(name); + if (type != TAG_NUM_OF_ITEM_TYPES) + return type; + + return tag_table_lookup_i(xiph_tags, name); +} + +static void +ScanOneOpusTag(const char *name, const char *value, + ReplayGainInfo *rgi, + const struct tag_handler *handler, void *ctx) +{ + if (rgi != nullptr && strcmp(name, "R128_TRACK_GAIN") == 0) { + /* R128_TRACK_GAIN is a Q7.8 fixed point number in + dB */ + + char *endptr; + long l = strtol(value, &endptr, 10); + if (endptr > value && *endptr == 0) + rgi->tuples[REPLAY_GAIN_TRACK].gain = double(l) / 256.; + } + + tag_handler_invoke_pair(handler, ctx, name, value); + + if (handler->tag != nullptr) { + TagType t = ParseOpusTagName(name); + if (t != TAG_NUM_OF_ITEM_TYPES) + tag_handler_invoke_tag(handler, ctx, t, value); + } +} + +bool +ScanOpusTags(const void *data, size_t size, + ReplayGainInfo *rgi, + 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, rgi, handler, ctx); + } + + delete[] p; + } + + return true; +} diff --git a/src/decoder/plugins/OpusTags.hxx b/src/decoder/plugins/OpusTags.hxx new file mode 100644 index 000000000..be3ac3a8d --- /dev/null +++ b/src/decoder/plugins/OpusTags.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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> + +struct ReplayGainInfo; + +bool +ScanOpusTags(const void *data, size_t size, + ReplayGainInfo *rgi, + const struct tag_handler *handler, void *ctx); + +#endif diff --git a/src/decoder/plugins/PcmDecoderPlugin.cxx b/src/decoder/plugins/PcmDecoderPlugin.cxx new file mode 100644 index 000000000..e7e593550 --- /dev/null +++ b/src/decoder/plugins/PcmDecoderPlugin.cxx @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PcmDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "util/Error.hxx" +#include "util/ByteReverse.hxx" +#include "Log.hxx" + +#include <string.h> +#include <stdio.h> /* for SEEK_SET */ + +static void +pcm_stream_decode(Decoder &decoder, InputStream &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 auto 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) { + InputStream::offset_type offset(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 DecoderPlugin pcm_decoder_plugin = { + "pcm", + nullptr, + nullptr, + pcm_stream_decode, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + pcm_mime_types, +}; diff --git a/src/decoder/plugins/PcmDecoderPlugin.hxx b/src/decoder/plugins/PcmDecoderPlugin.hxx new file mode 100644 index 000000000..3582e5856 --- /dev/null +++ b/src/decoder/plugins/PcmDecoderPlugin.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 DecoderPlugin pcm_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/SidplayDecoderPlugin.cxx b/src/decoder/plugins/SidplayDecoderPlugin.cxx new file mode 100644 index 000000000..8b3e3f8c0 --- /dev/null +++ b/src/decoder/plugins/SidplayDecoderPlugin.cxx @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SidplayDecoderPlugin.hxx" +#include "../DecoderAPI.hxx" +#include "tag/TagHandler.hxx" +#include "util/Alloc.hxx" +#include "util/Domain.hxx" +#include "system/ByteOrder.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> + +#define SUBTUNE_PREFIX "tune_" + +static constexpr Domain sidplay_domain("sidplay"); + +static GPatternSpec *path_with_subtune; +static const char *songlength_file; +static GKeyFile *songlength_database; + +static bool all_files_are_containers; +static unsigned default_songlength; + +static bool filter_setting; + +static GKeyFile * +sidplay_load_songlength_db(const char *path) +{ + GError *error = nullptr; + gchar *data; + gsize size; + + if (!g_file_get_contents(path, &data, &size, &error)) { + FormatError(sidplay_domain, + "unable to read songlengths file %s: %s", + path, error->message); + g_error_free(error); + return nullptr; + } + + /* replace any ; comment characters with # */ + for (gsize i = 0; i < size; i++) + if (data[i] == ';') + data[i] = '#'; + + GKeyFile *db = g_key_file_new(); + bool success = g_key_file_load_from_data(db, data, size, + G_KEY_FILE_NONE, &error); + g_free(data); + if (!success) { + FormatError(sidplay_domain, + "unable to parse songlengths file %s: %s", + path, error->message); + g_error_free(error); + g_key_file_free(db); + return nullptr; + } + + g_key_file_set_list_separator(db, ' '); + return db; +} + +static bool +sidplay_init(const config_param ¶m) +{ + /* read the songlengths database file */ + songlength_file = param.GetBlockValue("songlength_database"); + if (songlength_file != nullptr) + songlength_database = sidplay_load_songlength_db(songlength_file); + + default_songlength = param.GetBlockValue("default_songlength", 0u); + + all_files_are_containers = + param.GetBlockValue("all_files_are_containers", true); + + path_with_subtune=g_pattern_spec_new( + "*/" SUBTUNE_PREFIX "???.sid"); + + filter_setting = param.GetBlockValue("filter", true); + + return true; +} + +static void +sidplay_finish() +{ + g_pattern_spec_free(path_with_subtune); + + if(songlength_database) + g_key_file_free(songlength_database); +} + +/** + * returns the file path stripped of any /tune_xxx.sid subtune + * suffix + */ +static char * +get_container_name(const char *path_fs) +{ + char *path_container = strdup(path_fs); + + if(!g_pattern_match(path_with_subtune, + strlen(path_container), path_container, nullptr)) + return path_container; + + char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX); + if(ptr) *ptr='\0'; + + return path_container; +} + +/** + * returns tune number from file.sid/tune_xxx.sid style path or 1 if + * no subtune is appended + */ +static unsigned +get_song_num(const char *path_fs) +{ + if(g_pattern_match(path_with_subtune, + strlen(path_fs), path_fs, nullptr)) { + char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX); + if(!sub) return 1; + + sub+=strlen("/" SUBTUNE_PREFIX); + int song_num=strtol(sub, nullptr, 10); + + if (errno == EINVAL) + return 1; + else + return song_num; + } else + return 1; +} + +/* get the song length in seconds */ +static int +get_song_length(const char *path_fs) +{ + if (songlength_database == nullptr) + return -1; + + char *sid_file = get_container_name(path_fs); + SidTuneMod tune(sid_file); + free(sid_file); + if(!tune) { + LogWarning(sidplay_domain, + "failed to load file for calculating md5 sum"); + return -1; + } + char md5sum[SIDTUNE_MD5_LENGTH+1]; + tune.createMD5(md5sum); + + const unsigned song_num = get_song_num(path_fs); + + gsize num_items; + gchar **values=g_key_file_get_string_list(songlength_database, + "Database", md5sum, &num_items, nullptr); + if(!values || song_num>num_items) { + g_strfreev(values); + return -1; + } + + int minutes=strtol(values[song_num-1], nullptr, 10); + if(errno==EINVAL) minutes=0; + + int seconds; + char *ptr=strchr(values[song_num-1], ':'); + if(ptr) { + seconds=strtol(ptr+1, nullptr, 10); + if(errno==EINVAL) seconds=0; + } else + seconds=0; + + g_strfreev(values); + + return (minutes*60)+seconds; +} + +static void +sidplay_file_decode(Decoder &decoder, const char *path_fs) +{ + int channels; + + /* load the tune */ + + char *path_container=get_container_name(path_fs); + SidTune tune(path_container, nullptr, true); + free(path_container); + if (!tune) { + LogWarning(sidplay_domain, "failed to load file"); + return; + } + + int song_num=get_song_num(path_fs); + tune.selectSong(song_num); + + int song_len=get_song_length(path_fs); + if(song_len==-1) song_len=default_songlength; + + /* initialize the player */ + + sidplay2 player; + int iret = player.load(&tune); + if (iret != 0) { + FormatWarning(sidplay_domain, + "sidplay2.load() failed: %s", player.error()); + return; + } + + /* initialize the builder */ + + ReSIDBuilder builder("ReSID"); + if (!builder) { + LogWarning(sidplay_domain, + "failed to initialize ReSIDBuilder"); + return; + } + + builder.create(player.info().maxsids); + if (!builder) { + LogWarning(sidplay_domain, "ReSIDBuilder.create() failed"); + return; + } + + builder.filter(filter_setting); + if (!builder) { + LogWarning(sidplay_domain, "ReSIDBuilder.filter() failed"); + return; + } + + /* configure the player */ + + sid2_config_t config = player.config(); + + config.clockDefault = SID2_CLOCK_PAL; + config.clockForced = true; + config.clockSpeed = SID2_CLOCK_CORRECT; + config.frequency = 48000; + config.optimisation = SID2_DEFAULT_OPTIMISATION; + + config.precision = 16; + config.sidDefault = SID2_MOS6581; + config.sidEmulation = &builder; + config.sidModel = SID2_MODEL_CORRECT; + config.sidSamples = true; + config.sampleFormat = IsLittleEndian() + ? SID2_LITTLE_SIGNED + : SID2_BIG_SIGNED; + if (tune.isStereo()) { + config.playback = sid2_stereo; + channels = 2; + } else { + config.playback = sid2_mono; + channels = 1; + } + + iret = player.config(config); + if (iret != 0) { + FormatWarning(sidplay_domain, + "sidplay2.config() failed: %s", player.error()); + return; + } + + /* initialize the MPD decoder */ + + const AudioFormat audio_format(48000, SampleFormat::S16, channels); + assert(audio_format.IsValid()); + + decoder_initialized(decoder, audio_format, true, (float)song_len); + + /* .. and play */ + + const unsigned timebase = player.timebase(); + song_len *= timebase; + + DecoderCommand cmd; + do { + char buffer[4096]; + size_t nbytes; + + nbytes = player.play(buffer, sizeof(buffer)); + if (nbytes == 0) + break; + + decoder_timestamp(decoder, (double)player.time() / timebase); + + cmd = decoder_data(decoder, nullptr, buffer, nbytes, 0); + + if (cmd == DecoderCommand::SEEK) { + unsigned data_time = player.time(); + unsigned target_time = (unsigned) + (decoder_seek_where(decoder) * timebase); + + /* can't rewind so return to zero and seek forward */ + if(target_time<data_time) { + player.stop(); + data_time=0; + } + + /* ignore data until target time is reached */ + while(data_time<target_time) { + nbytes=player.play(buffer, sizeof(buffer)); + if(nbytes==0) + break; + data_time = player.time(); + } + + decoder_command_finished(decoder); + } + + if (song_len > 0 && player.time() >= (unsigned)song_len) + break; + + } while (cmd != DecoderCommand::STOP); +} + +static bool +sidplay_scan_file(const char *path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + int song_num=get_song_num(path_fs); + char *path_container=get_container_name(path_fs); + + SidTune tune(path_container, nullptr, true); + free(path_container); + if (!tune) + return false; + + const SidTuneInfo &info = tune.getInfo(); + + /* title */ + const char *title; + if (info.numberOfInfoStrings > 0 && info.infoString[0] != nullptr) + title=info.infoString[0]; + else + title=""; + + if(info.songs>1) { + char tag_title[1024]; + snprintf(tag_title, sizeof(tag_title), + "%s (%d/%d)", + title, song_num, info.songs); + tag_handler_invoke_tag(handler, handler_ctx, + TAG_TITLE, tag_title); + } else + tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, title); + + /* artist */ + if (info.numberOfInfoStrings > 1 && info.infoString[1] != nullptr) + tag_handler_invoke_tag(handler, handler_ctx, TAG_ARTIST, + info.infoString[1]); + + /* track */ + char track[16]; + sprintf(track, "%d", song_num); + tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track); + + /* time */ + int song_len=get_song_length(path_fs); + if (song_len >= 0) + tag_handler_invoke_duration(handler, handler_ctx, song_len); + + return true; +} + +static char * +sidplay_container_scan(const char *path_fs, const unsigned int tnum) +{ + SidTune tune(path_fs, nullptr, true); + if (!tune) + return nullptr; + + const SidTuneInfo &info=tune.getInfo(); + + /* Don't treat sids containing a single tune + as containers */ + if(!all_files_are_containers && info.songs<2) + return nullptr; + + /* Construct container/tune path names, eg. + Delta.sid/tune_001.sid */ + if(tnum<=info.songs) { + char *subtune= g_strdup_printf( + SUBTUNE_PREFIX "%03u.sid", tnum); + return subtune; + } else + return nullptr; +} + +static const char *const sidplay_suffixes[] = { + "sid", + "mus", + "str", + "prg", + "P00", + nullptr +}; + +extern const struct DecoderPlugin sidplay_decoder_plugin; +const struct DecoderPlugin sidplay_decoder_plugin = { + "sidplay", + sidplay_init, + sidplay_finish, + nullptr, /* stream_decode() */ + sidplay_file_decode, + sidplay_scan_file, + nullptr, /* stream_tag() */ + sidplay_container_scan, + sidplay_suffixes, + nullptr, /* mime_types */ +}; diff --git a/src/decoder/plugins/SidplayDecoderPlugin.hxx b/src/decoder/plugins/SidplayDecoderPlugin.hxx new file mode 100644 index 000000000..58786e646 --- /dev/null +++ b/src/decoder/plugins/SidplayDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_SIDPLAY_HXX +#define MPD_DECODER_SIDPLAY_HXX + +extern const struct DecoderPlugin sidplay_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/SndfileDecoderPlugin.cxx b/src/decoder/plugins/SndfileDecoderPlugin.cxx new file mode 100644 index 000000000..886c56748 --- /dev/null +++ b/src/decoder/plugins/SndfileDecoderPlugin.cxx @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "input/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 InputStream &is = *(const InputStream *)user_data; + + return is.GetSize(); +} + +static sf_count_t +sndfile_vio_seek(sf_count_t offset, int whence, void *user_data) +{ + InputStream &is = *(InputStream *)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) +{ + InputStream &is = *(InputStream *)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 InputStream &is = *(const InputStream *)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(Decoder &decoder, InputStream &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 DecoderPlugin 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/plugins/SndfileDecoderPlugin.hxx b/src/decoder/plugins/SndfileDecoderPlugin.hxx new file mode 100644 index 000000000..d56acdd5a --- /dev/null +++ b/src/decoder/plugins/SndfileDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderPlugin sndfile_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/VorbisComments.cxx b/src/decoder/plugins/VorbisComments.cxx new file mode 100644 index 000000000..2a0820ab5 --- /dev/null +++ b/src/decoder/plugins/VorbisComments.cxx @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/TagTable.hxx" +#include "tag/TagHandler.hxx" +#include "tag/TagBuilder.hxx" +#include "ReplayGainInfo.hxx" +#include "util/ASCII.hxx" +#include "util/SplitString.hxx" + +#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 (StringEqualsCaseASCII(comment, needle, len) && + comment[len] == '=') + return comment + len + 1; + + return nullptr; +} + +bool +vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments) +{ + rgi.Clear(); + + const char *temp; + bool found = false; + + 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, TagType tag_type, + const struct tag_handler *handler, void *handler_ctx) +{ + const char *value; + + value = vorbis_comment_value(comment, name); + if (value != nullptr) { + 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 != nullptr) { + const SplitString split(comment, '='); + if (split.IsDefined() && !split.IsEmpty()) + tag_handler_invoke_pair(handler, handler_ctx, + split.GetFirst(), + split.GetSecond()); + } + + for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++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], TagType(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.CommitNew(); +} diff --git a/src/decoder/plugins/VorbisComments.hxx b/src/decoder/plugins/VorbisComments.hxx new file mode 100644 index 000000000..893c89277 --- /dev/null +++ b/src/decoder/plugins/VorbisComments.hxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 ReplayGainInfo; +struct tag_handler; +struct Tag; + +bool +vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments); + +void +vorbis_comments_scan(char **comments, + const tag_handler *handler, void *handler_ctx); + +Tag * +vorbis_comments_to_tag(char **comments); + +#endif diff --git a/src/decoder/plugins/VorbisDecoderPlugin.cxx b/src/decoder/plugins/VorbisDecoderPlugin.cxx new file mode 100644 index 000000000..fd110f457 --- /dev/null +++ b/src/decoder/plugins/VorbisDecoderPlugin.cxx @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "input/InputStream.hxx" +#include "OggCodec.hxx" +#include "util/Error.hxx" +#include "util/Macros.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 <errno.h> + +struct vorbis_input_stream { + Decoder *decoder; + + InputStream *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 == nullptr || + 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, + Decoder *decoder, InputStream &input_stream) +{ + vis->decoder = decoder; + vis->input_stream = &input_stream; + vis->seekable = input_stream.CheapSeeking(); + + int ret = ov_open_callbacks(vis, vf, nullptr, 0, vorbis_is_callbacks); + if (ret < 0) { + if (decoder == nullptr || + 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(Decoder &decoder, InputStream &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(Decoder &decoder, + InputStream &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.LockRewind(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 == nullptr) { + 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 = + ARRAY_SIZE(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), + IsBigEndian(), 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 == nullptr) { + 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); + + ReplayGainInfo 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(InputStream &is, + const struct tag_handler *handler, void *handler_ctx) +{ + struct vorbis_input_stream vis; + OggVorbis_File vf; + + if (!vorbis_is_open(&vis, &vf, nullptr, 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", nullptr +}; + +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", + nullptr +}; + +const struct DecoderPlugin 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/plugins/VorbisDecoderPlugin.h b/src/decoder/plugins/VorbisDecoderPlugin.h new file mode 100644 index 000000000..b54df2e97 --- /dev/null +++ b/src/decoder/plugins/VorbisDecoderPlugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderPlugin vorbis_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/VorbisDomain.cxx b/src/decoder/plugins/VorbisDomain.cxx new file mode 100644 index 000000000..e3d880efa --- /dev/null +++ b/src/decoder/plugins/VorbisDomain.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/plugins/VorbisDomain.hxx b/src/decoder/plugins/VorbisDomain.hxx new file mode 100644 index 000000000..48715e328 --- /dev/null +++ b/src/decoder/plugins/VorbisDomain.hxx @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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" + +class Domain; + +extern const Domain vorbis_domain; + +#endif diff --git a/src/decoder/plugins/WavpackDecoderPlugin.cxx b/src/decoder/plugins/WavpackDecoderPlugin.cxx new file mode 100644 index 000000000..e65d374d2 --- /dev/null +++ b/src/decoder/plugins/WavpackDecoderPlugin.cxx @@ -0,0 +1,571 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "input/InputStream.hxx" +#include "CheckAudioFormat.hxx" +#include "tag/TagHandler.hxx" +#include "tag/ApeTag.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "util/Macros.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(Decoder &decoder, WavpackContext *wpc, bool can_seek) +{ + bool is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0; + SampleFormat sample_format = + wavpack_bits_to_sample_format(is_float, + WavpackGetBytesPerSample(wpc)); + + Error error; + AudioFormat audio_format; + if (!audio_format_init_checked(audio_format, + WavpackGetSampleRate(wpc), + sample_format, + WavpackGetNumChannels(wpc), error)) { + LogError(error); + return; + } + + const format_samples_t format_samples = is_float + ? format_samples_float + : format_samples_int; + + const float total_time = float(WavpackGetNumSamples(wpc)) + / audio_format.sample_rate; + + const int bytes_per_sample = WavpackGetBytesPerSample(wpc); + const int 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 = ARRAY_SIZE(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, nullptr, 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]; + if (WavpackGetTagItem(wpc, key, buffer, sizeof(buffer)) <= 0) + return false; + + *value_r = atof(buffer); + return true; +} + +static bool +wavpack_replaygain(ReplayGainInfo &rgi, + WavpackContext *wpc) +{ + rgi.Clear(); + + bool found = false; + found |= wavpack_tag_float(wpc, "replaygain_track_gain", + &rgi.tuples[REPLAY_GAIN_TRACK].gain); + found |= wavpack_tag_float(wpc, "replaygain_track_peak", + &rgi.tuples[REPLAY_GAIN_TRACK].peak); + found |= wavpack_tag_float(wpc, "replaygain_album_gain", + &rgi.tuples[REPLAY_GAIN_ALBUM].gain); + found |= wavpack_tag_float(wpc, "replaygain_album_peak", + &rgi.tuples[REPLAY_GAIN_ALBUM].peak); + + return found; +} + +static void +wavpack_scan_tag_item(WavpackContext *wpc, const char *name, + TagType 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) +{ + char error[ERRORLEN]; + WavpackContext *wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0); + if (wpc == nullptr) { + 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 != nullptr) + wavpack_scan_tag_item(wpc, name, (TagType)i, + handler, handler_ctx); + } + + for (const struct tag_table *i = ape_tags; i->name != nullptr; ++i) + wavpack_scan_tag_item(wpc, i->name, i->type, + handler, handler_ctx); + + if (handler->pair != nullptr) { + 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 { + Decoder *decoder; + InputStream *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, Decoder &decoder, + InputStream &is) +{ + isp->decoder = &decoder; + isp->is = &is; + isp->last_byte = EOF; +} + +static InputStream * +wavpack_open_wvc(Decoder &decoder, const char *uri, + Mutex &mutex, Cond &cond, + struct wavpack_input *wpi) +{ + /* + * As we use dc->utf8url, this function will be bad for + * single files. utf8url is not absolute file path :/ + */ + if (uri == nullptr) + return nullptr; + + char *wvc_url = g_strconcat(uri, "c", nullptr); + + InputStream *is_wvc = InputStream::Open(wvc_url, mutex, cond, + IgnoreError()); + g_free(wvc_url); + + if (is_wvc == nullptr) + return nullptr; + + /* + * And we try to buffer in order to get know + * about a possible 404 error. + */ + char first_byte; + size_t nbytes = decoder_read(decoder, *is_wvc, + &first_byte, sizeof(first_byte)); + if (nbytes == 0) { + is_wvc->Close(); + return nullptr; + } + + /* 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(Decoder &decoder, InputStream &is) +{ + int open_flags = OPEN_NORMALIZE; + bool can_seek = is.seekable; + + wavpack_input isp_wvc; + InputStream *is_wvc = wavpack_open_wvc(decoder, is.uri.c_str(), + is.mutex, is.cond, + &isp_wvc); + if (is_wvc != nullptr) { + open_flags |= OPEN_WVC; + can_seek &= is_wvc->seekable; + } + + if (!can_seek) { + open_flags |= OPEN_STREAMING; + } + + wavpack_input isp; + wavpack_input_init(&isp, decoder, is); + + char error[ERRORLEN]; + WavpackContext *wpc = + WavpackOpenFileInputEx(&mpd_is_reader, &isp, + open_flags & OPEN_WVC + ? &isp_wvc : nullptr, + error, open_flags, 23); + + if (wpc == nullptr) { + 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(Decoder &decoder, const char *fname) +{ + char error[ERRORLEN]; + WavpackContext *wpc = WavpackOpenFileInput(fname, error, + OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE, + 23); + if (wpc == nullptr) { + FormatWarning(wavpack_domain, + "failed to open WavPack file \"%s\": %s", + fname, error); + return; + } + + ReplayGainInfo rgi; + if (wavpack_replaygain(rgi, wpc)) + decoder_replay_gain(decoder, &rgi); + + wavpack_decode(decoder, wpc, true); + + WavpackCloseFile(wpc); +} + +static char const *const wavpack_suffixes[] = { + "wv", + nullptr +}; + +static char const *const wavpack_mime_types[] = { + "audio/x-wavpack", + nullptr +}; + +const struct DecoderPlugin 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/plugins/WavpackDecoderPlugin.hxx b/src/decoder/plugins/WavpackDecoderPlugin.hxx new file mode 100644 index 000000000..2e5f9bd42 --- /dev/null +++ b/src/decoder/plugins/WavpackDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderPlugin wavpack_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/WildmidiDecoderPlugin.cxx b/src/decoder/plugins/WildmidiDecoderPlugin.cxx new file mode 100644 index 000000000..9a6018022 --- /dev/null +++ b/src/decoder/plugins/WildmidiDecoderPlugin.cxx @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/AllocatedPath.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 AllocatedPath 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(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); + + WildMidi_FastSeek(wm, &seek_where); + 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 DecoderPlugin 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/plugins/WildmidiDecoderPlugin.hxx b/src/decoder/plugins/WildmidiDecoderPlugin.hxx new file mode 100644 index 000000000..fc87aab80 --- /dev/null +++ b/src/decoder/plugins/WildmidiDecoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 DecoderPlugin wildmidi_decoder_plugin; + +#endif diff --git a/src/decoder/plugins/XiphTags.cxx b/src/decoder/plugins/XiphTags.cxx new file mode 100644 index 000000000..a5e534086 --- /dev/null +++ b/src/decoder/plugins/XiphTags.cxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/plugins/XiphTags.hxx b/src/decoder/plugins/XiphTags.hxx new file mode 100644 index 000000000..48a27425f --- /dev/null +++ b/src/decoder/plugins/XiphTags.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/encoder/EncoderAPI.hxx b/src/encoder/EncoderAPI.hxx new file mode 100644 index 000000000..b147eac21 --- /dev/null +++ b/src/encoder/EncoderAPI.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + +// IWYU pragma: begin_exports + +#include "EncoderPlugin.hxx" +#include "AudioFormat.hxx" +#include "tag/Tag.hxx" +#include "config/ConfigData.hxx" + +// IWYU pragma: end_exports + +#endif diff --git a/src/encoder/EncoderList.cxx b/src/encoder/EncoderList.cxx new file mode 100644 index 000000000..4bca5a4fe --- /dev/null +++ b/src/encoder/EncoderList.cxx @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "plugins/NullEncoderPlugin.hxx" +#include "plugins/WaveEncoderPlugin.hxx" +#include "plugins/VorbisEncoderPlugin.hxx" +#include "plugins/OpusEncoderPlugin.hxx" +#include "plugins/FlacEncoderPlugin.hxx" +#include "plugins/ShineEncoderPlugin.hxx" +#include "plugins/LameEncoderPlugin.hxx" +#include "plugins/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 +#ifdef ENABLE_SHINE_ENCODER + &shine_encoder_plugin, +#endif + nullptr +}; + +const EncoderPlugin * +encoder_plugin_get(const char *name) +{ + encoder_plugins_for_each(plugin) + if (strcmp(plugin->name, name) == 0) + return plugin; + + return nullptr; +} diff --git a/src/encoder/EncoderList.hxx b/src/encoder/EncoderList.hxx new file mode 100644 index 000000000..e18d8ec74 --- /dev/null +++ b/src/encoder/EncoderList.hxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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) != nullptr; \ + ++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 nullptr if none + * was found + */ +const EncoderPlugin * +encoder_plugin_get(const char *name); + +#endif diff --git a/src/encoder/EncoderPlugin.hxx b/src/encoder/EncoderPlugin.hxx new file mode 100644 index 000000000..95e4e5838 --- /dev/null +++ b/src/encoder/EncoderPlugin.hxx @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 nullptr to ignore errors. + * @return an encoder object on success, nullptr 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 + * @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 != nullptr) + 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 + * @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 != nullptr + ? 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 + * @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 != nullptr + ? 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 + * @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 != nullptr + ? 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 + * @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 != nullptr + ? 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 + * @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, nullptr on failure + */ +static inline const char * +encoder_get_mime_type(Encoder *encoder) +{ + /* this method is optional */ + return encoder->plugin.get_mime_type != nullptr + ? encoder->plugin.get_mime_type(encoder) + : nullptr; +} + +#endif diff --git a/src/encoder/FlacEncoderPlugin.cxx b/src/encoder/FlacEncoderPlugin.cxx deleted file mode 100644 index fa7ed992d..000000000 --- a/src/encoder/FlacEncoderPlugin.cxx +++ /dev/null @@ -1,342 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 <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 deleted file mode 100644 index 928a7f93e..000000000 --- a/src/encoder/FlacEncoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 06082d16b..000000000 --- a/src/encoder/LameEncoderPlugin.cxx +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/NumberParser.hxx" -#include "util/ReusableArray.hxx" -#include "util/Manual.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" - -#include <lame/lame.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 = ParseDouble(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 = ParseInt(value, &endptr); - - 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 deleted file mode 100644 index 49832baee..000000000 --- a/src/encoder/LameEncoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 3b1aae5e2..000000000 --- a/src/encoder/NullEncoderPlugin.cxx +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "Compiler.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 deleted file mode 100644 index b741a2f6d..000000000 --- a/src/encoder/NullEncoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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/OggSerial.cxx b/src/encoder/OggSerial.cxx deleted file mode 100644 index 0d4fc5a9f..000000000 --- a/src/encoder/OggSerial.cxx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "OggSerial.hxx" -#include "system/Clock.hxx" -#include "Compiler.h" - -#include <atomic> - -static std::atomic_uint next_ogg_serial; - -int -GenerateOggSerial() -{ - unsigned serial = ++next_ogg_serial; - if (gcc_unlikely(serial < 16)) { - /* first-time initialization: seed with a clock value, - which is random enough for our use */ - - /* this code is not race-free, but good enough */ - const unsigned seed = MonotonicClockMS(); - next_ogg_serial = serial = seed; - } - - return serial; -} - diff --git a/src/encoder/OggSerial.hxx b/src/encoder/OggSerial.hxx deleted file mode 100644 index 2e5a020c6..000000000 --- a/src/encoder/OggSerial.hxx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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_SERIAL_HXX -#define MPD_OGG_SERIAL_HXX - -/** - * Generate the next pseudo-random Ogg serial. - */ -int -GenerateOggSerial(); - -#endif diff --git a/src/encoder/OggStream.hxx b/src/encoder/OggStream.hxx deleted file mode 100644 index e8dcdf970..000000000 --- a/src/encoder/OggStream.hxx +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 243dc0836..000000000 --- a/src/encoder/OpusEncoderPlugin.cxx +++ /dev/null @@ -1,420 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "OggSerial.hxx" -#include "EncoderAPI.hxx" -#include "AudioFormat.hxx" -#include "ConfigError.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "system/ByteOrder.hxx" - -#include <opus.h> -#include <ogg/ogg.h> - -#include <glib.h> - -#include <assert.h> -#include <stdlib.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(GenerateOggSerial()); - 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) = ToLE16(encoder->lookahead); - *(uint32_t *)(header + 12) = - ToLE32(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) = ToLE32(version_length); - memcpy(comments + 12, version, version_length); - *(uint32_t *)(comments + 12 + version_length) = ToLE32(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 deleted file mode 100644 index 3bb55e051..000000000 --- a/src/encoder/OpusEncoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 543e71d64..000000000 --- a/src/encoder/TwolameEncoderPlugin.cxx +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/NumberParser.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <twolame.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 = ParseDouble(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 = ParseInt(value, &endptr); - - 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 deleted file mode 100644 index dd8a536f6..000000000 --- a/src/encoder/TwolameEncoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 5b40aaea1..000000000 --- a/src/encoder/VorbisEncoderPlugin.cxx +++ /dev/null @@ -1,367 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "OggSerial.hxx" -#include "EncoderAPI.hxx" -#include "tag/Tag.hxx" -#include "AudioFormat.hxx" -#include "ConfigError.hxx" -#include "util/NumberParser.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 = ParseDouble(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 = ParseInt(value, &endptr); - 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(GenerateOggSerial()); - - 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(GenerateOggSerial()); - - /* 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 deleted file mode 100644 index d5d6125d2..000000000 --- a/src/encoder/VorbisEncoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index acae0be9e..000000000 --- a/src/encoder/WaveEncoderPlugin.cxx +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "system/ByteOrder.hxx" -#include "util/fifo_buffer.h" -extern "C" { -#include "util/growing_fifo.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 = ToLE32(0x46464952); - header->id_wave = ToLE32(0x45564157); - header->id_fmt = ToLE32(0x20746d66); - header->id_data = ToLE32(0x61746164); - - /* wave format */ - header->format = ToLE16(1); // PCM_FORMAT - header->channels = ToLE16(channels); - header->bits = ToLE16(bits); - header->freq = ToLE32(freq); - header->blocksize = ToLE16(block_size); - header->byterate = ToLE32(freq * block_size); - - /* chunk sizes (fake data length) */ - header->fmt_size = ToLE32(16); - header->data_size = ToLE32(data_size); - header->riff_size = ToLE32(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; - - delete 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 size_t -pcm16_to_wave(uint16_t *dst16, const uint16_t *src16, size_t length) -{ - size_t cnt = length >> 1; - while (cnt > 0) { - *dst16++ = ToLE16(*src16++); - cnt--; - } - return length; -} - -static size_t -pcm32_to_wave(uint32_t *dst32, const uint32_t *src32, size_t length) -{ - size_t cnt = length >> 2; - while (cnt > 0){ - *dst32++ = ToLE32(*src32++); - cnt--; - } - return length; -} - -static 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 (IsLittleEndian()) { - 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; - } - } else { - switch (encoder->bits) { - case 8: - memcpy(dst, src, length); - break; - case 16: - length = pcm16_to_wave((uint16_t *)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((uint32_t *)dst, - (const uint32_t *)src, length); - break; - } - } - - 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 deleted file mode 100644 index 190ee131e..000000000 --- a/src/encoder/WaveEncoderPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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/plugins/FlacEncoderPlugin.cxx b/src/encoder/plugins/FlacEncoderPlugin.cxx new file mode 100644 index 000000000..26987fe99 --- /dev/null +++ b/src/encoder/plugins/FlacEncoderPlugin.cxx @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "config/ConfigError.hxx" +#include "util/Manual.hxx" +#include "util/DynamicFifoBuffer.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#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(). + */ + Manual<DynamicFifoBuffer<uint8_t>> 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 + encoder->output_buffer->Append((const uint8_t *)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(); + encoder->output_buffer.Destruct(); +} + +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.Construct(8192); + + /* 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; + + return encoder->output_buffer->Read((uint8_t *)dest, 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/plugins/FlacEncoderPlugin.hxx b/src/encoder/plugins/FlacEncoderPlugin.hxx new file mode 100644 index 000000000..0cdc01600 --- /dev/null +++ b/src/encoder/plugins/FlacEncoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/LameEncoderPlugin.cxx b/src/encoder/plugins/LameEncoderPlugin.cxx new file mode 100644 index 000000000..3878b52bb --- /dev/null +++ b/src/encoder/plugins/LameEncoderPlugin.cxx @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "config/ConfigError.hxx" +#include "util/NumberParser.hxx" +#include "util/ReusableArray.hxx" +#include "util/Manual.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <lame/lame.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 = ParseDouble(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 = ParseInt(value, &endptr); + + 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/plugins/LameEncoderPlugin.hxx b/src/encoder/plugins/LameEncoderPlugin.hxx new file mode 100644 index 000000000..03e398f67 --- /dev/null +++ b/src/encoder/plugins/LameEncoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/NullEncoderPlugin.cxx b/src/encoder/plugins/NullEncoderPlugin.cxx new file mode 100644 index 000000000..1d571d465 --- /dev/null +++ b/src/encoder/plugins/NullEncoderPlugin.cxx @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/Manual.hxx" +#include "util/DynamicFifoBuffer.hxx" +#include "Compiler.h" + +#include <assert.h> + +struct NullEncoder final { + Encoder encoder; + + Manual<DynamicFifoBuffer<uint8_t>> 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; + + encoder->buffer.Destruct(); +} + + +static bool +null_encoder_open(Encoder *_encoder, + gcc_unused AudioFormat &audio_format, + gcc_unused Error &error) +{ + NullEncoder *encoder = (NullEncoder *)_encoder; + encoder->buffer.Construct(8192); + return true; +} + +static bool +null_encoder_write(Encoder *_encoder, + const void *data, size_t length, + gcc_unused Error &error) +{ + NullEncoder *encoder = (NullEncoder *)_encoder; + + encoder->buffer->Append((const uint8_t *)data, length); + return length; +} + +static size_t +null_encoder_read(Encoder *_encoder, void *dest, size_t length) +{ + NullEncoder *encoder = (NullEncoder *)_encoder; + + return encoder->buffer->Read((uint8_t *)dest, 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/plugins/NullEncoderPlugin.hxx b/src/encoder/plugins/NullEncoderPlugin.hxx new file mode 100644 index 000000000..6acf88e49 --- /dev/null +++ b/src/encoder/plugins/NullEncoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/OggSerial.cxx b/src/encoder/plugins/OggSerial.cxx new file mode 100644 index 000000000..677829439 --- /dev/null +++ b/src/encoder/plugins/OggSerial.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "OggSerial.hxx" +#include "system/Clock.hxx" +#include "Compiler.h" + +#include <atomic> + +static std::atomic_uint next_ogg_serial; + +int +GenerateOggSerial() +{ + unsigned serial = ++next_ogg_serial; + if (gcc_unlikely(serial < 16)) { + /* first-time initialization: seed with a clock value, + which is random enough for our use */ + + /* this code is not race-free, but good enough */ + const unsigned seed = MonotonicClockMS(); + next_ogg_serial = serial = seed; + } + + return serial; +} + diff --git a/src/encoder/plugins/OggSerial.hxx b/src/encoder/plugins/OggSerial.hxx new file mode 100644 index 000000000..ceba8ebf9 --- /dev/null +++ b/src/encoder/plugins/OggSerial.hxx @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_SERIAL_HXX +#define MPD_OGG_SERIAL_HXX + +/** + * Generate the next pseudo-random Ogg serial. + */ +int +GenerateOggSerial(); + +#endif diff --git a/src/encoder/plugins/OggStream.hxx b/src/encoder/plugins/OggStream.hxx new file mode 100644 index 000000000..805238c1d --- /dev/null +++ b/src/encoder/plugins/OggStream.hxx @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/OpusEncoderPlugin.cxx b/src/encoder/plugins/OpusEncoderPlugin.cxx new file mode 100644 index 000000000..2d3194c26 --- /dev/null +++ b/src/encoder/plugins/OpusEncoderPlugin.cxx @@ -0,0 +1,420 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "OggSerial.hxx" +#include "../EncoderAPI.hxx" +#include "AudioFormat.hxx" +#include "config/ConfigError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "system/ByteOrder.hxx" + +#include <opus.h> +#include <ogg/ogg.h> + +#include <glib.h> + +#include <assert.h> +#include <stdlib.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(GenerateOggSerial()); + 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) = ToLE16(encoder->lookahead); + *(uint32_t *)(header + 12) = + ToLE32(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) = ToLE32(version_length); + memcpy(comments + 12, version, version_length); + *(uint32_t *)(comments + 12 + version_length) = ToLE32(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/plugins/OpusEncoderPlugin.hxx b/src/encoder/plugins/OpusEncoderPlugin.hxx new file mode 100644 index 000000000..4e71694b9 --- /dev/null +++ b/src/encoder/plugins/OpusEncoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/ShineEncoderPlugin.cxx b/src/encoder/plugins/ShineEncoderPlugin.cxx new file mode 100644 index 000000000..00b8eec7c --- /dev/null +++ b/src/encoder/plugins/ShineEncoderPlugin.cxx @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ShineEncoderPlugin.hxx" +#include "config.h" +#include "../EncoderAPI.hxx" +#include "AudioFormat.hxx" +#include "config/ConfigError.hxx" +#include "util/Manual.hxx" +#include "util/NumberParser.hxx" +#include "util/DynamicFifoBuffer.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +extern "C" +{ +#include <shine/layer3.h> +} + +static constexpr size_t BUFFER_INIT_SIZE = 8192; +static constexpr unsigned CHANNELS = 2; + +struct ShineEncoder { + Encoder encoder; + + AudioFormat audio_format; + + shine_t shine; + + shine_config_t config; + + size_t frame_size; + size_t input_pos; + int16_t *stereo[CHANNELS]; + + Manual<DynamicFifoBuffer<uint8_t>> output_buffer; + + ShineEncoder():encoder(shine_encoder_plugin){} + + bool Configure(const config_param ¶m, Error &error); + + bool Setup(Error &error); + + bool WriteChunk(bool flush); +}; + +static constexpr Domain shine_encoder_domain("shine_encoder"); + +inline bool +ShineEncoder::Configure(const config_param ¶m, + gcc_unused Error &error) +{ + shine_set_config_mpeg_defaults(&config.mpeg); + config.mpeg.bitr = param.GetBlockValue("bitrate", 128); + + return true; +} + +static Encoder * +shine_encoder_init(const config_param ¶m, Error &error) +{ + ShineEncoder *encoder = new ShineEncoder(); + + /* 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 +shine_encoder_finish(Encoder *_encoder) +{ + ShineEncoder *encoder = (ShineEncoder *)_encoder; + + delete encoder; +} + +inline bool +ShineEncoder::Setup(Error &error) +{ + config.mpeg.mode = audio_format.channels == 2 ? STEREO : MONO; + config.wave.samplerate = audio_format.sample_rate; + config.wave.channels = + audio_format.channels == 2 ? PCM_STEREO : PCM_MONO; + + if (shine_check_config(config.wave.samplerate, config.mpeg.bitr) < 0) { + error.Format(config_domain, + "error configuring shine. " + "samplerate %d and bitrate %d configuration" + " not supported.", + config.wave.samplerate, + config.mpeg.bitr); + + return false; + } + + shine = shine_initialise(&config); + + if (!shine) { + error.Format(config_domain, + "error initializing shine."); + + return false; + } + + frame_size = shine_samples_per_pass(shine); + + return true; +} + +static bool +shine_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error) +{ + ShineEncoder *encoder = (ShineEncoder *)_encoder; + + audio_format.format = SampleFormat::S16; + audio_format.channels = CHANNELS; + encoder->audio_format = audio_format; + + if (!encoder->Setup(error)) + return false; + + encoder->stereo[0] = new int16_t[encoder->frame_size]; + encoder->stereo[1] = new int16_t[encoder->frame_size]; + /* workaround for bug: + https://github.com/savonet/shine/issues/11 */ + encoder->input_pos = SHINE_MAX_SAMPLES + 1; + + encoder->output_buffer.Construct(BUFFER_INIT_SIZE); + + return true; +} + +static void +shine_encoder_close(Encoder *_encoder) +{ + ShineEncoder *encoder = (ShineEncoder *)_encoder; + + if (encoder->input_pos > SHINE_MAX_SAMPLES) { + /* write zero chunk */ + encoder->input_pos = 0; + encoder->WriteChunk(true); + } + + shine_close(encoder->shine); + delete[] encoder->stereo[0]; + delete[] encoder->stereo[1]; + encoder->output_buffer.Destruct(); +} + +bool +ShineEncoder::WriteChunk(bool flush) +{ + if (flush || input_pos == frame_size) { + long written; + + if (flush) { + /* fill remaining with 0s */ + for (; input_pos < frame_size; input_pos++) { + stereo[0][input_pos] = stereo[1][input_pos] = 0; + } + } + + const uint8_t *out = + shine_encode_buffer(shine, stereo, &written); + + if (written > 0) + output_buffer->Append(out, written); + + input_pos = 0; + } + + return true; +} + +static bool +shine_encoder_write(Encoder *_encoder, + const void *_data, size_t length, + gcc_unused Error &error) +{ + ShineEncoder *encoder = (ShineEncoder *)_encoder; + const int16_t *data = (const int16_t*)_data; + length /= sizeof(*data) * encoder->audio_format.channels; + size_t written = 0; + + if (encoder->input_pos > SHINE_MAX_SAMPLES) { + encoder->input_pos = 0; + } + + /* write all data to de-interleaved buffers */ + while (written < length) { + for (; + written < length + && encoder->input_pos < encoder->frame_size; + written++, encoder->input_pos++) { + const size_t base = + written * encoder->audio_format.channels; + encoder->stereo[0][encoder->input_pos] = data[base]; + encoder->stereo[1][encoder->input_pos] = data[base + 1]; + } + /* write if chunk is filled */ + encoder->WriteChunk(false); + } + + return true; +} + +static bool +shine_encoder_flush(Encoder *_encoder, gcc_unused Error &error) +{ + ShineEncoder *encoder = (ShineEncoder *)_encoder; + long written; + + /* flush buffers and flush shine */ + encoder->WriteChunk(true); + const uint8_t *data = shine_flush(encoder->shine, &written); + + if (written > 0) + encoder->output_buffer->Append(data, written); + + return true; +} + +static size_t +shine_encoder_read(Encoder *_encoder, void *dest, size_t length) +{ + ShineEncoder *encoder = (ShineEncoder *)_encoder; + + return encoder->output_buffer->Read((uint8_t *)dest, length); +} + +static const char * +shine_encoder_get_mime_type(gcc_unused Encoder *_encoder) +{ + return "audio/mpeg"; +} + +const EncoderPlugin shine_encoder_plugin = { + "shine", + shine_encoder_init, + shine_encoder_finish, + shine_encoder_open, + shine_encoder_close, + shine_encoder_flush, + shine_encoder_flush, + nullptr, + nullptr, + shine_encoder_write, + shine_encoder_read, + shine_encoder_get_mime_type, +}; diff --git a/src/encoder/plugins/ShineEncoderPlugin.hxx b/src/encoder/plugins/ShineEncoderPlugin.hxx new file mode 100644 index 000000000..8b1520a74 --- /dev/null +++ b/src/encoder/plugins/ShineEncoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_SHINE_HXX +#define MPD_ENCODER_SHINE_HXX + +extern const struct EncoderPlugin shine_encoder_plugin; + +#endif diff --git a/src/encoder/plugins/TwolameEncoderPlugin.cxx b/src/encoder/plugins/TwolameEncoderPlugin.cxx new file mode 100644 index 000000000..2eb6b2b1c --- /dev/null +++ b/src/encoder/plugins/TwolameEncoderPlugin.cxx @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "config/ConfigError.hxx" +#include "util/NumberParser.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <twolame.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 = ParseDouble(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 = ParseInt(value, &endptr); + + 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/plugins/TwolameEncoderPlugin.hxx b/src/encoder/plugins/TwolameEncoderPlugin.hxx new file mode 100644 index 000000000..531dd3e90 --- /dev/null +++ b/src/encoder/plugins/TwolameEncoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/VorbisEncoderPlugin.cxx b/src/encoder/plugins/VorbisEncoderPlugin.cxx new file mode 100644 index 000000000..7fdb3066f --- /dev/null +++ b/src/encoder/plugins/VorbisEncoderPlugin.cxx @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "OggSerial.hxx" +#include "../EncoderAPI.hxx" +#include "tag/Tag.hxx" +#include "AudioFormat.hxx" +#include "config/ConfigError.hxx" +#include "util/NumberParser.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <vorbis/vorbisenc.h> + +#include <glib.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 = ParseDouble(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 = ParseInt(value, &endptr); + 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(GenerateOggSerial()); + + 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(GenerateOggSerial()); + + /* 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/plugins/VorbisEncoderPlugin.hxx b/src/encoder/plugins/VorbisEncoderPlugin.hxx new file mode 100644 index 000000000..80703bf88 --- /dev/null +++ b/src/encoder/plugins/VorbisEncoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/WaveEncoderPlugin.cxx b/src/encoder/plugins/WaveEncoderPlugin.cxx new file mode 100644 index 000000000..97a26e821 --- /dev/null +++ b/src/encoder/plugins/WaveEncoderPlugin.cxx @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "system/ByteOrder.hxx" +#include "util/Manual.hxx" +#include "util/DynamicFifoBuffer.hxx" + +#include <assert.h> +#include <string.h> + +struct WaveEncoder { + Encoder encoder; + unsigned bits; + + Manual<DynamicFifoBuffer<uint8_t>> 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 = ToLE32(0x46464952); + header->id_wave = ToLE32(0x45564157); + header->id_fmt = ToLE32(0x20746d66); + header->id_data = ToLE32(0x61746164); + + /* wave format */ + header->format = ToLE16(1); // PCM_FORMAT + header->channels = ToLE16(channels); + header->bits = ToLE16(bits); + header->freq = ToLE32(freq); + header->blocksize = ToLE16(block_size); + header->byterate = ToLE32(freq * block_size); + + /* chunk sizes (fake data length) */ + header->fmt_size = ToLE32(16); + header->data_size = ToLE32(data_size); + header->riff_size = ToLE32(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; + + delete 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.Construct(8192); + + auto range = encoder->buffer->Write(); + assert(range.size >= sizeof(wave_header)); + wave_header *header = (wave_header *)range.data; + + /* 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); + + encoder->buffer->Append(sizeof(*header)); + + return true; +} + +static void +wave_encoder_close(Encoder *_encoder) +{ + WaveEncoder *encoder = (WaveEncoder *)_encoder; + + encoder->buffer.Destruct(); +} + +static size_t +pcm16_to_wave(uint16_t *dst16, const uint16_t *src16, size_t length) +{ + size_t cnt = length >> 1; + while (cnt > 0) { + *dst16++ = ToLE16(*src16++); + cnt--; + } + return length; +} + +static size_t +pcm32_to_wave(uint32_t *dst32, const uint32_t *src32, size_t length) +{ + size_t cnt = length >> 2; + while (cnt > 0){ + *dst32++ = ToLE32(*src32++); + cnt--; + } + return length; +} + +static 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 = encoder->buffer->Write(length); + + if (IsLittleEndian()) { + 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; + } + } else { + switch (encoder->bits) { + case 8: + memcpy(dst, src, length); + break; + case 16: + length = pcm16_to_wave((uint16_t *)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((uint32_t *)dst, + (const uint32_t *)src, length); + break; + } + } + + encoder->buffer->Append(length); + return true; +} + +static size_t +wave_encoder_read(Encoder *_encoder, void *dest, size_t length) +{ + WaveEncoder *encoder = (WaveEncoder *)_encoder; + + return encoder->buffer->Read((uint8_t *)dest, 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/plugins/WaveEncoderPlugin.hxx b/src/encoder/plugins/WaveEncoderPlugin.hxx new file mode 100644 index 000000000..341b98adc --- /dev/null +++ b/src/encoder/plugins/WaveEncoderPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/event/BufferedSocket.cxx b/src/event/BufferedSocket.cxx index 92e350e85..c590c215d 100644 --- a/src/event/BufferedSocket.cxx +++ b/src/event/BufferedSocket.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,6 +22,9 @@ #include "system/SocketError.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" +#include "Compiler.h" + +#include <algorithm> BufferedSocket::ssize_t BufferedSocket::DirectRead(void *data, size_t length) diff --git a/src/event/BufferedSocket.hxx b/src/event/BufferedSocket.hxx index db920f981..1c9b44e46 100644 --- a/src/event/BufferedSocket.hxx +++ b/src/event/BufferedSocket.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,13 +23,12 @@ #include "check.h" #include "SocketMonitor.hxx" #include "util/FifoBuffer.hxx" -#include "Compiler.h" #include <assert.h> #include <stdint.h> -struct fifo_buffer; class Error; +class EventLoop; /** * A #SocketMonitor specialization that adds an input buffer. diff --git a/src/event/Call.cxx b/src/event/Call.cxx index ab1d5ffbd..bc16c4e95 100644 --- a/src/event/Call.cxx +++ b/src/event/Call.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,9 +28,7 @@ #include <assert.h> class BlockingCallMonitor final -#ifndef USE_EPOLL : DeferredMonitor -#endif { const std::function<void()> f; @@ -40,24 +38,13 @@ class BlockingCallMonitor final 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) @@ -65,16 +52,8 @@ public: mutex.unlock(); } -#ifndef USE_EPOLL private: virtual void RunDeferred() override { - DoRun(); - } - -#else -public: -#endif - void DoRun() { assert(!done); f(); diff --git a/src/event/Call.hxx b/src/event/Call.hxx index 34d886ca5..808965de1 100644 --- a/src/event/Call.hxx +++ b/src/event/Call.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/event/DeferredMonitor.cxx b/src/event/DeferredMonitor.cxx index 4ffffaa89..3e824012f 100644 --- a/src/event/DeferredMonitor.cxx +++ b/src/event/DeferredMonitor.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,58 +24,11 @@ void DeferredMonitor::Cancel() { -#ifdef USE_EPOLL - pending = false; -#else - const auto id = source_id.exchange(0); - if (id != 0) - g_source_remove(id); -#endif + loop.RemoveDeferred(*this); } 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 + loop.AddDeferred(*this); } - -#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 index 2380fb66f..3d3ab22b7 100644 --- a/src/event/DeferredMonitor.hxx +++ b/src/event/DeferredMonitor.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,61 +23,31 @@ #include "check.h" #include "Compiler.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. + * + * This class is thread-safe. */ -class DeferredMonitor -#ifdef USE_EPOLL - : private SocketMonitor -#endif -{ -#ifdef USE_EPOLL - std::atomic_bool pending; - WakeFD fd; -#else +class DeferredMonitor { EventLoop &loop; - std::atomic<guint> source_id; -#endif + friend class EventLoop; + bool pending; 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 + :loop(_loop), pending(false) {} ~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(); @@ -85,14 +55,6 @@ public: 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 index 8b57b1308..457add2b0 100644 --- a/src/event/FullyBufferedSocket.cxx +++ b/src/event/FullyBufferedSocket.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,9 +20,9 @@ #include "config.h" #include "FullyBufferedSocket.hxx" #include "system/SocketError.hxx" -#include "util/fifo_buffer.h" #include "util/Error.hxx" #include "util/Domain.hxx" +#include "Compiler.h" #include <assert.h> #include <stdint.h> @@ -59,15 +59,14 @@ FullyBufferedSocket::Flush() { assert(IsDefined()); - size_t length; - const void *data = output.Read(&length); - if (data == nullptr) { + const auto data = output.Read(); + if (data.IsEmpty()) { IdleMonitor::Cancel(); CancelWrite(); return true; } - auto nbytes = DirectWrite(data, length); + auto nbytes = DirectWrite(data.data, data.size); if (gcc_unlikely(nbytes <= 0)) return nbytes == 0; diff --git a/src/event/FullyBufferedSocket.hxx b/src/event/FullyBufferedSocket.hxx index c50bb5f61..b03152be2 100644 --- a/src/event/FullyBufferedSocket.hxx +++ b/src/event/FullyBufferedSocket.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,7 +24,6 @@ #include "BufferedSocket.hxx" #include "IdleMonitor.hxx" #include "util/PeakBuffer.hxx" -#include "Compiler.h" /** * A #BufferedSocket specialization that adds an output buffer. diff --git a/src/event/IdleMonitor.cxx b/src/event/IdleMonitor.cxx index c99c66b26..4af656a22 100644 --- a/src/event/IdleMonitor.cxx +++ b/src/event/IdleMonitor.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,38 +21,31 @@ #include "IdleMonitor.hxx" #include "Loop.hxx" +#include <assert.h> + void IdleMonitor::Cancel() { - assert(loop.IsInside()); + assert(loop.IsInsideOrNull()); 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()); + assert(loop.IsInsideOrVirgin()); if (IsActive()) /* already scheduled */ return; -#ifdef USE_EPOLL active = true; loop.AddIdle(*this); -#else - source_id = loop.AddIdle(Callback, this); -#endif } void @@ -60,25 +53,8 @@ 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 index c8e79eb1d..65aaa38cf 100644 --- a/src/event/IdleMonitor.hxx +++ b/src/event/IdleMonitor.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,41 +22,35 @@ #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. + * + * This class is not thread-safe, all methods must be called from the + * thread that runs the #EventLoop, except where explicitly documented + * as thread-safe. */ 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(); +#ifndef NDEBUG + /* this check is redundant, it is only here to avoid + the assertion in Cancel() */ + if (IsActive()) +#endif + Cancel(); } EventLoop &GetEventLoop() const { @@ -64,11 +58,7 @@ public: } bool IsActive() const { -#ifdef USE_EPOLL return active; -#else - return source_id != 0; -#endif } void Schedule(); @@ -79,9 +69,6 @@ protected: 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 index 5aa24aea2..6bbb6df8e 100644 --- a/src/event/Loop.cxx +++ b/src/event/Loop.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,20 +20,21 @@ #include "config.h" #include "Loop.hxx" -#ifdef USE_EPOLL - #include "system/Clock.hxx" #include "TimeoutMonitor.hxx" #include "SocketMonitor.hxx" #include "IdleMonitor.hxx" +#include "DeferredMonitor.hxx" #include <algorithm> EventLoop::EventLoop(Default) :SocketMonitor(*this), now_ms(::MonotonicClockMS()), - quit(false), - n_events(0), + quit(false), busy(true), +#ifndef NDEBUG + virgin(true), +#endif thread(ThreadId::Null()) { SocketMonitor::Open(wake_fd.Get()); @@ -45,45 +46,51 @@ EventLoop::~EventLoop() assert(idle.empty()); assert(timers.empty()); - /* avoid closing the WakeFD twice */ - SocketMonitor::Steal(); + /* this is necessary to get a well-defined destruction + order */ + SocketMonitor::Cancel(); } void EventLoop::Break() { - if (IsInside()) - quit = true; - else - AddCall([this]() { Break(); }); + quit = true; + wake_fd.Write(); } -void -EventLoop::Abandon(SocketMonitor &m) +bool +EventLoop::Abandon(int _fd, SocketMonitor &m) { - for (unsigned i = 0, n = n_events; i < n; ++i) - if (events[i].data.ptr == &m) - events[i].events = 0; + assert(IsInsideOrVirgin()); + + poll_result.Clear(&m); + return poll_group.Abandon(_fd); } bool EventLoop::RemoveFD(int _fd, SocketMonitor &m) { - Abandon(m); - return epoll.Remove(_fd); + assert(IsInsideOrNull()); + + poll_result.Clear(&m); + return poll_group.Remove(_fd); } void EventLoop::AddIdle(IdleMonitor &i) { + assert(IsInsideOrVirgin()); assert(std::find(idle.begin(), idle.end(), &i) == idle.end()); idle.push_back(&i); + again = true; } void EventLoop::RemoveIdle(IdleMonitor &i) { + assert(IsInsideOrVirgin()); + auto it = std::find(idle.begin(), idle.end(), &i); assert(it != idle.end()); @@ -93,12 +100,19 @@ EventLoop::RemoveIdle(IdleMonitor &i) void EventLoop::AddTimer(TimeoutMonitor &t, unsigned ms) { + /* can't use IsInsideOrVirgin() here because libavahi-client + modifies the timeout during avahi_client_free() */ + assert(IsInsideOrNull()); + timers.insert(TimerRecord(t, now_ms + ms)); + again = true; } void EventLoop::CancelTimer(TimeoutMonitor &t) { + assert(IsInsideOrNull()); + for (auto i = timers.begin(), end = timers.end(); i != end; ++i) { if (&i->timer == &t) { timers.erase(i); @@ -107,19 +121,24 @@ EventLoop::CancelTimer(TimeoutMonitor &t) } } -#endif - void EventLoop::Run() { assert(thread.IsNull()); + assert(virgin); + +#ifndef NDEBUG + virgin = false; +#endif + thread = ThreadId::GetCurrent(); -#ifdef USE_EPOLL assert(!quit); + assert(busy); do { now_ms = ::MonotonicClockMS(); + again = false; /* invoke timers */ @@ -146,7 +165,6 @@ EventLoop::Run() /* invoke idle */ - const bool idle_empty = idle.empty(); while (!idle.empty()) { IdleMonitor &m = *idle.front(); idle.pop_front(); @@ -156,7 +174,14 @@ EventLoop::Run() return; } - if (!idle_empty) + /* try to handle DeferredMonitors without WakeFD + overhead */ + mutex.lock(); + HandleDeferred(); + busy = false; + mutex.unlock(); + + if (again) /* re-evaluate timers because one of the IdleMonitors may have added a new timeout */ @@ -164,101 +189,107 @@ EventLoop::Run() /* wait for new event */ - const int n = epoll.Wait(events, MAX_EVENTS, timeout_ms); - n_events = std::max(n, 0); + poll_group.ReadEvents(poll_result, timeout_ms); now_ms = ::MonotonicClockMS(); - assert(!quit); + mutex.lock(); + busy = true; + mutex.unlock(); /* 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); - + for (int i = 0; i < poll_result.GetSize(); ++i) { + auto events = poll_result.GetEvents(i); + if (events != 0) { if (quit) break; + + auto m = (SocketMonitor *)poll_result.GetObject(i); + m->Dispatch(events); } } - n_events = 0; + poll_result.Reset(); + } while (!quit); -#else - g_main_loop_run(loop); -#endif +#ifndef NDEBUG + assert(busy); assert(thread.IsInside()); + thread = ThreadId::Null(); +#endif } -#ifdef USE_EPOLL - void -EventLoop::AddCall(std::function<void()> &&f) +EventLoop::AddDeferred(DeferredMonitor &d) { mutex.lock(); - calls.push_back(f); + if (d.pending) { + mutex.unlock(); + return; + } + + assert(std::find(deferred.begin(), + deferred.end(), &d) == deferred.end()); + + /* we don't need to wake up the EventLoop if another + DeferredMonitor has already done it */ + const bool must_wake = !busy && deferred.empty(); + + d.pending = true; + deferred.push_back(&d); + again = true; mutex.unlock(); - wake_fd.Write(); + if (must_wake) + wake_fd.Write(); } -bool -EventLoop::OnSocketReady(gcc_unused unsigned flags) +void +EventLoop::RemoveDeferred(DeferredMonitor &d) { - assert(!quit); + const ScopeLock protect(mutex); - wake_fd.Read(); + if (!d.pending) { + assert(std::find(deferred.begin(), + deferred.end(), &d) == deferred.end()); + return; + } - mutex.lock(); + d.pending = false; - while (!calls.empty() && !quit) { - auto f = std::move(calls.front()); - calls.pop_front(); + auto i = std::find(deferred.begin(), deferred.end(), &d); + assert(i != deferred.end()); + + deferred.erase(i); +} + +void +EventLoop::HandleDeferred() +{ + while (!deferred.empty() && !quit) { + DeferredMonitor &m = *deferred.front(); + assert(m.pending); + + deferred.pop_front(); + m.pending = false; mutex.unlock(); - f(); + m.RunDeferred(); mutex.lock(); } - - mutex.unlock(); - - return true; } -#else - -guint -EventLoop::AddIdle(GSourceFunc function, gpointer data) +bool +EventLoop::OnSocketReady(gcc_unused unsigned flags) { - GSource *source = g_idle_source_new(); - g_source_set_callback(source, function, data, nullptr); - guint id = g_source_attach(source, GetContext()); - g_source_unref(source); - return id; -} + assert(IsInside()); -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; -} + wake_fd.Read(); -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; -} + mutex.lock(); + HandleDeferred(); + mutex.unlock(); -#endif + return true; +} diff --git a/src/event/Loop.hxx b/src/event/Loop.hxx index 62e733747..322c1ec82 100644 --- a/src/event/Loop.hxx +++ b/src/event/Loop.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,33 +24,32 @@ #include "thread/Id.hxx" #include "Compiler.h" -#ifdef USE_EPOLL -#include "system/EPollFD.hxx" +#include "PollGroup.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 DeferredMonitor; class SocketMonitor; -#endif #include <assert.h> -class EventLoop final -#ifdef USE_EPOLL - : private SocketMonitor -#endif +/** + * An event loop that polls for events on file/socket descriptors. + * + * This class is not thread-safe, all methods must be called from the + * thread that runs it, except where explicitly documented as + * thread-safe. + * + * @see SocketMonitor, MultiSocketMonitor, TimeoutMonitor, IdleMonitor + */ +class EventLoop final : SocketMonitor { -#ifdef USE_EPOLL struct TimerRecord { /** * Projected monotonic_clock_ms() value when this @@ -73,52 +72,80 @@ class EventLoop final } }; - EPollFD epoll; - WakeFD wake_fd; std::multiset<TimerRecord> timers; std::list<IdleMonitor *> idle; Mutex mutex; - std::list<std::function<void()>> calls; + std::list<DeferredMonitor *> deferred; unsigned now_ms; bool quit; - static constexpr unsigned MAX_EVENTS = 16; - unsigned n_events; - epoll_event events[MAX_EVENTS]; -#else - GMainContext *context; - GMainLoop *loop; + /** + * True when the object has been modified and another check is + * necessary before going to sleep via PollGroup::ReadEvents(). + */ + bool again; + + /** + * True when handling callbacks, false when waiting for I/O or + * timeout. + * + * Protected with #mutex. + */ + bool busy; + +#ifndef NDEBUG + /** + * True if Run() was never called. This is used for assert() + * calls. + */ + bool virgin; #endif + PollGroup poll_group; + PollResult poll_result; + /** * A reference to the thread that is currently inside Run(). */ ThreadId thread; public: -#ifdef USE_EPOLL struct Default {}; EventLoop(Default dummy=Default()); ~EventLoop(); + /** + * A caching wrapper for MonotonicClockMS(). + */ unsigned GetTimeMS() const { + assert(IsInside()); + return now_ms; } + /** + * Stop execution of this #EventLoop at the next chance. This + * method is thread-safe and non-blocking: after returning, it + * is not guaranteed that the EventLoop has really stopped. + */ void Break(); bool AddFD(int _fd, unsigned flags, SocketMonitor &m) { - return epoll.Add(_fd, flags, &m); + assert(thread.IsNull() || thread.IsInside()); + + return poll_group.Add(_fd, flags, &m); } bool ModifyFD(int _fd, unsigned flags, SocketMonitor &m) { - return epoll.Modify(_fd, flags, &m); + assert(IsInside()); + + return poll_group.Modify(_fd, flags, &m); } /** @@ -126,7 +153,7 @@ public: * has been closed. This is like RemoveFD(), but does not * attempt to use #EPOLL_CTL_DEL. */ - void Abandon(SocketMonitor &m); + bool Abandon(int fd, SocketMonitor &m); bool RemoveFD(int fd, SocketMonitor &m); @@ -136,53 +163,38 @@ public: void AddTimer(TimeoutMonitor &t, unsigned ms); void CancelTimer(TimeoutMonitor &t); - void AddCall(std::function<void()> &&f); + /** + * Schedule a call to DeferredMonitor::RunDeferred(). + * + * This method is thread-safe. + */ + void AddDeferred(DeferredMonitor &d); + + /** + * Cancel a pending call to DeferredMonitor::RunDeferred(). + * However after returning, the call may still be running. + * + * This method is thread-safe. + */ + void RemoveDeferred(DeferredMonitor &d); + /** + * The main function of this class. It will loop until + * Break() gets called. Can be called only once. + */ void Run(); private: + /** + * Invoke all pending DeferredMonitors. + * + * Caller must lock the mutex. + */ + void HandleDeferred(); + 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? @@ -193,6 +205,20 @@ public: return thread.IsInside(); } + +#ifndef NDEBUG + gcc_pure + bool IsInsideOrVirgin() const { + return virgin || IsInside(); + } +#endif + +#ifndef NDEBUG + gcc_pure + bool IsInsideOrNull() const { + return thread.IsNull() || thread.IsInside(); + } +#endif }; #endif /* MAIN_NOTIFY_H */ diff --git a/src/event/MultiSocketMonitor.cxx b/src/event/MultiSocketMonitor.cxx index bd1aa6fef..ef77de425 100644 --- a/src/event/MultiSocketMonitor.cxx +++ b/src/event/MultiSocketMonitor.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,12 +20,12 @@ #include "config.h" #include "MultiSocketMonitor.hxx" #include "Loop.hxx" -#include "system/fd_util.h" -#include "Compiler.h" -#include <assert.h> +#include <algorithm> -#ifdef USE_EPOLL +#ifndef WIN32 +#include <poll.h> +#endif MultiSocketMonitor::MultiSocketMonitor(EventLoop &_loop) :IdleMonitor(_loop), TimeoutMonitor(_loop), ready(false) { @@ -37,6 +37,40 @@ MultiSocketMonitor::~MultiSocketMonitor() } void +MultiSocketMonitor::ClearSocketList() +{ + assert(GetEventLoop().IsInsideOrNull()); + + fds.clear(); +} + +#ifndef WIN32 + +void +MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) +{ + pollfd *const end = pfds + n; + + 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); +} + +#endif + +void MultiSocketMonitor::Prepare() { int timeout_ms = PrepareSockets(); @@ -64,100 +98,3 @@ MultiSocketMonitor::OnIdle() 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 index 8ee81a508..b40ee8caa 100644 --- a/src/event/MultiSocketMonitor.hxx +++ b/src/event/MultiSocketMonitor.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,40 +21,38 @@ #define MPD_MULTI_SOCKET_MONITOR_HXX #include "check.h" -#include "Compiler.h" - -#ifdef USE_EPOLL #include "IdleMonitor.hxx" #include "TimeoutMonitor.hxx" #include "SocketMonitor.hxx" -#else -#include <glib.h> -#endif +#include "Compiler.h" #include <forward_list> +#include <iterator> #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 */ +/* ERROR is a WIN32 macro that poisons our namespace; this is a kludge + to allow us to use it anyway */ #ifdef ERROR #undef ERROR #endif #endif +#ifndef WIN32 +struct pollfd; +#endif + class EventLoop; /** - * Monitor multiple sockets. + * Similar to #SocketMonitor, but monitors multiple sockets. To use + * it, implement the methods PrepareSockets() and DispatchSockets(). + * In PrepareSockets(), use UpdateSocketList() and AddSocket(). + * DispatchSockets() will be called if at least one socket is ready. */ -class MultiSocketMonitor -#ifdef USE_EPOLL - : private IdleMonitor, private TimeoutMonitor -#endif +class MultiSocketMonitor : IdleMonitor, TimeoutMonitor { -#ifdef USE_EPOLL class SingleFD final : public SocketMonitor { MultiSocketMonitor &multi; @@ -99,93 +97,45 @@ class MultiSocketMonitor 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 - + /** + * Invalidate the socket list. A call to PrepareSockets() is + * scheduled which will then update the list. + */ 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 } + /** + * Remove all sockets. + */ + void ClearSocketList(); + + /** + * Update the known sockets by invoking the given function for + * each one; its return value is the events bit mask. A + * return value of 0 means the socket will be removed from the + * list. + */ template<typename E> void UpdateSocketList(E &&e) { for (auto prev = fds.before_begin(), end = fds.end(), @@ -198,16 +148,19 @@ public: 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); } } } +#ifndef WIN32 + /** + * Replace the socket list with the given file descriptors. + * The given pollfd array will be modified by this method. + */ + void ReplaceSocketList(pollfd *pfds, unsigned n); +#endif + protected: /** * @return timeout [ms] or -1 for no timeout @@ -215,7 +168,6 @@ protected: virtual int PrepareSockets() = 0; virtual void DispatchSockets() = 0; -#ifdef USE_EPOLL private: void SetReady() { ready = true; @@ -230,23 +182,6 @@ private: } 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/PollGroup.hxx b/src/event/PollGroup.hxx new file mode 100644 index 000000000..a2f176860 --- /dev/null +++ b/src/event/PollGroup.hxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_POLLGROUP_HXX +#define MPD_EVENT_POLLGROUP_HXX + +#ifdef USE_EPOLL +#include "PollGroupEPoll.hxx" +typedef PollResultEPoll PollResult; +typedef PollGroupEPoll PollGroup; +#endif + +#ifdef USE_WINSELECT +#include "PollGroupWinSelect.hxx" +typedef PollResultGeneric PollResult; +typedef PollGroupWinSelect PollGroup; +#endif + +#ifdef USE_POLL +#include "PollGroupPoll.hxx" +typedef PollResultGeneric PollResult; +typedef PollGroupPoll PollGroup; +#endif + +#endif diff --git a/src/event/PollGroupEPoll.hxx b/src/event/PollGroupEPoll.hxx new file mode 100644 index 000000000..d8edb8a1f --- /dev/null +++ b/src/event/PollGroupEPoll.hxx @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_POLLGROUP_EPOLL_HXX +#define MPD_EVENT_POLLGROUP_EPOLL_HXX + +#include "check.h" + +#include "Compiler.h" +#include "system/EPollFD.hxx" + +#include <array> +#include <algorithm> + +class PollResultEPoll +{ + friend class PollGroupEPoll; + + std::array<epoll_event, 16> events; + int n_events; +public: + PollResultEPoll() : n_events(0) { } + + int GetSize() const { return n_events; } + unsigned GetEvents(int i) const { return events[i].events; } + void *GetObject(int i) const { return events[i].data.ptr; } + void Reset() { n_events = 0; } + + void Clear(void *obj) { + for (int i = 0; i < n_events; ++i) + if (events[i].data.ptr == obj) + events[i].events = 0; + } +}; + +class PollGroupEPoll +{ + EPollFD epoll; + + PollGroupEPoll(PollGroupEPoll &) = delete; + PollGroupEPoll &operator=(PollGroupEPoll &) = delete; +public: + static constexpr unsigned READ = EPOLLIN; + static constexpr unsigned WRITE = EPOLLOUT; + static constexpr unsigned ERROR = EPOLLERR; + static constexpr unsigned HANGUP = EPOLLHUP; + + PollGroupEPoll() = default; + + void ReadEvents(PollResultEPoll &result, int timeout_ms) { + int ret = epoll.Wait(result.events.data(), result.events.size(), + timeout_ms); + result.n_events = std::max(0, ret); + } + + bool Add(int fd, unsigned events, void *obj) { + return epoll.Add(fd, events, obj); + } + + bool Modify(int fd, unsigned events, void *obj) { + return epoll.Modify(fd, events, obj); + } + + bool Remove(int fd) { + return epoll.Remove(fd); + } + + bool Abandon(gcc_unused int fd) { + // Nothing to do in this implementation. + // Closed descriptors are automatically unregistered. + return true; + } +}; + +#endif diff --git a/src/event/PollGroupPoll.cxx b/src/event/PollGroupPoll.cxx new file mode 100644 index 000000000..402f8616f --- /dev/null +++ b/src/event/PollGroupPoll.cxx @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with 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_POLL + +#include "PollGroupPoll.hxx" + +#include <assert.h> + +PollGroupPoll::PollGroupPoll() { } +PollGroupPoll::~PollGroupPoll() { } + +bool PollGroupPoll::Add(int fd, unsigned events, void *obj) +{ + assert(items.find(fd) == items.end()); + + const size_t index = poll_events.size(); + poll_events.resize(index + 1); + auto &e = poll_events[index]; + e.fd = fd; + e.events = events; + e.revents = 0; + auto &item = items[fd]; + item.index = index; + item.obj = obj; + return true; +} + +bool PollGroupPoll::Modify(int fd, unsigned events, void *obj) +{ + auto item_iter = items.find(fd); + assert(item_iter != items.end()); + auto &item = item_iter->second; + item.obj = obj; + auto &e = poll_events[item.index]; + e.events = events; + e.revents &= events; + return true; +} + +bool PollGroupPoll::Remove(int fd) +{ + auto item_iter = items.find(fd); + assert(item_iter != items.end()); + auto &item = item_iter->second; + size_t index = item.index; + size_t last_index = poll_events.size() - 1; + if (index != last_index) { + std::swap(poll_events[index], poll_events[last_index]); + items[poll_events[index].fd].index = index; + } + poll_events.pop_back(); + items.erase(item_iter); + return true; +} + +void PollGroupPoll::ReadEvents(PollResultGeneric &result, int timeout_ms) +{ + int n = poll(poll_events.empty() ? nullptr : &poll_events[0], + poll_events.size(), timeout_ms); + + for (size_t i = 0; n > 0 && i < poll_events.size(); ++i) { + const auto &e = poll_events[i]; + if (e.revents != 0) { + result.Add(e.revents, items[e.fd].obj); + --n; + } + } +} + +#endif diff --git a/src/event/PollGroupPoll.hxx b/src/event/PollGroupPoll.hxx new file mode 100644 index 000000000..f7a3ccb4f --- /dev/null +++ b/src/event/PollGroupPoll.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_POLLGROUP_POLL_HXX +#define MPD_EVENT_POLLGROUP_POLL_HXX + +#include "check.h" +#include "PollResultGeneric.hxx" + +#include <vector> +#include <unordered_map> + +#include <stddef.h> +#include <sys/poll.h> + +class PollGroupPoll +{ + struct Item + { + size_t index; + void *obj; + }; + + std::vector<pollfd> poll_events; + std::unordered_map<int, Item> items; + + PollGroupPoll(PollGroupPoll &) = delete; + PollGroupPoll &operator=(PollGroupPoll &) = delete; +public: + static constexpr unsigned READ = POLLIN; + static constexpr unsigned WRITE = POLLOUT; + static constexpr unsigned ERROR = POLLERR; + static constexpr unsigned HANGUP = POLLHUP; + + PollGroupPoll(); + ~PollGroupPoll(); + + void ReadEvents(PollResultGeneric &result, int timeout_ms); + bool Add(int fd, unsigned events, void *obj); + bool Modify(int fd, unsigned events, void *obj); + bool Remove(int fd); + bool Abandon(int fd) { + return Remove(fd); + } +}; + +#endif diff --git a/src/event/PollGroupWinSelect.cxx b/src/event/PollGroupWinSelect.cxx new file mode 100644 index 000000000..26c8abd46 --- /dev/null +++ b/src/event/PollGroupWinSelect.cxx @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with 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_WINSELECT + +#include "PollGroupWinSelect.hxx" + +constexpr int EVENT_READ = 0; +constexpr int EVENT_WRITE = 1; + +static inline bool HasEvent(unsigned events, int event_id) +{ + return (events & (1 << event_id)) != 0; +} + +PollGroupWinSelect::PollGroupWinSelect() { } +PollGroupWinSelect::~PollGroupWinSelect() { } + +bool PollGroupWinSelect::CanModify(PollGroupWinSelect::Item &item, + unsigned events, int event_id) +{ + if (item.index[event_id] < 0 && HasEvent(events, event_id)) + return !event_set[event_id].IsFull(); + return true; +} + +void PollGroupWinSelect::Modify(PollGroupWinSelect::Item &item, int fd, + unsigned events, int event_id) +{ + int index = item.index[event_id]; + auto &set = event_set[event_id]; + + if (index < 0 && HasEvent(events, event_id)) + item.index[event_id] = set.Add(fd); + else if (index >= 0 && !HasEvent(events, event_id)) { + if (index != set.Size() - 1) { + set.MoveToEnd(index); + items[set[index]].index[event_id] = index; + } + set.RemoveLast(); + item.index[event_id] = -1; + } +} + +bool PollGroupWinSelect::Add(int fd, unsigned events, void *obj) +{ + assert(items.find(fd) == items.end()); + auto &item = items[fd]; + + item.index[EVENT_READ] = -1; + item.index[EVENT_WRITE] = -1; + item.obj = obj; + item.events = 0; + + if (!CanModify(item, events, EVENT_READ)) { + items.erase(fd); + return false; + } + if (!CanModify(item, events, EVENT_WRITE)) { + items.erase(fd); + return false; + } + + Modify(item, fd, events, EVENT_READ); + Modify(item, fd, events, EVENT_WRITE); + return true; +} + +bool PollGroupWinSelect::Modify(int fd, unsigned events, void *obj) +{ + auto item_iter = items.find(fd); + assert(item_iter != items.end()); + auto &item = item_iter->second; + + if (!CanModify(item, events, EVENT_READ)) + return false; + if (!CanModify(item, events, EVENT_WRITE)) + return false; + + item.obj = obj; + Modify(item, fd, events, EVENT_READ); + Modify(item, fd, events, EVENT_WRITE); + return true; +} + +bool PollGroupWinSelect::Remove(int fd) +{ + auto item_iter = items.find(fd); + assert(item_iter != items.end()); + auto &item = item_iter->second; + + Modify(item, fd, 0, EVENT_READ); + Modify(item, fd, 0, EVENT_WRITE); + items.erase(item_iter); + return true; +} + +void PollGroupWinSelect::ReadEvents(PollResultGeneric &result, int timeout_ms) +{ + bool use_sleep = event_set[EVENT_READ].IsEmpty() && + event_set[EVENT_WRITE].IsEmpty(); + + if (use_sleep) { + Sleep(timeout_ms < 0 ? INFINITE : (DWORD) timeout_ms); + return; + } + + SocketSet read_set(event_set[EVENT_READ]); + SocketSet write_set(event_set[EVENT_WRITE]); + SocketSet except_set(event_set[EVENT_WRITE]); + + timeval tv; + if (timeout_ms >= 0) { + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + } + + int ret = select(0, + read_set.IsEmpty() ? nullptr : read_set.GetPtr(), + write_set.IsEmpty() ? nullptr : write_set.GetPtr(), + except_set.IsEmpty() ? nullptr : except_set.GetPtr(), + timeout_ms < 0 ? nullptr : &tv); + + if (ret == 0 || ret == SOCKET_ERROR) + return; + + for (int i = 0; i < read_set.Size(); ++i) + items[read_set[i]].events |= READ; + + for (int i = 0; i < write_set.Size(); ++i) + items[write_set[i]].events |= WRITE; + + for (int i = 0; i < except_set.Size(); ++i) + items[except_set[i]].events |= WRITE; + + for (auto i = items.begin(); i != items.end(); ++i) + if (i->second.events != 0) { + result.Add(i->second.events, i->second.obj); + i->second.events = 0; + } +} + +#endif diff --git a/src/event/PollGroupWinSelect.hxx b/src/event/PollGroupWinSelect.hxx new file mode 100644 index 000000000..d01067709 --- /dev/null +++ b/src/event/PollGroupWinSelect.hxx @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_POLLGROUP_WINSELECT_HXX +#define MPD_EVENT_POLLGROUP_WINSELECT_HXX + +#include "check.h" + +#include "PollResultGeneric.hxx" + +#include <assert.h> +#include <string.h> + +#include <unordered_map> + +#include <windows.h> +#include <winsock2.h> + +#ifdef ERROR +#undef ERROR +#endif + +class SocketSet +{ + fd_set set; +public: + SocketSet() { set.fd_count = 0; } + SocketSet(SocketSet &other) { + set.fd_count = other.set.fd_count; + memcpy(set.fd_array, + other.set.fd_array, + sizeof (SOCKET) * set.fd_count); + } + + fd_set *GetPtr() { return &set; } + int Size() { return set.fd_count; } + bool IsEmpty() { return set.fd_count == 0; } + bool IsFull() { return set.fd_count == FD_SETSIZE; } + + int operator[](int index) { + assert(index >= 0 && (u_int)index < set.fd_count); + return set.fd_array[index]; + } + + int Add(int fd) { + assert(!IsFull()); + set.fd_array[set.fd_count] = fd; + return set.fd_count++; + } + + void MoveToEnd(int index) { + assert(index >= 0 && (u_int)index < set.fd_count); + std::swap(set.fd_array[index], set.fd_array[set.fd_count - 1]); + } + + void RemoveLast() { + assert(!IsEmpty()); + --set.fd_count; + } +}; + +class PollGroupWinSelect +{ + struct Item + { + int index[2]; + void *obj; + unsigned events; + }; + + SocketSet event_set[2]; + std::unordered_map<int, Item> items; + + bool CanModify(Item &item, unsigned events, int event_id); + void Modify(Item &item, int fd, unsigned events, int event_id); + + PollGroupWinSelect(PollGroupWinSelect &) = delete; + PollGroupWinSelect &operator=(PollGroupWinSelect &) = delete; +public: + static constexpr unsigned READ = 1; + static constexpr unsigned WRITE = 2; + static constexpr unsigned ERROR = 0; + static constexpr unsigned HANGUP = 0; + + PollGroupWinSelect(); + ~PollGroupWinSelect(); + + void ReadEvents(PollResultGeneric &result, int timeout_ms); + bool Add(int fd, unsigned events, void *obj); + bool Modify(int fd, unsigned events, void *obj); + bool Remove(int fd); + bool Abandon(int fd) { return Remove(fd); } +}; + +#endif diff --git a/src/event/PollResultGeneric.hxx b/src/event/PollResultGeneric.hxx new file mode 100644 index 000000000..35daf7f08 --- /dev/null +++ b/src/event/PollResultGeneric.hxx @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_POLLRESULT_GENERIC_HXX +#define MPD_EVENT_POLLRESULT_GENERIC_HXX + +#include "check.h" + +#include <vector> + +class PollResultGeneric +{ + struct Item + { + unsigned events; + void *obj; + + Item() = default; + Item(unsigned _events, void *_obj) + : events(_events), obj(_obj) { } + }; + + std::vector<Item> items; +public: + int GetSize() const { return items.size(); } + unsigned GetEvents(int i) const { return items[i].events; } + void *GetObject(int i) const { return items[i].obj; } + void Reset() { items.clear(); } + + void Clear(void *obj) { + for (auto i = items.begin(); i != items.end(); ++i) + if (i->obj == obj) + i->events = 0; + } + + void Add(unsigned events, void *obj) { + items.emplace_back(events, obj); + } +}; + +#endif diff --git a/src/event/ServerSocket.cxx b/src/event/ServerSocket.cxx index 781d29181..c465c15c8 100644 --- a/src/event/ServerSocket.cxx +++ b/src/event/ServerSocket.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -31,13 +31,13 @@ #include "system/fd_util.h" #include "fs/AllocatedPath.hxx" #include "fs/FileSystem.hxx" +#include "util/Alloc.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" -#include <glib.h> - #include <string> +#include <algorithm> #include <sys/types.h> #include <sys/stat.h> @@ -78,7 +78,7 @@ public: parent(_parent), serial(_serial), path(AllocatedPath::Null()), address_length(_address_length), - address((sockaddr *)g_memdup(_address, _address_length)) + address((sockaddr *)xmemdup(_address, _address_length)) { assert(_address != nullptr); assert(_address_length > 0); @@ -88,7 +88,10 @@ public: OneServerSocket &operator=(const OneServerSocket &other) = delete; ~OneServerSocket() { - g_free(address); + free(address); + + if (IsDefined()) + Close(); } unsigned GetSerial() const { @@ -106,7 +109,10 @@ public: using SocketMonitor::IsDefined; using SocketMonitor::Close; - char *ToString() const; + gcc_pure + std::string ToString() const { + return sockaddr_to_string(address, address_length); + } void SetFD(int _fd) { SocketMonitor::Open(_fd); @@ -121,18 +127,6 @@ private: 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) { @@ -242,23 +236,21 @@ ServerSocket::Open(Error &error) Error error2; if (!i.Open(error2)) { if (good != nullptr && good->GetSerial() == i.GetSerial()) { - char *address_string = i.ToString(); - char *good_string = good->ToString(); + const auto address_string = i.ToString(); + const auto 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); + address_string.c_str(), + error2.GetMessage(), + good_string.c_str()); } else if (bad == nullptr) { bad = &i; - char *address_string = i.ToString(); + const auto address_string = i.ToString(); error2.FormatPrefix("Failed to bind to '%s': ", - address_string); - g_free(address_string); + address_string.c_str()); last_error = std::move(error2); } diff --git a/src/event/ServerSocket.hxx b/src/event/ServerSocket.hxx index facb10371..4c3fd9f1d 100644 --- a/src/event/ServerSocket.hxx +++ b/src/event/ServerSocket.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -36,6 +36,9 @@ typedef void (*server_socket_callback_t)(int fd, class OneServerSocket; +/** + * A socket that accepts incoming stream connections (e.g. TCP). + */ class ServerSocket { friend class OneServerSocket; diff --git a/src/event/SignalMonitor.cxx b/src/event/SignalMonitor.cxx index 8c8527a77..a845030c4 100644 --- a/src/event/SignalMonitor.cxx +++ b/src/event/SignalMonitor.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -39,6 +39,9 @@ #include <algorithm> +#include <assert.h> +#include <signal.h> + class SignalMonitor final : private SocketMonitor { #ifdef USE_SIGNALFD SignalFD fd; @@ -55,14 +58,6 @@ public: #endif } - ~SignalMonitor() { - /* prevent the descriptor to be closed twice */ -#ifdef USE_SIGNALFD - if (SocketMonitor::IsDefined()) -#endif - SocketMonitor::Steal(); - } - using SocketMonitor::GetEventLoop; #ifdef USE_SIGNALFD diff --git a/src/event/SignalMonitor.hxx b/src/event/SignalMonitor.hxx index 1ecccd40b..a41e57ef9 100644 --- a/src/event/SignalMonitor.hxx +++ b/src/event/SignalMonitor.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/event/SocketMonitor.cxx b/src/event/SocketMonitor.cxx index 2b97059f7..69207287d 100644 --- a/src/event/SocketMonitor.cxx +++ b/src/event/SocketMonitor.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,12 +28,9 @@ #ifdef WIN32 #include <winsock2.h> #else -#include <sys/types.h> #include <sys/socket.h> #endif -#ifdef USE_EPOLL - void SocketMonitor::Dispatch(unsigned flags) { @@ -43,93 +40,19 @@ SocketMonitor::Dispatch(unsigned flags) 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(); + Cancel(); } 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 @@ -142,12 +65,6 @@ SocketMonitor::Steal() int result = fd; fd = -1; -#ifndef USE_EPOLL - g_source_destroy(&source->base); - g_source_unref(&source->base); - source = nullptr; -#endif - return result; } @@ -156,12 +73,9 @@ SocketMonitor::Abandon() { assert(IsDefined()); -#ifdef USE_EPOLL + int old_fd = fd; fd = -1; - loop.Abandon(*this); -#else - Steal(); -#endif + loop.Abandon(old_fd, *this); } void @@ -178,7 +92,6 @@ SocketMonitor::Schedule(unsigned flags) if (flags == GetScheduledFlags()) return; -#ifdef USE_EPOLL if (scheduled_flags == 0) loop.AddFD(fd, flags, *this); else if (flags == 0) @@ -187,12 +100,6 @@ SocketMonitor::Schedule(unsigned flags) loop.ModifyFD(fd, flags, *this); scheduled_flags = flags; -#else - poll.events = flags; - poll.revents &= flags; - - loop.WakeUp(); -#endif } SocketMonitor::ssize_t diff --git a/src/event/SocketMonitor.hxx b/src/event/SocketMonitor.hxx index 5369ddb8a..56d4273f0 100644 --- a/src/event/SocketMonitor.hxx +++ b/src/event/SocketMonitor.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,12 +21,7 @@ #define MPD_SOCKET_MONITOR_HXX #include "check.h" - -#ifdef USE_EPOLL -#include <sys/epoll.h> -#else -#include <glib.h> -#endif +#include "PollGroup.hxx" #include <type_traits> @@ -34,8 +29,8 @@ #include <stddef.h> #ifdef WIN32 -/* ERRORis a WIN32 macro that poisons our namespace; this is a - kludge to allow us to use it anyway */ +/* ERROR is a WIN32 macro that poisons our namespace; this is a kludge + to allow us to use it anyway */ #ifdef ERROR #undef ERROR #endif @@ -43,56 +38,41 @@ class EventLoop; +/** + * Monitor events on a socket. Call Schedule() to announce events + * you're interested in, or Cancel() to cancel your subscription. The + * #EventLoop will invoke virtual method OnSocketReady() as soon as + * any of the subscribed events are ready. + * + * This class does not feel responsible for closing the socket. Call + * Close() to do it manually. + * + * This class is not thread-safe, all methods must be called from the + * thread that runs the #EventLoop, except where explicitly documented + * as thread-safe. + */ 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 + static constexpr unsigned READ = PollGroup::READ; + static constexpr unsigned WRITE = PollGroup::WRITE; + static constexpr unsigned ERROR = PollGroup::ERROR; + static constexpr unsigned HANGUP = PollGroup::HANGUP; 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(); @@ -114,7 +94,7 @@ public: /** * "Steal" the socket descriptor. This abandons the socket - * and puts the responsibility for closing it to the caller. + * and returns it. */ int Steal(); @@ -128,11 +108,7 @@ public: unsigned GetScheduledFlags() const { assert(IsDefined()); -#ifdef USE_EPOLL return scheduled_flags; -#else - return poll.events; -#endif } void Schedule(unsigned flags); @@ -167,28 +143,7 @@ protected: 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 index cffad6b92..e04af3e4e 100644 --- a/src/event/TimeoutMonitor.cxx +++ b/src/event/TimeoutMonitor.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,28 +25,19 @@ 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 @@ -54,31 +45,11 @@ 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 index 98e4e5564..414d48aa6 100644 --- a/src/event/TimeoutMonitor.hxx +++ b/src/event/TimeoutMonitor.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,34 +22,27 @@ #include "check.h" -#ifndef USE_EPOLL -#include <glib.h> -#endif - class EventLoop; +/** + * This class monitors a timeout. Use Schedule() to begin the timeout + * or Cancel() to cancel it. + * + * This class is not thread-safe, all methods must be called from the + * thread that runs the #EventLoop, except where explicitly documented + * as thread-safe. + */ 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(); @@ -60,11 +53,7 @@ public: } bool IsActive() const { -#ifdef USE_EPOLL return active; -#else - return source != nullptr; -#endif } void Schedule(unsigned ms); @@ -76,10 +65,6 @@ protected: 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 index ed1baafd8..c6222b59c 100644 --- a/src/event/WakeFD.hxx +++ b/src/event/WakeFD.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/filter/AutoConvertFilterPlugin.cxx b/src/filter/AutoConvertFilterPlugin.cxx deleted file mode 100644 index 918a16e53..000000000 --- a/src/filter/AutoConvertFilterPlugin.cxx +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 */ - - AudioFormat child_audio_format = in_audio_format; - AudioFormat out_audio_format = filter->Open(child_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 deleted file mode 100644 index 7db72a345..000000000 --- a/src/filter/AutoConvertFilterPlugin.hxx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index cb52b86ca..000000000 --- a/src/filter/ChainFilterPlugin.cxx +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 <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 == nullptr) - return nullptr; - } - - /* 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 deleted file mode 100644 index 884c7ca19..000000000 --- a/src/filter/ChainFilterPlugin.hxx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 deleted file mode 100644 index 040f8426f..000000000 --- a/src/filter/ConvertFilterPlugin.cxx +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index c814aaf49..000000000 --- a/src/filter/ConvertFilterPlugin.hxx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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/FilterConfig.cxx b/src/filter/FilterConfig.cxx new file mode 100644 index 000000000..d8c1fc6c2 --- /dev/null +++ b/src/filter/FilterConfig.cxx @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "plugins/ChainFilterPlugin.hxx" +#include "FilterPlugin.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigOption.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigError.hxx" +#include "util/Error.hxx" + +#include <algorithm> + +#include <string.h> + +static bool +filter_chain_append_new(Filter &chain, const char *template_name, Error &error) +{ + const struct config_param *cfg = + config_find_block(CONF_AUDIO_FILTER, "name", template_name); + if (cfg == nullptr) { + error.Format(config_domain, + "filter template not found: %s", + template_name); + return false; + } + + // Instantiate one of those filter plugins with the template name as a hint + Filter *f = filter_configured_new(*cfg, error); + if (f == nullptr) + // The error has already been set, just stop. + return false; + + const char *plugin_name = cfg->GetBlockValue("plugin", + "unknown"); + filter_chain_append(chain, plugin_name, f); + + return true; +} + +bool +filter_chain_parse(Filter &chain, const char *spec, Error &error) +{ + const char *const end = spec + strlen(spec); + + while (true) { + const char *comma = std::find(spec, end, ','); + if (comma > spec) { + const std::string name(spec, comma); + if (!filter_chain_append_new(chain, name.c_str(), + error)) + return false; + } + + if (comma == end) + break; + + spec = comma + 1; + } + + return true; +} diff --git a/src/filter/FilterConfig.hxx b/src/filter/FilterConfig.hxx new file mode 100644 index 000000000..1018eed51 --- /dev/null +++ b/src/filter/FilterConfig.hxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 true on success + */ +bool +filter_chain_parse(Filter &chain, const char *spec, Error &error); + +#endif diff --git a/src/filter/FilterInternal.hxx b/src/filter/FilterInternal.hxx new file mode 100644 index 000000000..131c3b3f5 --- /dev/null +++ b/src/filter/FilterInternal.hxx @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 nullptr + * 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 nullptr + * to ignore errors. + * @return the destination buffer on success (will be + * invalidated by filter_close() or filter_filter()), nullptr 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/filter/FilterPlugin.cxx b/src/filter/FilterPlugin.cxx new file mode 100644 index 000000000..98314f771 --- /dev/null +++ b/src/filter/FilterPlugin.cxx @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "FilterRegistry.hxx" +#include "config/ConfigData.hxx" +#include "config/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 != nullptr); + 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 == nullptr) { + error.Set(config_domain, "No filter plugin specified"); + return nullptr; + } + + const filter_plugin *plugin = filter_plugin_by_name(plugin_name); + if (plugin == nullptr) { + error.Format(config_domain, + "No such filter plugin: %s", plugin_name); + return nullptr; + } + + return filter_new(plugin, param, error); +} diff --git a/src/filter/FilterPlugin.hxx b/src/filter/FilterPlugin.hxx new file mode 100644 index 000000000..443d29881 --- /dev/null +++ b/src/filter/FilterPlugin.hxx @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 nullptr to + * ignore errors. + * @return a new filter object, or nullptr 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 nullptr to + * ignore errors. + * @return a new filter object, or nullptr on error + */ +Filter * +filter_configured_new(const config_param ¶m, Error &error); + +#endif diff --git a/src/filter/FilterRegistry.cxx b/src/filter/FilterRegistry.cxx new file mode 100644 index 000000000..286fb8db3 --- /dev/null +++ b/src/filter/FilterRegistry.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 <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, + nullptr, +}; + +const struct filter_plugin * +filter_plugin_by_name(const char *name) +{ + for (unsigned i = 0; filter_plugins[i] != nullptr; ++i) + if (strcmp(filter_plugins[i]->name, name) == 0) + return filter_plugins[i]; + + return nullptr; +} diff --git a/src/filter/FilterRegistry.hxx b/src/filter/FilterRegistry.hxx new file mode 100644 index 000000000..24618a87a --- /dev/null +++ b/src/filter/FilterRegistry.hxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + +#include "Compiler.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; + +gcc_pure +const struct filter_plugin * +filter_plugin_by_name(const char *name); + +#endif diff --git a/src/filter/NormalizeFilterPlugin.cxx b/src/filter/NormalizeFilterPlugin.cxx deleted file mode 100644 index 6c4f6b0e5..000000000 --- a/src/filter/NormalizeFilterPlugin.cxx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index c762592f6..000000000 --- a/src/filter/NullFilterPlugin.cxx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 "Compiler.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 deleted file mode 100644 index b2dcde4cc..000000000 --- a/src/filter/ReplayGainFilterPlugin.cxx +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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; - - ReplayGainMode mode; - - ReplayGainInfo 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) { - info.Clear(); - } - - void SetMixer(Mixer *_mixer, unsigned _base) { - assert(_mixer == nullptr || (_base > 0 && _base <= 100)); - - mixer = _mixer; - base = _base; - - Update(); - } - - void SetInfo(const ReplayGainInfo *_info) { - if (_info != nullptr) { - info = *_info; - info.Complete(); - } else - info.Clear(); - - Update(); - } - - void SetMode(ReplayGainMode _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) { - const auto &tuple = info.tuples[mode]; - float scale = tuple.CalculateScale(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 != nullptr) { - /* 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 nullptr; - } - - 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 ReplayGainInfo *info) -{ - ReplayGainFilter *filter = (ReplayGainFilter *)_filter; - - filter->SetInfo(info); -} - -void -replay_gain_filter_set_mode(Filter *_filter, ReplayGainMode mode) -{ - ReplayGainFilter *filter = (ReplayGainFilter *)_filter; - - filter->SetMode(mode); -} diff --git a/src/filter/ReplayGainFilterPlugin.hxx b/src/filter/ReplayGainFilterPlugin.hxx deleted file mode 100644 index fbd1f2712..000000000 --- a/src/filter/ReplayGainFilterPlugin.hxx +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 nullptr 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 nullptr if no replay - * gain data is available for the current song - */ -void -replay_gain_filter_set_info(Filter *filter, const ReplayGainInfo *info); - -void -replay_gain_filter_set_mode(Filter *filter, ReplayGainMode mode); - -#endif diff --git a/src/filter/RouteFilterPlugin.cxx b/src/filter/RouteFilterPlugin.cxx deleted file mode 100644 index d9042c21f..000000000 --- a/src/filter/RouteFilterPlugin.cxx +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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/StringUtil.hxx" -#include "util/Error.hxx" - -#include <algorithm> - -#include <assert.h> -#include <string.h> -#include <stdint.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 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 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" - */ - int8_t sources[MAX_CHANNELS]; - - /** - * 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: - /** - * 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 realloc() instead of one count run and one malloc(). - */ - - std::fill_n(sources, MAX_CHANNELS, -1); - - min_input_channels = 0; - min_output_channels = 0; - - // A cowardly default, just passthrough stereo - const char *routes = param.GetBlockValue("routes", "0>0, 1>1"); - while (true) { - routes = strchug_fast(routes); - - char *endptr; - const unsigned source = strtoul(routes, &endptr, 10); - endptr = strchug_fast(endptr); - if (endptr == routes || *endptr != '>') { - error.Set(config_domain, - "Malformed 'routes' specification"); - return false; - } - - if (source >= MAX_CHANNELS) { - error.Format(config_domain, - "Invalid source channel number: %u", - source); - return false; - } - - if (source >= min_input_channels) - min_input_channels = source + 1; - - routes = strchug_fast(endptr + 1); - - unsigned dest = strtoul(routes, &endptr, 10); - endptr = strchug_fast(endptr); - if (endptr == routes) { - error.Set(config_domain, - "Malformed 'routes' specification"); - return false; - } - - if (dest >= MAX_CHANNELS) { - error.Format(config_domain, - "Invalid destination channel number: %u", - dest); - return false; - } - - if (dest >= min_output_channels) - min_output_channels = dest + 1; - - sources[dest] = source; - - routes = endptr; - - if (*routes == 0) - break; - - if (*routes != ',') { - error.Set(config_domain, - "Malformed 'routes' specification"); - return false; - } - - ++routes; - } - - 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 deleted file mode 100644 index 1b663f6eb..000000000 --- a/src/filter/VolumeFilterPlugin.cxx +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 <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 deleted file mode 100644 index 822b7e93a..000000000 --- a/src/filter/VolumeFilterPlugin.hxx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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/plugins/AutoConvertFilterPlugin.cxx b/src/filter/plugins/AutoConvertFilterPlugin.cxx new file mode 100644 index 000000000..cdeeefdc6 --- /dev/null +++ b/src/filter/plugins/AutoConvertFilterPlugin.cxx @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "filter/FilterPlugin.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/FilterRegistry.hxx" +#include "AudioFormat.hxx" +#include "config/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 */ + + AudioFormat child_audio_format = in_audio_format; + AudioFormat out_audio_format = filter->Open(child_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); + + if (!convert_filter_set(convert, child_audio_format, error)) { + delete convert; + filter->Close(); + return AudioFormat::Undefined(); + } + } 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/plugins/AutoConvertFilterPlugin.hxx b/src/filter/plugins/AutoConvertFilterPlugin.hxx new file mode 100644 index 000000000..c5dfdd2f6 --- /dev/null +++ b/src/filter/plugins/AutoConvertFilterPlugin.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/ChainFilterPlugin.cxx b/src/filter/plugins/ChainFilterPlugin.cxx new file mode 100644 index 000000000..7dc6db667 --- /dev/null +++ b/src/filter/plugins/ChainFilterPlugin.cxx @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "filter/FilterPlugin.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/FilterRegistry.hxx" +#include "AudioFormat.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#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 == nullptr) + return nullptr; + } + + /* 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/plugins/ChainFilterPlugin.hxx b/src/filter/plugins/ChainFilterPlugin.hxx new file mode 100644 index 000000000..b36aa3322 --- /dev/null +++ b/src/filter/plugins/ChainFilterPlugin.hxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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/plugins/ConvertFilterPlugin.cxx b/src/filter/plugins/ConvertFilterPlugin.cxx new file mode 100644 index 000000000..27e6774f8 --- /dev/null +++ b/src/filter/plugins/ConvertFilterPlugin.cxx @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "filter/FilterPlugin.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/FilterRegistry.hxx" +#include "pcm/PcmConvert.hxx" +#include "util/Manual.hxx" +#include "AudioFormat.hxx" +#include "poison.h" + +#include <assert.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. + * + * If this is AudioFormat::Undefined(), then the #PcmConvert + * attribute is not open. This can mean that Set() has failed + * or that no conversion is necessary. + */ + AudioFormat out_audio_format; + + Manual<PcmConvert> state; + +public: + bool Set(const AudioFormat &_out_audio_format, Error &error); + + 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(); +} + +bool +ConvertFilter::Set(const AudioFormat &_out_audio_format, Error &error) +{ + assert(in_audio_format.IsValid()); + assert(_out_audio_format.IsValid()); + + if (_out_audio_format == out_audio_format) + /* no change */ + return true; + + if (out_audio_format.IsValid()) { + out_audio_format.Clear(); + state->Close(); + } + + if (_out_audio_format == in_audio_format) + /* optimized special case: no-op */ + return true; + + if (!state->Open(in_audio_format, _out_audio_format, error)) + return false; + + out_audio_format = _out_audio_format; + return true; +} + +AudioFormat +ConvertFilter::Open(AudioFormat &audio_format, gcc_unused Error &error) +{ + assert(audio_format.IsValid()); + + in_audio_format = audio_format; + out_audio_format.Clear(); + + state.Construct(); + + return in_audio_format; +} + +void +ConvertFilter::Close() +{ + assert(in_audio_format.IsValid()); + + if (out_audio_format.IsValid()) + state->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) +{ + assert(in_audio_format.IsValid()); + + if (!out_audio_format.IsValid()) { + /* optimized special case: no-op */ + *dest_size_r = src_size; + return src; + } + + return state->Convert(src, src_size, dest_size_r, + error); +} + +const struct filter_plugin convert_filter_plugin = { + "convert", + convert_filter_init, +}; + +bool +convert_filter_set(Filter *_filter, AudioFormat out_audio_format, + Error &error) +{ + ConvertFilter *filter = (ConvertFilter *)_filter; + + return filter->Set(out_audio_format, error); +} diff --git a/src/filter/plugins/ConvertFilterPlugin.hxx b/src/filter/plugins/ConvertFilterPlugin.hxx new file mode 100644 index 000000000..bb4673651 --- /dev/null +++ b/src/filter/plugins/ConvertFilterPlugin.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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; +class Error; +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. + */ +bool +convert_filter_set(Filter *filter, AudioFormat out_audio_format, + Error &error); + +#endif diff --git a/src/filter/plugins/NormalizeFilterPlugin.cxx b/src/filter/plugins/NormalizeFilterPlugin.cxx new file mode 100644 index 000000000..58eb0c6a2 --- /dev/null +++ b/src/filter/plugins/NormalizeFilterPlugin.cxx @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/FilterPlugin.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/FilterRegistry.hxx" +#include "pcm/PcmBuffer.hxx" +#include "AudioFormat.hxx" +#include "AudioCompress/compress.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/plugins/NullFilterPlugin.cxx b/src/filter/plugins/NullFilterPlugin.cxx new file mode 100644 index 000000000..f79aa19f7 --- /dev/null +++ b/src/filter/plugins/NullFilterPlugin.cxx @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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/FilterPlugin.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/FilterRegistry.hxx" +#include "AudioFormat.hxx" +#include "Compiler.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/plugins/ReplayGainFilterPlugin.cxx b/src/filter/plugins/ReplayGainFilterPlugin.cxx new file mode 100644 index 000000000..a5d9668cc --- /dev/null +++ b/src/filter/plugins/ReplayGainFilterPlugin.cxx @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "filter/FilterPlugin.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/FilterRegistry.hxx" +#include "AudioFormat.hxx" +#include "ReplayGainInfo.hxx" +#include "ReplayGainConfig.hxx" +#include "mixer/MixerControl.hxx" +#include "pcm/Volume.hxx" +#include "pcm/PcmBuffer.hxx" +#include "util/ConstBuffer.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; + + ReplayGainMode mode; + + ReplayGainInfo info; + + /** + * About the current volume: it is 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. + */ + PcmVolume pv; + +public: + ReplayGainFilter() + :mixer(nullptr), mode(REPLAY_GAIN_OFF) { + info.Clear(); + } + + void SetMixer(Mixer *_mixer, unsigned _base) { + assert(_mixer == nullptr || (_base > 0 && _base <= 100)); + + mixer = _mixer; + base = _base; + + Update(); + } + + void SetInfo(const ReplayGainInfo *_info) { + if (_info != nullptr) { + info = *_info; + info.Complete(); + } else + info.Clear(); + + Update(); + } + + void SetMode(ReplayGainMode _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() +{ + unsigned volume = PCM_VOLUME_1; + if (mode != REPLAY_GAIN_OFF) { + const auto &tuple = info.tuples[mode]; + float scale = tuple.CalculateScale(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); + } + + pv.SetVolume(volume); + + if (mixer != nullptr) { + /* 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) +{ + if (!pv.Open(af.format, error)) + return AudioFormat::Undefined(); + + return af; +} + +void +ReplayGainFilter::Close() +{ + pv.Close(); +} + +const void * +ReplayGainFilter::FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, gcc_unused Error &error) +{ + const auto dest = pv.Apply({src, src_size}); + *dest_size_r = dest.size; + return dest.data; +} + +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 ReplayGainInfo *info) +{ + ReplayGainFilter *filter = (ReplayGainFilter *)_filter; + + filter->SetInfo(info); +} + +void +replay_gain_filter_set_mode(Filter *_filter, ReplayGainMode mode) +{ + ReplayGainFilter *filter = (ReplayGainFilter *)_filter; + + filter->SetMode(mode); +} diff --git a/src/filter/plugins/ReplayGainFilterPlugin.hxx b/src/filter/plugins/ReplayGainFilterPlugin.hxx new file mode 100644 index 000000000..346541b97 --- /dev/null +++ b/src/filter/plugins/ReplayGainFilterPlugin.hxx @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 nullptr 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 nullptr if no replay + * gain data is available for the current song + */ +void +replay_gain_filter_set_info(Filter *filter, const ReplayGainInfo *info); + +void +replay_gain_filter_set_mode(Filter *filter, ReplayGainMode mode); + +#endif diff --git a/src/filter/plugins/RouteFilterPlugin.cxx b/src/filter/plugins/RouteFilterPlugin.cxx new file mode 100644 index 000000000..38c4ec43b --- /dev/null +++ b/src/filter/plugins/RouteFilterPlugin.cxx @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "config/ConfigError.hxx" +#include "config/ConfigData.hxx" +#include "AudioFormat.hxx" +#include "filter/FilterPlugin.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/FilterRegistry.hxx" +#include "pcm/PcmBuffer.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" + +#include <algorithm> + +#include <assert.h> +#include <string.h> +#include <stdint.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 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 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" + */ + int8_t sources[MAX_CHANNELS]; + + /** + * 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: + /** + * 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 realloc() instead of one count run and one malloc(). + */ + + std::fill_n(sources, MAX_CHANNELS, -1); + + min_input_channels = 0; + min_output_channels = 0; + + // A cowardly default, just passthrough stereo + const char *routes = param.GetBlockValue("routes", "0>0, 1>1"); + while (true) { + routes = strchug_fast(routes); + + char *endptr; + const unsigned source = strtoul(routes, &endptr, 10); + endptr = strchug_fast(endptr); + if (endptr == routes || *endptr != '>') { + error.Set(config_domain, + "Malformed 'routes' specification"); + return false; + } + + if (source >= MAX_CHANNELS) { + error.Format(config_domain, + "Invalid source channel number: %u", + source); + return false; + } + + if (source >= min_input_channels) + min_input_channels = source + 1; + + routes = strchug_fast(endptr + 1); + + unsigned dest = strtoul(routes, &endptr, 10); + endptr = strchug_fast(endptr); + if (endptr == routes) { + error.Set(config_domain, + "Malformed 'routes' specification"); + return false; + } + + if (dest >= MAX_CHANNELS) { + error.Format(config_domain, + "Invalid destination channel number: %u", + dest); + return false; + } + + if (dest >= min_output_channels) + min_output_channels = dest + 1; + + sources[dest] = source; + + routes = endptr; + + if (*routes == 0) + break; + + if (*routes != ',') { + error.Set(config_domain, + "Malformed 'routes' specification"); + return false; + } + + ++routes; + } + + 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/plugins/VolumeFilterPlugin.cxx b/src/filter/plugins/VolumeFilterPlugin.cxx new file mode 100644 index 000000000..c9b7aa89e --- /dev/null +++ b/src/filter/plugins/VolumeFilterPlugin.cxx @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "filter/FilterPlugin.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/FilterRegistry.hxx" +#include "pcm/Volume.hxx" +#include "AudioFormat.hxx" +#include "util/ConstBuffer.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <assert.h> +#include <string.h> + +class VolumeFilter final : public Filter { + PcmVolume pv; + +public: + unsigned GetVolume() const { + return pv.GetVolume(); + } + + void SetVolume(unsigned _volume) { + pv.SetVolume(_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, Error &error) +{ + if (!pv.Open(audio_format.format, error)) + return AudioFormat::Undefined(); + + return audio_format; +} + +void +VolumeFilter::Close() +{ + pv.Close(); +} + +const void * +VolumeFilter::FilterPCM(const void *src, size_t src_size, + size_t *dest_size_r, gcc_unused Error &error) +{ + const auto dest = pv.Apply({src, src_size}); + *dest_size_r = dest.size; + return dest.data; +} + +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/plugins/VolumeFilterPlugin.hxx b/src/filter/plugins/VolumeFilterPlugin.hxx new file mode 100644 index 000000000..b5317dc6f --- /dev/null +++ b/src/filter/plugins/VolumeFilterPlugin.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/fs/AllocatedPath.cxx b/src/fs/AllocatedPath.cxx index 37b79a685..6770fa1b0 100644 --- a/src/fs/AllocatedPath.cxx +++ b/src/fs/AllocatedPath.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,7 +26,6 @@ #include <glib.h> -#include <assert.h> #include <string.h> inline AllocatedPath::AllocatedPath(Donate, pointer _value) @@ -38,12 +37,6 @@ inline AllocatedPath::AllocatedPath(Donate, pointer _value) AllocatedPath::~AllocatedPath() {} AllocatedPath -AllocatedPath::Build(const_pointer a, const_pointer b) -{ - return AllocatedPath(Donate(), g_build_filename(a, b, nullptr)); -} - -AllocatedPath AllocatedPath::FromUTF8(const char *path_utf8) { return AllocatedPath(Donate(), ::PathFromUTF8(path_utf8)); @@ -64,7 +57,7 @@ AllocatedPath::FromUTF8(const char *path_utf8, Error &error) AllocatedPath AllocatedPath::GetDirectoryName() const { - return AllocatedPath(Donate(), g_path_get_dirname(c_str())); + return FromFS(PathTraitsFS::GetParent(c_str())); } std::string @@ -82,14 +75,14 @@ AllocatedPath::RelativeFS(const char *other_fs) const other_fs += l; if (*other_fs != 0) { - if (!PathTraits::IsSeparatorFS(*other_fs)) + if (!PathTraitsFS::IsSeparator(*other_fs)) /* mismatch */ return nullptr; /* skip remaining path separators */ do { ++other_fs; - } while (PathTraits::IsSeparatorFS(*other_fs)); + } while (PathTraitsFS::IsSeparator(*other_fs)); } return other_fs; @@ -101,7 +94,7 @@ AllocatedPath::ChopSeparators() size_t l = length(); const char *p = data(); - while (l >= 2 && PathTraits::IsSeparatorFS(p[l - 1])) { + while (l >= 2 && PathTraitsFS::IsSeparator(p[l - 1])) { --l; #if GCC_CHECK_VERSION(4,7) && !defined(__clang__) diff --git a/src/fs/AllocatedPath.hxx b/src/fs/AllocatedPath.hxx index 36d8a1598..68a1a4766 100644 --- a/src/fs/AllocatedPath.hxx +++ b/src/fs/AllocatedPath.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,8 +28,6 @@ #include <utility> #include <string> -#include <assert.h> - class Error; /** @@ -39,11 +37,10 @@ class Error; * stored. */ class AllocatedPath { - typedef std::string string; - - typedef PathTraits::value_type value_type; - typedef PathTraits::pointer pointer; - typedef PathTraits::const_pointer const_pointer; + typedef PathTraitsFS::string string; + typedef PathTraitsFS::value_type value_type; + typedef PathTraitsFS::pointer pointer; + typedef PathTraitsFS::const_pointer const_pointer; string value; @@ -56,6 +53,12 @@ class AllocatedPath { AllocatedPath(const_pointer _value):value(_value) {} + AllocatedPath(string &&_value):value(std::move(_value)) {} + + static AllocatedPath Build(const_pointer a, size_t a_size, + const_pointer b, size_t b_size) { + return AllocatedPath(PathTraitsFS::Build(a, a_size, b, b_size)); + } public: /** * Copy a #AllocatedPath object. @@ -89,22 +92,28 @@ public: * Join two path components with the path separator. */ gcc_pure gcc_nonnull_all - static AllocatedPath Build(const_pointer a, const_pointer b); + static AllocatedPath Build(const_pointer a, const_pointer b) { + return Build(a, PathTraitsFS::GetLength(a), + b, PathTraitsFS::GetLength(b)); + } gcc_pure gcc_nonnull_all static AllocatedPath Build(const_pointer a, const AllocatedPath &b) { - return Build(a, b.c_str()); + return Build(a, PathTraitsFS::GetLength(a), + b.value.c_str(), b.value.size()); } gcc_pure gcc_nonnull_all static AllocatedPath Build(const AllocatedPath &a, const_pointer b) { - return Build(a.c_str(), b); + return Build(a.value.c_str(), a.value.size(), + b, PathTraitsFS::GetLength(b)); } gcc_pure static AllocatedPath Build(const AllocatedPath &a, const AllocatedPath &b) { - return Build(a.c_str(), b.c_str()); + return Build(a.value.c_str(), a.value.size(), + b.value.c_str(), b.value.size()); } /** @@ -117,6 +126,15 @@ public: } /** + * Convert a C++ string that is already in the filesystem + * character set to a #Path instance. + */ + gcc_pure + static AllocatedPath FromFS(string &&fs) { + return AllocatedPath(std::move(fs)); + } + + /** * Convert a UTF-8 C string to a #AllocatedPath instance. * Returns return a "nulled" instance on error. */ @@ -215,7 +233,7 @@ public: gcc_pure bool IsAbsolute() { - return PathTraits::IsAbsoluteFS(c_str()); + return PathTraitsFS::IsAbsolute(c_str()); } }; diff --git a/src/fs/Charset.cxx b/src/fs/Charset.cxx index dad5779f9..05d644b89 100644 --- a/src/fs/Charset.cxx +++ b/src/fs/Charset.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,12 +22,13 @@ #include "Domain.hxx" #include "Limits.hxx" #include "system/FatalError.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" #include "Log.hxx" +#include "Traits.hxx" #include <glib.h> +#include <algorithm> + #include <assert.h> #include <string.h> @@ -76,13 +77,29 @@ GetFSCharset() return fs_charset.empty() ? "utf-8" : fs_charset.c_str(); } +static inline void FixSeparators(std::string &s) +{ +#ifdef WIN32 + // For whatever reason GCC can't convert constexpr to value reference. + // This leads to link errors when passing separators directly. + auto from = PathTraitsFS::SEPARATOR; + auto to = PathTraitsUTF8::SEPARATOR; + std::replace(s.begin(), s.end(), from, to); +#else + (void)s; +#endif +} + std::string PathToUTF8(const char *path_fs) { assert(path_fs != nullptr); - if (fs_charset.empty()) - return std::string(path_fs); + if (fs_charset.empty()) { + auto result = std::string(path_fs); + FixSeparators(result); + return result; + } GIConv conv = g_iconv_open("utf-8", fs_charset.c_str()); if (conv == reinterpret_cast<GIConv>(-1)) @@ -103,7 +120,9 @@ PathToUTF8(const char *path_fs) if (ret == static_cast<size_t>(-1) || in_left > 0) return std::string(); - return std::string(path_utf8, sizeof(path_utf8) - out_left); + auto result_path = std::string(path_utf8, sizeof(path_utf8) - out_left); + FixSeparators(result_path); + return result_path; } char * diff --git a/src/fs/Charset.hxx b/src/fs/Charset.hxx index a89cb0459..0a71d7c58 100644 --- a/src/fs/Charset.hxx +++ b/src/fs/Charset.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/fs/Config.cxx b/src/fs/Config.cxx index 63e64ef99..c9555bcc2 100644 --- a/src/fs/Config.cxx +++ b/src/fs/Config.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,16 +20,10 @@ #include "config.h" #include "Config.hxx" #include "Charset.hxx" -#include "Domain.hxx" -#include "ConfigGlobal.hxx" -#include "Log.hxx" -#include "Compiler.h" +#include "config/ConfigGlobal.hxx" #include <glib.h> -#include <assert.h> -#include <string.h> - #ifdef WIN32 #include <windows.h> // for GetACP() #include <stdio.h> // for sprintf() diff --git a/src/fs/Config.hxx b/src/fs/Config.hxx index 9d7035706..d4f1709f5 100644 --- a/src/fs/Config.hxx +++ b/src/fs/Config.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/fs/DirectoryReader.hxx b/src/fs/DirectoryReader.hxx index c9d2c04b8..566b608ec 100644 --- a/src/fs/DirectoryReader.hxx +++ b/src/fs/DirectoryReader.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/fs/Domain.cxx b/src/fs/Domain.cxx index 0877bca4c..4f3129219 100644 --- a/src/fs/Domain.cxx +++ b/src/fs/Domain.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/fs/Domain.hxx b/src/fs/Domain.hxx index b303570fc..1fd17b37f 100644 --- a/src/fs/Domain.hxx +++ b/src/fs/Domain.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/fs/FileSystem.cxx b/src/fs/FileSystem.cxx index 4cd9f33b2..4e7c87415 100644 --- a/src/fs/FileSystem.cxx +++ b/src/fs/FileSystem.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/fs/FileSystem.hxx b/src/fs/FileSystem.hxx index cb2f82d22..4dbb064cb 100644 --- a/src/fs/FileSystem.hxx +++ b/src/fs/FileSystem.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,7 +28,6 @@ #include <sys/stat.h> #include <unistd.h> -#include <assert.h> #include <stdio.h> class AllocatedPath; @@ -37,39 +36,39 @@ namespace FOpenMode { /** * Open mode for reading text files. */ - constexpr PathTraits::const_pointer ReadText = "r"; + constexpr PathTraitsFS::const_pointer ReadText = "r"; /** * Open mode for reading binary files. */ - constexpr PathTraits::const_pointer ReadBinary = "rb"; + constexpr PathTraitsFS::const_pointer ReadBinary = "rb"; /** * Open mode for writing text files. */ - constexpr PathTraits::const_pointer WriteText = "w"; + constexpr PathTraitsFS::const_pointer WriteText = "w"; /** * Open mode for writing binary files. */ - constexpr PathTraits::const_pointer WriteBinary = "wb"; + constexpr PathTraitsFS::const_pointer WriteBinary = "wb"; /** * Open mode for appending text files. */ - constexpr PathTraits::const_pointer AppendText = "a"; + constexpr PathTraitsFS::const_pointer AppendText = "a"; /** * Open mode for appending binary files. */ - constexpr PathTraits::const_pointer AppendBinary = "ab"; + constexpr PathTraitsFS::const_pointer AppendBinary = "ab"; } /** * Wrapper for fopen() that uses #Path names. */ static inline FILE * -FOpen(Path file, PathTraits::const_pointer mode) +FOpen(Path file, PathTraitsFS::const_pointer mode) { return fopen(file.c_str(), mode); } @@ -132,20 +131,28 @@ MakeFifo(Path path, mode_t mode) return mkfifo(path.c_str(), mode) == 0; } -#endif - /** * Wrapper for access() that uses #Path names. */ static inline bool CheckAccess(Path path, int mode) { + return access(path.c_str(), mode) == 0; +} + +#endif + +/** + * Checks is specified path exists and accessible. + */ +static inline bool +CheckAccess(Path path) +{ #ifdef WIN32 - (void)path; - (void)mode; - return true; + struct stat buf; + return StatFile(path, buf); #else - return access(path.c_str(), mode) == 0; + return CheckAccess(path, F_OK); #endif } diff --git a/src/fs/Limits.hxx b/src/fs/Limits.hxx index 480b08851..432897a69 100644 --- a/src/fs/Limits.hxx +++ b/src/fs/Limits.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/fs/Path.cxx b/src/fs/Path.cxx index 0ff0591fb..54ad1c3a8 100644 --- a/src/fs/Path.cxx +++ b/src/fs/Path.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -36,14 +36,14 @@ Path::RelativeFS(const char *other_fs) const other_fs += l; if (*other_fs != 0) { - if (!PathTraits::IsSeparatorFS(*other_fs)) + if (!PathTraitsFS::IsSeparator(*other_fs)) /* mismatch */ return nullptr; /* skip remaining path separators */ do { ++other_fs; - } while (PathTraits::IsSeparatorFS(*other_fs)); + } while (PathTraitsFS::IsSeparator(*other_fs)); } return other_fs; diff --git a/src/fs/Path.hxx b/src/fs/Path.hxx index 6ea954577..3c9dc806f 100644 --- a/src/fs/Path.hxx +++ b/src/fs/Path.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -29,8 +29,6 @@ #include <assert.h> #include <string.h> -class Error; - /** * A path name in the native file system character set. * @@ -38,9 +36,9 @@ class Error; * instance lives, the string must not be invalidated. */ class Path { - typedef PathTraits::value_type value_type; - typedef PathTraits::pointer pointer; - typedef PathTraits::const_pointer const_pointer; + typedef PathTraitsFS::value_type value_type; + typedef PathTraitsFS::pointer pointer; + typedef PathTraitsFS::const_pointer const_pointer; const char *value; @@ -141,7 +139,7 @@ public: gcc_pure bool IsAbsolute() { - return PathTraits::IsAbsoluteFS(c_str()); + return PathTraitsFS::IsAbsolute(c_str()); } }; diff --git a/src/fs/StandardDirectory.cxx b/src/fs/StandardDirectory.cxx new file mode 100644 index 000000000..4f0958995 --- /dev/null +++ b/src/fs/StandardDirectory.cxx @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +// Use X Desktop guidelines where applicable +#if !defined(__APPLE__) && !defined(WIN32) +#define USE_XDG +#endif + +#include "StandardDirectory.hxx" +#include "FileSystem.hxx" + +#include <array> + +#ifdef WIN32 +#include <windows.h> +#include <shlobj.h> +#else +#include <stdlib.h> +#include <sys/types.h> +#include <unistd.h> +#include <pwd.h> +#endif + +#ifdef USE_XDG +#include "util/CharUtil.hxx" +#include "util/StringUtil.hxx" +#include "TextFile.hxx" +#include <string.h> +#include <utility> +#endif + +#ifndef WIN32 +class PasswdEntry +{ +#if defined(HAVE_GETPWNAM_R) || defined(HAVE_GETPWUID_R) + std::array<char, 16 * 1024> buf; + passwd pw; +#endif + + passwd *result; +public: + PasswdEntry() : result(nullptr) { } + + bool ReadByName(const char *name) { +#ifdef HAVE_GETPWNAM_R + getpwnam_r(name, &pw, buf.data(), buf.size(), &result); +#else + result = getpwnam(name); +#endif + return result != nullptr; + } + + bool ReadByUid(uid_t uid) { +#ifdef HAVE_GETPWUID_R + getpwuid_r(uid, &pw, buf.data(), buf.size(), &result); +#else + result = getpwuid(uid); +#endif + return result != nullptr; + } + + const passwd *operator->() { + assert(result != nullptr); + return result; + } +}; +#endif + +static inline bool IsValidPathString(PathTraitsFS::const_pointer path) +{ + return path != nullptr && *path != '\0'; +} + +static inline bool IsValidDir(PathTraitsFS::const_pointer dir) +{ + return PathTraitsFS::IsAbsolute(dir) && + DirectoryExists(Path::FromFS(dir)); +} + +static inline AllocatedPath SafePathFromFS(PathTraitsFS::const_pointer dir) +{ + if (IsValidPathString(dir) && IsValidDir(dir)) + return AllocatedPath::FromFS(dir); + return AllocatedPath::Null(); +} + +#ifdef WIN32 +static AllocatedPath GetStandardDir(int folder_id) +{ + std::array<char, MAX_PATH> dir; + auto ret = SHGetFolderPath(nullptr, folder_id | CSIDL_FLAG_DONT_VERIFY, + nullptr, SHGFP_TYPE_CURRENT, dir.data()); + if (FAILED(ret)) + return AllocatedPath::Null(); + return SafePathFromFS(dir.data()); +} +#endif + +#ifdef USE_XDG + +static const char home_prefix[] = "$HOME/"; + +static bool ParseConfigLine(const char *line, const char *dir_name, + AllocatedPath &result_dir) +{ + // strip leading white space + line = strchug_fast(line); + + // check for end-of-line or comment + if (*line == '\0' || *line == '#') + return false; + + // check if current setting is for requested dir + if (!StringStartsWith(line, dir_name)) + return false; + line += strlen(dir_name); + + // strip equals sign and spaces around it + line = strchug_fast(line); + if (*line != '=') + return false; + ++line; + line = strchug_fast(line); + + // check if path is quoted + bool quoted = false; + if (*line == '"') { + ++line; + quoted = true; + } + + // check if path is relative to $HOME + bool home_relative = false; + if (StringStartsWith(line, home_prefix)) { + line += strlen(home_prefix); + home_relative = true; + } + + + const char *line_end; + // find end of the string + if (quoted) { + line_end = strrchr(line, '"'); + if (line_end == nullptr) + return true; + } else { + line_end = line + strlen(line); + while (line < line_end && IsWhitespaceNotNull(line_end[-1])) + --line_end; + } + + // check for empty result + if (line == line_end) + return true; + + // build the result path + std::string path(line, line_end); + + auto result = AllocatedPath::Null(); + if (home_relative) { + auto home = GetHomeDir(); + if (home.IsNull()) + return true; + result = AllocatedPath::Build(home, path.c_str()); + } else { + result = AllocatedPath::FromFS(std::move(path)); + } + + if (IsValidDir(result.c_str())) { + result_dir = std::move(result); + return true; + } + return true; +} + +static AllocatedPath GetUserDir(const char *name) +{ + auto result = AllocatedPath::Null(); + auto config_dir = GetUserConfigDir(); + if (config_dir.IsNull()) + return result; + auto dirs_file = AllocatedPath::Build(config_dir, "user-dirs.dirs"); + TextFile input(dirs_file); + if (input.HasFailed()) + return result; + const char *line; + while ((line = input.ReadLine()) != nullptr) + if (ParseConfigLine(line, name, result)) + return result; + return result; +} + +#endif + +AllocatedPath GetUserConfigDir() +{ +#if defined(WIN32) + return GetStandardDir(CSIDL_LOCAL_APPDATA); +#elif defined(USE_XDG) + // Check for $XDG_CONFIG_HOME + auto config_home = getenv("XDG_CONFIG_HOME"); + if (IsValidPathString(config_home) && IsValidDir(config_home)) + return AllocatedPath::FromFS(config_home); + + // Check for $HOME/.config + auto home = GetHomeDir(); + if (!home.IsNull()) { + AllocatedPath fallback = AllocatedPath::Build(home, ".config"); + if (IsValidDir(fallback.c_str())) + return fallback; + } + + return AllocatedPath::Null(); +#else + return AllocatedPath::Null(); +#endif +} + +AllocatedPath GetUserMusicDir() +{ +#if defined(WIN32) + return GetStandardDir(CSIDL_MYMUSIC); +#elif defined(USE_XDG) + return GetUserDir("XDG_MUSIC_DIR"); +#else + return AllocatedPath::Null(); +#endif +} + +#ifdef WIN32 + +AllocatedPath GetSystemConfigDir() +{ + return GetStandardDir(CSIDL_COMMON_APPDATA); +} + +AllocatedPath GetAppBaseDir() +{ + std::array<char, MAX_PATH> app; + auto ret = GetModuleFileName(nullptr, app.data(), app.size()); + + // Check for error + if (ret == 0) + return AllocatedPath::Null(); + + // Check for truncation + if (ret == app.size() && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + return AllocatedPath::Null(); + + auto app_path = AllocatedPath::FromFS(app.data()); + return app_path.GetDirectoryName().GetDirectoryName(); +} + +#else + +AllocatedPath GetHomeDir() +{ + auto home = getenv("HOME"); + if (IsValidPathString(home) && IsValidDir(home)) + return AllocatedPath::FromFS(home); + PasswdEntry pw; + if (pw.ReadByUid(getuid())) + return SafePathFromFS(pw->pw_dir); + return AllocatedPath::Null(); +} + +AllocatedPath GetHomeDir(const char *user_name) +{ + assert(user_name != nullptr); + PasswdEntry pw; + if (pw.ReadByName(user_name)) + return SafePathFromFS(pw->pw_dir); + return AllocatedPath::Null(); +} + +#endif diff --git a/src/fs/StandardDirectory.hxx b/src/fs/StandardDirectory.hxx new file mode 100644 index 000000000..dca35b486 --- /dev/null +++ b/src/fs/StandardDirectory.hxx @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_STANDARD_DIRECTORY_HXX +#define MPD_FS_STANDARD_DIRECTORY_HXX + +#include "check.h" +#include "AllocatedPath.hxx" + +/** + * Obtains configuration directory for the current user. + */ +AllocatedPath GetUserConfigDir(); + +/** + * Obtains music directory for the current user. + */ +AllocatedPath GetUserMusicDir(); + +#ifdef WIN32 + +/** + * Obtains system configuration directory. + */ +AllocatedPath GetSystemConfigDir(); + +/** + * Obtains application application base directory. + * Application base directory is a directory that contains 'bin' folder + * for current executable. + */ +AllocatedPath GetAppBaseDir(); + +#else + +/** + * Obtains home directory for the current user. + */ +AllocatedPath GetHomeDir(); + +/** + * Obtains home directory for the specified user. + */ +AllocatedPath GetHomeDir(const char *user_name); + +#endif + +#endif diff --git a/src/fs/TextFile.cxx b/src/fs/TextFile.cxx new file mode 100644 index 000000000..21bd8ee8f --- /dev/null +++ b/src/fs/TextFile.cxx @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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(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 != nullptr); + assert(buffer != nullptr); + assert(buffer->allocated_len >= step); + + while (buffer->len < max_length) { + p = fgets(buffer->str + length, + buffer->allocated_len - length, file); + if (p == nullptr) { + if (length == 0 || ferror(file)) + return nullptr; + 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/fs/TextFile.hxx b/src/fs/TextFile.hxx new file mode 100644 index 000000000..b4b850c0f --- /dev/null +++ b/src/fs/TextFile.hxx @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.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(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 nullptr on end-of-file or error + */ + char *ReadLine(); +}; + +#endif diff --git a/src/fs/Traits.cxx b/src/fs/Traits.cxx index 2c3ce075b..a84c745a1 100644 --- a/src/fs/Traits.cxx +++ b/src/fs/Traits.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,24 +22,96 @@ #include <string.h> -const char * -PathTraits::GetBaseUTF8(const char *p) +template<typename Traits> +typename Traits::string +BuildPathImpl(typename Traits::const_pointer a, size_t a_size, + typename Traits::const_pointer b, size_t b_size) +{ + assert(a != nullptr); + assert(b != nullptr); + + if (a_size == 0) + return typename Traits::string(b, b_size); + if (b_size == 0) + return typename Traits::string(a, a_size); + + typename Traits::string result(a, a_size); + + if (!Traits::IsSeparator(a[a_size - 1])) + result.push_back(Traits::SEPARATOR); + + if (Traits::IsSeparator(b[0])) + result.append(b + 1, b_size - 1); + else + result.append(b, b_size); + + return result; +} + +template<typename Traits> +typename Traits::const_pointer +GetBasePathImpl(typename Traits::const_pointer p) { assert(p != nullptr); - const char *slash = strrchr(p, SEPARATOR_UTF8); - return slash != nullptr - ? slash + 1 + typename Traits::const_pointer sep = Traits::FindLastSeparator(p); + return sep != nullptr + ? sep + 1 : p; } -std::string -PathTraits::GetParentUTF8(const char *p) +template<typename Traits> +typename Traits::string +GetParentPathImpl(typename Traits::const_pointer p) { assert(p != nullptr); - const char *slash = strrchr(p, SEPARATOR_UTF8); - return slash != nullptr - ? std::string(p, slash) - : std::string("."); + typename Traits::const_pointer sep = Traits::FindLastSeparator(p); + if (sep == nullptr) + return typename Traits::string("."); + if (sep == p) + return typename Traits::string(p, p + 1); +#ifdef WIN32 + if (Traits::IsDrive(p) && sep == p + 2) + return typename Traits::string(p, p + 3); +#endif + return typename Traits::string(p, sep); +} + +PathTraitsFS::string +PathTraitsFS::Build(PathTraitsFS::const_pointer a, size_t a_size, + PathTraitsFS::const_pointer b, size_t b_size) +{ + return BuildPathImpl<PathTraitsFS>(a, a_size, b, b_size); +} + +PathTraitsFS::const_pointer +PathTraitsFS::GetBase(PathTraitsFS::const_pointer p) +{ + return GetBasePathImpl<PathTraitsFS>(p); +} + +PathTraitsFS::string +PathTraitsFS::GetParent(PathTraitsFS::const_pointer p) +{ + return GetParentPathImpl<PathTraitsFS>(p); +} + +PathTraitsUTF8::string +PathTraitsUTF8::Build(PathTraitsUTF8::const_pointer a, size_t a_size, + PathTraitsUTF8::const_pointer b, size_t b_size) +{ + return BuildPathImpl<PathTraitsUTF8>(a, a_size, b, b_size); +} + +PathTraitsUTF8::const_pointer +PathTraitsUTF8::GetBase(PathTraitsUTF8::const_pointer p) +{ + return GetBasePathImpl<PathTraitsUTF8>(p); +} + +PathTraitsUTF8::string +PathTraitsUTF8::GetParent(PathTraitsUTF8::const_pointer p) +{ + return GetParentPathImpl<PathTraitsUTF8>(p); } diff --git a/src/fs/Traits.hxx b/src/fs/Traits.hxx index 244ab8b5c..323656991 100644 --- a/src/fs/Traits.hxx +++ b/src/fs/Traits.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,67 +24,144 @@ #include "Compiler.h" #ifdef WIN32 -#include <glib.h> +#include "util/CharUtil.hxx" #endif #include <string> +#include <string.h> #include <assert.h> -class Error; - /** - * This class describes the nature of a filesystem path. + * This class describes the nature of a native filesystem path. */ -struct PathTraits { +struct PathTraitsFS { + typedef std::string string; typedef char value_type; typedef char *pointer; typedef const char *const_pointer; #ifdef WIN32 - static constexpr value_type SEPARATOR_FS = '\\'; - static constexpr char SEPARATOR_UTF8 = '/'; + static constexpr value_type SEPARATOR = '\\'; #else - static constexpr value_type SEPARATOR_FS = '/'; - static constexpr char SEPARATOR_UTF8 = '/'; + static constexpr value_type SEPARATOR = '/'; #endif - static constexpr bool IsSeparatorFS(value_type ch) { + static constexpr bool IsSeparator(value_type ch) { return #ifdef WIN32 ch == '/' || #endif - ch == SEPARATOR_FS; + ch == SEPARATOR; } - static constexpr bool IsSeparatorUTF8(char ch) { - return + gcc_pure gcc_nonnull_all + static const_pointer FindLastSeparator(const_pointer p) { + assert(p != nullptr); #ifdef WIN32 - ch == '/' || + const_pointer pos = p + GetLength(p); + while (p != pos && !IsSeparator(*pos)) + --pos; + return IsSeparator(*pos) ? pos : nullptr; +#else + return strrchr(p, SEPARATOR); #endif - ch == SEPARATOR_UTF8; } - gcc_pure - static bool IsAbsoluteFS(const_pointer p) { - assert(p != nullptr); +#ifdef WIN32 + gcc_pure gcc_nonnull_all + static constexpr bool IsDrive(const_pointer p) { + return IsAlphaASCII(p[0]) && p[1] == ':'; + } +#endif + gcc_pure gcc_nonnull_all + static bool IsAbsolute(const_pointer p) { + assert(p != nullptr); #ifdef WIN32 - return g_path_is_absolute(p); -#else - return IsSeparatorFS(*p); + if (IsDrive(p) && IsSeparator(p[2])) + return true; #endif + return IsSeparator(*p); + } + + gcc_pure gcc_nonnull_all + static size_t GetLength(const_pointer p) { + return strlen(p); } - gcc_pure - static bool IsAbsoluteUTF8(const char *p) { + /** + * Determine the "base" file name of the given native path. + * The return value points inside the given string. + */ + gcc_pure gcc_nonnull_all + static const_pointer GetBase(const_pointer p); + + /** + * Determine the "parent" file name of the given native path. + * As a special case, returns the string "." if there is no + * separator in the given input string. + */ + gcc_pure gcc_nonnull_all + static string GetParent(const_pointer p); + + /** + * Constructs the path from the given components. + * If either of the components is empty string, + * remaining component is returned unchanged. + * If both components are empty strings, empty string is returned. + */ + gcc_pure gcc_nonnull_all + static string Build(const_pointer a, size_t a_size, + const_pointer b, size_t b_size); + + gcc_pure gcc_nonnull_all + static string Build(const_pointer a, const_pointer b) { + return Build(a, GetLength(a), b, GetLength(b)); + } +}; + +/** + * This class describes the nature of a MPD internal filesystem path. + */ +struct PathTraitsUTF8 { + typedef std::string string; + typedef char value_type; + typedef char *pointer; + typedef const char *const_pointer; + + static constexpr value_type SEPARATOR = '/'; + + static constexpr bool IsSeparator(value_type ch) { + return ch == SEPARATOR; + } + + gcc_pure gcc_nonnull_all + static const_pointer FindLastSeparator(const_pointer p) { assert(p != nullptr); + return strrchr(p, SEPARATOR); + } #ifdef WIN32 - return g_path_is_absolute(p); -#else - return IsSeparatorUTF8(*p); + gcc_pure gcc_nonnull_all + static constexpr bool IsDrive(const_pointer p) { + return IsAlphaASCII(p[0]) && p[1] == ':'; + } +#endif + + gcc_pure gcc_nonnull_all + static bool IsAbsolute(const_pointer p) { + assert(p != nullptr); +#ifdef WIN32 + if (IsDrive(p) && IsSeparator(p[2])) + return true; #endif + return IsSeparator(*p); + } + + gcc_pure gcc_nonnull_all + static size_t GetLength(const_pointer p) { + return strlen(p); } /** @@ -92,7 +169,7 @@ struct PathTraits { * The return value points inside the given string. */ gcc_pure gcc_nonnull_all - static const char *GetBaseUTF8(const char *p); + static const_pointer GetBase(const_pointer p); /** * Determine the "parent" file name of the given UTF-8 path. @@ -100,7 +177,22 @@ struct PathTraits { * separator in the given input string. */ gcc_pure gcc_nonnull_all - static std::string GetParentUTF8(const char *p); + static string GetParent(const_pointer p); + + /** + * Constructs the path from the given components. + * If either of the components is empty string, + * remaining component is returned unchanged. + * If both components are empty strings, empty string is returned. + */ + gcc_pure gcc_nonnull_all + static string Build(const_pointer a, size_t a_size, + const_pointer b, size_t b_size); + + gcc_pure gcc_nonnull_all + static string Build(const_pointer a, const_pointer b) { + return Build(a, GetLength(a), b, GetLength(b)); + } }; #endif diff --git a/src/gerror.h b/src/gerror.h index fe4c54da9..797ad90a2 100644 --- a/src/gerror.h +++ b/src/gerror.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/input/ArchiveInputPlugin.cxx b/src/input/ArchiveInputPlugin.cxx deleted file mode 100644 index 5288f2b3b..000000000 --- a/src/input/ArchiveInputPlugin.cxx +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "ArchiveDomain.hxx" -#include "ArchiveLookup.hxx" -#include "ArchiveList.hxx" -#include "ArchivePlugin.hxx" -#include "ArchiveFile.hxx" -#include "InputPlugin.hxx" -#include "util/Error.hxx" -#include "fs/Traits.hxx" -#include "Log.hxx" - -#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 InputStream * -input_archive_open(const char *pathname, - Mutex &mutex, Cond &cond, - Error &error) -{ - const struct archive_plugin *arplug; - InputStream *is; - - if (!PathTraits::IsAbsoluteFS(pathname)) - return nullptr; - - char *pname = g_strdup(pathname); - // archive_lookup will modify pname when true is returned - const char *archive, *filename, *suffix; - if (!archive_lookup(pname, &archive, &filename, &suffix)) { - FormatDebug(archive_domain, - "not an archive, lookup %s failed", pname); - g_free(pname); - return nullptr; - } - - //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 nullptr; - } - - auto file = archive_file_open(arplug, archive, error); - if (file == nullptr) { - g_free(pname); - return nullptr; - } - - //setup fileops - is = file->OpenStream(filename, mutex, cond, error); - g_free(pname); - file->Close(); - - return is; -} - -const InputPlugin 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 deleted file mode 100644 index 9ac70b2fc..000000000 --- a/src/input/ArchiveInputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 InputPlugin input_plugin_archive; - -#endif diff --git a/src/input/CdioParanoiaInputPlugin.cxx b/src/input/CdioParanoiaInputPlugin.cxx deleted file mode 100644 index b3ac57413..000000000 --- a/src/input/CdioParanoiaInputPlugin.cxx +++ /dev/null @@ -1,406 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 "InputStream.hxx" -#include "InputPlugin.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "system/ByteOrder.hxx" -#include "fs/AllocatedPath.hxx" -#include "Log.hxx" -#include "ConfigData.hxx" -#include "ConfigError.hxx" - -#include <stdio.h> -#include <stdint.h> -#include <stddef.h> -#include <string.h> -#include <stdlib.h> -#include <glib.h> -#include <assert.h> - -#ifdef HAVE_CDIO_PARANOIA_PARANOIA_H -#include <cdio/paranoia/paranoia.h> -#else -#include <cdio/paranoia.h> -#endif - -#include <cdio/cd_types.h> - -struct CdioParanoiaInputStream { - InputStream 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 bool default_reverse_endian; - -static bool -input_cdio_init(const config_param ¶m, Error &error) -{ - const char *value = param.GetBlockValue("default_byte_order"); - if (value != nullptr) { - if (strcmp(value, "little_endian") == 0) - default_reverse_endian = IsBigEndian(); - else if (strcmp(value, "big_endian") == 0) - default_reverse_endian = IsLittleEndian(); - else { - error.Format(config_domain, 0, - "Unrecognized 'default_byte_order' setting: %s", - value); - return false; - } - } - - return true; -} - -static void -input_cdio_close(InputStream *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 AllocatedPath -cdio_detect_device(void) -{ - char **devices = cdio_get_devices_with_cap(nullptr, CDIO_FS_AUDIO, - false); - if (devices == nullptr) - return AllocatedPath::Null(); - - AllocatedPath path = AllocatedPath::FromFS(devices[0]); - cdio_free_device_list(devices); - return path; -} - -static InputStream * -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 */ - const AllocatedPath device = parsed_uri.device[0] != 0 - ? AllocatedPath::FromFS(parsed_uri.device) - : cdio_detect_device(); - if (device.IsNull()) { - 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.c_str(), DRIVER_UNKNOWN); - - 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 = default_reverse_endian; - break; - - case 0: - LogDebug(cdio_domain, "drive returns audio data Little Endian"); - reverse_endian = IsBigEndian(); - break; - - case 1: - LogDebug(cdio_domain, "drive returns audio data Big Endian"); - reverse_endian = IsLittleEndian(); - 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 = reverse_endian - ? "audio/x-mpd-cdda-pcm-reverse" - : "audio/x-mpd-cdda-pcm"; - - return &i->base; -} - -static bool -input_cdio_seek(InputStream *is, - InputPlugin::offset_type 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(InputStream *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(InputStream *is) -{ - CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is; - - return (cis->lsn_from + cis->lsn_relofs > cis->lsn_to); -} - -const InputPlugin input_plugin_cdio_paranoia = { - "cdio_paranoia", - input_cdio_init, - 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 deleted file mode 100644 index 847802a48..000000000 --- a/src/input/CdioParanoiaInputPlugin.hxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 InputPlugin input_plugin_cdio_paranoia; - -#endif diff --git a/src/input/CurlInputPlugin.cxx b/src/input/CurlInputPlugin.cxx deleted file mode 100644 index b78545951..000000000 --- a/src/input/CurlInputPlugin.cxx +++ /dev/null @@ -1,1166 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "InputStream.hxx" -#include "InputPlugin.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigData.hxx" -#include "tag/Tag.hxx" -#include "IcyMetaDataParser.hxx" -#include "event/SocketMonitor.hxx" -#include "event/TimeoutMonitor.hxx" -#include "event/Call.hxx" -#include "IOThread.hxx" -#include "util/ASCII.hxx" -#include "util/CharUtil.hxx" -#include "util/NumberParser.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 <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 { - InputStream base; - - /* some buffers which were passed to libcurl, which we have - too free */ - char range[32]; - 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 */ - std::string meta_name; - - /** the tag object ready to be requested via - InputStream::ReadTag() */ - Tag *tag; - - Error postponed_error; - - input_curl(const char *url, Mutex &mutex, Cond &cond) - :base(input_plugin_curl, url, mutex, cond), - request_headers(nullptr), - paused(false), - tag(nullptr) {} - - ~input_curl(); - - input_curl(const input_curl &) = delete; - input_curl &operator=(const input_curl &) = delete; -}; - -class CurlMulti; - -/** - * Monitor for one socket created by CURL. - */ -class CurlSocket final : SocketMonitor { - CurlMulti &multi; - -public: - CurlSocket(CurlMulti &_multi, EventLoop &_loop, int _fd) - :SocketMonitor(_fd, _loop), multi(_multi) {} - - ~CurlSocket() { - /* TODO: sometimes, CURL uses CURL_POLL_REMOVE after - closing the socket, and sometimes, it uses - CURL_POLL_REMOVE just to move the (still open) - connection to the pool; in the first case, - Abandon() would be most appropriate, but it breaks - the second case - is that a CURL bug? is there a - better solution? */ - - Steal(); - } - - /** - * Callback function for CURLMOPT_SOCKETFUNCTION. - */ - static int SocketFunction(CURL *easy, - curl_socket_t s, int action, - void *userp, void *socketp); - - virtual bool OnSocketReady(unsigned flags) override; - -private: - static constexpr int FlagsToCurlCSelect(unsigned flags) { - return (flags & (READ | HANGUP) ? CURL_CSELECT_IN : 0) | - (flags & WRITE ? CURL_CSELECT_OUT : 0) | - (flags & ERROR ? CURL_CSELECT_ERR : 0); - } - - gcc_const - static unsigned CurlPollToFlags(int action) { - switch (action) { - case CURL_POLL_NONE: - return 0; - - case CURL_POLL_IN: - return READ; - - case CURL_POLL_OUT: - return WRITE; - - case CURL_POLL_INOUT: - return READ|WRITE; - } - - assert(false); - gcc_unreachable(); - } -}; - -/** - * Manager for the global CURLM object. - */ -class CurlMulti final : private TimeoutMonitor { - CURLM *const multi; - -public: - CurlMulti(EventLoop &_loop, CURLM *_multi); - - ~CurlMulti() { - curl_multi_cleanup(multi); - } - - bool Add(input_curl *c, Error &error); - void Remove(input_curl *c); - - /** - * Check for finished HTTP responses. - * - * Runs in the I/O thread. The caller must not hold locks. - */ - void ReadInfo(); - - void Assign(curl_socket_t fd, CurlSocket &cs) { - curl_multi_assign(multi, fd, &cs); - } - - void SocketAction(curl_socket_t fd, int ev_bitmask); - - void InvalidateSockets() { - SocketAction(CURL_SOCKET_TIMEOUT, 0); - } - - /** - * This is a kludge to allow pausing/resuming a stream with - * libcurl < 7.32.0. Read the curl_easy_pause manpage for - * more information. - */ - void ResumeSockets() { - int running_handles; - curl_multi_socket_all(multi, &running_handles); - } - -private: - static int TimerFunction(CURLM *multi, long timeout_ms, void *userp); - - virtual void OnTimeout() override; -}; - -/** - * libcurl version number encoded in a 24 bit integer. - */ -static unsigned curl_version_num; - -/** 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 CurlMulti *curl_multi; - -static constexpr Domain http_domain("http"); -static constexpr Domain curl_domain("curl"); -static constexpr Domain curlm_domain("curlm"); - -CurlMulti::CurlMulti(EventLoop &_loop, CURLM *_multi) - :TimeoutMonitor(_loop), multi(_multi) -{ - curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, - CurlSocket::SocketFunction); - curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, this); - - curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, TimerFunction); - curl_multi_setopt(multi, CURLMOPT_TIMERDATA, this); -} - -/** - * Find a request by its CURL "easy" handle. - * - * Runs in the I/O thread. No lock needed. - */ -gcc_pure -static struct input_curl * -input_curl_find_request(CURL *easy) -{ - assert(io_thread_inside()); - - void *p; - CURLcode code = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &p); - if (code != CURLE_OK) - return nullptr; - - return (input_curl *)p; -} - -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); - - if (curl_version_num < 0x072000) - /* libcurl older than 7.32.0 does not update - its sockets after curl_easy_pause(); force - libcurl to do it now */ - curl_multi->ResumeSockets(); - - curl_multi->InvalidateSockets(); - } -} - -int -CurlSocket::SocketFunction(gcc_unused CURL *easy, - curl_socket_t s, int action, - void *userp, void *socketp) { - CurlMulti &multi = *(CurlMulti *)userp; - CurlSocket *cs = (CurlSocket *)socketp; - - assert(io_thread_inside()); - - if (action == CURL_POLL_REMOVE) { - delete cs; - return 0; - } - - if (cs == nullptr) { - cs = new CurlSocket(multi, io_thread_get(), s); - multi.Assign(s, *cs); - } else { -#ifdef USE_EPOLL - /* when using epoll, we need to unregister the socket - each time this callback is invoked, because older - CURL versions may omit the CURL_POLL_REMOVE call - when the socket has been closed and recreated with - the same file number (bug found in CURL 7.26, CURL - 7.33 not affected); in that case, epoll refuses the - EPOLL_CTL_MOD because it does not know the new - socket yet */ - cs->Cancel(); -#endif - } - - unsigned flags = CurlPollToFlags(action); - if (flags != 0) - cs->Schedule(flags); - return 0; -} - -bool -CurlSocket::OnSocketReady(unsigned flags) -{ - assert(io_thread_inside()); - - multi.SocketAction(Get(), FlagsToCurlCSelect(flags)); - return true; -} - -/** - * Runs in the I/O thread. No lock needed. - */ -inline bool -CurlMulti::Add(struct input_curl *c, Error &error) -{ - assert(io_thread_inside()); - assert(c != nullptr); - assert(c->easy != nullptr); - - CURLMcode mcode = curl_multi_add_handle(multi, c->easy); - if (mcode != CURLM_OK) { - error.Format(curlm_domain, mcode, - "curl_multi_add_handle() failed: %s", - curl_multi_strerror(mcode)); - return false; - } - - 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 != nullptr); - assert(c->easy != nullptr); - - bool result; - BlockingCall(io_thread_get(), [c, &error, &result](){ - result = curl_multi->Add(c, error); - }); - return result; -} - -inline void -CurlMulti::Remove(input_curl *c) -{ - curl_multi_remove_handle(multi, c->easy); -} - -/** - * 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 != nullptr); - - if (c->easy == nullptr) - return; - - curl_multi->Remove(c); - - curl_easy_cleanup(c->easy); - c->easy = nullptr; - - curl_slist_free_all(c->request_headers); - c->request_headers = nullptr; -} - -/** - * 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_multi->InvalidateSockets(); - }); - - assert(c->easy == nullptr); -} - -/** - * 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 != nullptr); - assert(c->easy == nullptr); - 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 != nullptr); - - long status = 0; - curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status); - - input_curl_easy_free(c); - input_curl_request_done(c, result, status); -} - -void -CurlMulti::SocketAction(curl_socket_t fd, int ev_bitmask) -{ - int running_handles; - CURLMcode mcode = curl_multi_socket_action(multi, fd, ev_bitmask, - &running_handles); - if (mcode != CURLM_OK) - FormatError(curlm_domain, - "curl_multi_socket_action() failed: %s", - curl_multi_strerror(mcode)); - - ReadInfo(); -} - -/** - * Check for finished HTTP responses. - * - * Runs in the I/O thread. The caller must not hold locks. - */ -inline void -CurlMulti::ReadInfo() -{ - assert(io_thread_inside()); - - CURLMsg *msg; - int msgs_in_queue; - - while ((msg = curl_multi_info_read(multi, - &msgs_in_queue)) != nullptr) { - if (msg->msg == CURLMSG_DONE) - input_curl_handle_done(msg->easy_handle, msg->data.result); - } -} - -int -CurlMulti::TimerFunction(gcc_unused CURLM *_multi, long timeout_ms, void *userp) -{ - CurlMulti &multi = *(CurlMulti *)userp; - assert(_multi == multi.multi); - - if (timeout_ms < 0) { - multi.Cancel(); - return 0; - } - - if (timeout_ms >= 0 && timeout_ms < 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. */ - timeout_ms = 10; - - multi.Schedule(timeout_ms); - return 0; -} - -void -CurlMulti::OnTimeout() -{ - SocketAction(CURL_SOCKET_TIMEOUT, 0); -} - -/* - * InputPlugin 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; - } - - const auto version_info = curl_version_info(CURLVERSION_FIRST); - if (version_info != nullptr) { - FormatDebug(curl_domain, "version %s", version_info->version); - if (version_info->features & CURL_VERSION_SSL) - FormatDebug(curl_domain, "with %s", - version_info->ssl_version); - - curl_version_num = version_info->version_num; - } - - 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 == nullptr) { - /* deprecated proxy configuration */ - proxy = config_get_string(CONF_HTTP_PROXY_HOST, nullptr); - proxy_port = config_get_positive(CONF_HTTP_PROXY_PORT, 0); - proxy_user = config_get_string(CONF_HTTP_PROXY_USER, nullptr); - proxy_password = config_get_string(CONF_HTTP_PROXY_PASSWORD, - ""); - } - - CURLM *multi = curl_multi_init(); - if (multi == nullptr) { - error.Set(curl_domain, 0, "curl_multi_init() failed"); - return false; - } - - curl_multi = new CurlMulti(io_thread_get(), multi); - return true; -} - -static void -input_curl_finish(void) -{ - BlockingCall(io_thread_get(), [](){ - delete 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; - - input_curl_easy_free_indirect(this); -} - -static bool -input_curl_check(InputStream *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(InputStream *is) -{ - struct input_curl *c = (struct input_curl *)is; - Tag *tag = c->tag; - - c->tag = nullptr; - return tag; -} - -static bool -fill_buffer(struct input_curl *c, Error &error) -{ - while (c->easy != nullptr && 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 == nullptr) - return; - - delete c->tag; - - if (!c->meta_name.empty() && !tag->HasType(TAG_NAME)) - tag->AddItem(TAG_NAME, c->meta_name.c_str()); - - c->tag = tag; -} - -static bool -input_curl_available(InputStream *is) -{ - struct input_curl *c = (struct input_curl *)is; - - return c->postponed_error.IsDefined() || c->easy == nullptr || - !c->buffers.empty(); -} - -static size_t -input_curl_read(InputStream *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 += (InputPlugin::offset_type)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(InputStream *is) -{ - struct input_curl *c = (struct input_curl *)is; - - delete c; -} - -static bool -input_curl_eof(gcc_unused InputStream *is) -{ - struct input_curl *c = (struct input_curl *)is; - - return c->easy == nullptr && 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 == nullptr || (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 && IsWhitespaceOrNull(*value)) - ++value; - - while (end > value && IsWhitespaceOrNull(end[-1])) - --end; - - if (StringEqualsCaseASCII(name, "accept-ranges")) { - /* a stream with icy-metadata is not seekable */ - if (!c->icy.IsDefined()) - c->base.seekable = true; - } else if (StringEqualsCaseASCII(name, "content-length")) { - 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 + ParseUint64(buffer); - } else if (StringEqualsCaseASCII(name, "content-type")) { - c->base.mime.assign(value, end); - } else if (StringEqualsCaseASCII(name, "icy-name") || - StringEqualsCaseASCII(name, "ice-name") || - StringEqualsCaseASCII(name, "x-audiocast-name")) { - c->meta_name.assign(value, end); - - delete c->tag; - - c->tag = new Tag(); - c->tag->AddItem(TAG_NAME, c->meta_name.c_str()); - } else if (StringEqualsCaseASCII(name, "icy-metaint")) { - 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 = ParseUint64(buffer); - 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 == nullptr) { - error.Set(curl_domain, "curl_easy_init() failed"); - return false; - } - - curl_easy_setopt(c->easy, CURLOPT_PRIVATE, (void *)c); - 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 != nullptr) - 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 != nullptr && proxy_password != nullptr) { - char proxy_auth_str[1024]; - snprintf(proxy_auth_str, sizeof(proxy_auth_str), - "%s:%s", - proxy_user, proxy_password); - curl_easy_setopt(c->easy, CURLOPT_PROXYUSERPWD, 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 = nullptr; - 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(InputStream *is, InputPlugin::offset_type 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 < (InputPlugin::offset_type)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) { - sprintf(c->range, "%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 InputStream * -input_curl_open(const char *url, Mutex &mutex, Cond &cond, - Error &error) -{ - if (memcmp(url, "http://", 7) != 0 && - memcmp(url, "https://", 8) != 0) - return nullptr; - - struct input_curl *c = new input_curl(url, mutex, cond); - - if (!input_curl_easy_init(c, error)) { - delete c; - return nullptr; - } - - if (!input_curl_easy_add_indirect(c, error)) { - delete c; - return nullptr; - } - - return &c->base; -} - -const struct InputPlugin 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 deleted file mode 100644 index 30e917257..000000000 --- a/src/input/CurlInputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 - -extern const struct InputPlugin input_plugin_curl; - -#endif diff --git a/src/input/DespotifyInputPlugin.cxx b/src/input/DespotifyInputPlugin.cxx deleted file mode 100644 index b08299516..000000000 --- a/src/input/DespotifyInputPlugin.cxx +++ /dev/null @@ -1,234 +0,0 @@ -/* - * 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 "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 { - InputStream 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 = "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 InputStream * -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 nullptr; - - session = mpd_despotify_get_session(); - if (!session) - return nullptr; - - ds_link = despotify_link_from_uri(url + 6); - if (!ds_link) { - FormatDebug(despotify_domain, "Can't find %s", url); - return nullptr; - } - if (ds_link->type != LINK_TYPE_TRACK) { - despotify_free_link(ds_link); - return nullptr; - } - - track = despotify_link_get_track(session, ds_link); - despotify_free_link(ds_link); - if (!track) - return nullptr; - - DespotifyInputStream *ctx = - new DespotifyInputStream(url, mutex, cond, - session, track); - - if (!mpd_despotify_register_callback(callback, ctx)) { - delete ctx; - return nullptr; - } - - if (despotify_play(ctx->session, ctx->track, false) == false) { - mpd_despotify_unregister_callback(callback); - delete ctx; - return nullptr; - } - - return &ctx->base; -} - -static size_t -input_despotify_read(InputStream *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(InputStream *is) -{ - DespotifyInputStream *ctx = (DespotifyInputStream *)is; - - mpd_despotify_unregister_callback(callback); - delete ctx; -} - -static bool -input_despotify_eof(InputStream *is) -{ - DespotifyInputStream *ctx = (DespotifyInputStream *)is; - - return ctx->eof; -} - -static Tag * -input_despotify_tag(InputStream *is) -{ - DespotifyInputStream *ctx = (DespotifyInputStream *)is; - Tag *tag = ctx->tag; - - ctx->tag = nullptr; - - return tag; -} - -const InputPlugin 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 deleted file mode 100644 index f1911f235..000000000 --- a/src/input/DespotifyInputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 InputPlugin input_plugin_despotify; - -#endif diff --git a/src/input/FfmpegInputPlugin.cxx b/src/input/FfmpegInputPlugin.cxx deleted file mode 100644 index 8f9cd0b86..000000000 --- a/src/input/FfmpegInputPlugin.cxx +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 "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> -} - -#include <glib.h> - -struct FfmpegInputStream { - InputStream 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 = "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 InputStream * -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(InputStream *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(InputStream *is) -{ - FfmpegInputStream *i = (FfmpegInputStream *)is; - - delete i; -} - -static bool -input_ffmpeg_eof(InputStream *is) -{ - FfmpegInputStream *i = (FfmpegInputStream *)is; - - return i->eof; -} - -static bool -input_ffmpeg_seek(InputStream *is, InputPlugin::offset_type 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 InputPlugin 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 deleted file mode 100644 index 9bc2eeaea..000000000 --- a/src/input/FfmpegInputPlugin.hxx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 InputPlugin input_plugin_ffmpeg; - -#endif diff --git a/src/input/FileInputPlugin.cxx b/src/input/FileInputPlugin.cxx deleted file mode 100644 index 26e40d609..000000000 --- a/src/input/FileInputPlugin.cxx +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with 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 "InputStream.hxx" -#include "InputPlugin.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "fs/Traits.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 { - InputStream 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 InputStream * -input_file_open(const char *filename, - Mutex &mutex, Cond &cond, - Error &error) -{ - int fd, ret; - struct stat st; - - if (!PathTraits::IsAbsoluteFS(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(InputStream *is, InputPlugin::offset_type offset, - int whence, - Error &error) -{ - FileInputStream *fis = (FileInputStream *)is; - - offset = (InputPlugin::offset_type)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(InputStream *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(InputStream *is) -{ - FileInputStream *fis = (FileInputStream *)is; - - delete fis; -} - -static bool -input_file_eof(InputStream *is) -{ - return is->offset >= is->size; -} - -const InputPlugin 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 deleted file mode 100644 index f76f4dd0e..000000000 --- a/src/input/FileInputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 InputPlugin input_plugin_file; - -#endif diff --git a/src/input/Init.cxx b/src/input/Init.cxx new file mode 100644 index 000000000..4b8eac320 --- /dev/null +++ b/src/input/Init.cxx @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Init.hxx" +#include "Registry.hxx" +#include "InputPlugin.hxx" +#include "util/Error.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" +#include "config/ConfigData.hxx" + +#include <assert.h> +#include <string.h> + +bool +input_stream_global_init(Error &error) +{ + const config_param empty; + + for (unsigned i = 0; input_plugins[i] != nullptr; ++i) { + const InputPlugin *plugin = input_plugins[i]; + + assert(plugin->name != nullptr); + assert(*plugin->name != 0); + assert(plugin->open != nullptr); + + const struct config_param *param = + config_find_block(CONF_INPUT, "plugin", plugin->name); + if (param == nullptr) { + param = ∅ + } else if (!param->GetBlockValue("enabled", true)) + /* the plugin is disabled in mpd.conf */ + continue; + + if (plugin->init == nullptr || 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 != nullptr) + plugin->finish(); +} diff --git a/src/input/Init.hxx b/src/input/Init.hxx new file mode 100644 index 000000000..875fdce7c --- /dev/null +++ b/src/input/Init.hxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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. + */ +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/input/InputPlugin.hxx b/src/input/InputPlugin.hxx new file mode 100644 index 000000000..83e0ab26e --- /dev/null +++ b/src/input/InputPlugin.hxx @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 <stddef.h> +#include <stdint.h> + +struct config_param; +struct InputStream; +class Error; +struct Tag; + +struct InputPlugin { + typedef int64_t offset_type; + + const char *name; + + /** + * Global initialization. This method is called when MPD starts. + * + * @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); + + InputStream *(*open)(const char *uri, + Mutex &mutex, Cond &cond, + Error &error); + void (*close)(InputStream *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)(InputStream *is, Error &error); + + /** + * Update the public attributes. Call before access. Can be + * nullptr if the plugin always keeps its attributes up to date. + */ + void (*update)(InputStream *is); + + Tag *(*tag)(InputStream *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)(InputStream *is); + + size_t (*read)(InputStream *is, void *ptr, size_t size, + Error &error); + bool (*eof)(InputStream *is); + bool (*seek)(InputStream *is, offset_type offset, int whence, + Error &error); +}; + +#endif diff --git a/src/input/InputStream.cxx b/src/input/InputStream.cxx new file mode 100644 index 000000000..0621437c4 --- /dev/null +++ b/src/input/InputStream.cxx @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "Registry.hxx" +#include "InputPlugin.hxx" +#include "plugins/RewindInputPlugin.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <assert.h> +#include <stdio.h> /* for SEEK_SET */ + +static constexpr Domain input_domain("input"); + +InputStream * +InputStream::Open(const char *url, + Mutex &mutex, Cond &cond, + Error &error) +{ + input_plugins_for_each_enabled(plugin) { + InputStream *is; + + is = plugin->open(url, mutex, cond, error); + if (is != nullptr) { + assert(is->plugin.close != nullptr); + assert(is->plugin.read != nullptr); + assert(is->plugin.eof != nullptr); + assert(!is->seekable || is->plugin.seek != nullptr); + + is = input_rewind_open(is); + + return is; + } else if (error.IsDefined()) + return nullptr; + } + + error.Set(input_domain, "Unrecognized URI"); + return nullptr; +} + +InputStream * +InputStream::OpenReady(const char *uri, + Mutex &mutex, Cond &cond, + Error &error) +{ + InputStream *is = Open(uri, mutex, cond, error); + if (is == nullptr) + return nullptr; + + mutex.lock(); + is->WaitReady(); + bool success = is->Check(error); + mutex.unlock(); + + if (!success) { + is->Close(); + is = nullptr; + } + + return is; +} + +bool +InputStream::Check(Error &error) +{ + return plugin.check == nullptr || plugin.check(this, error); +} + +void +InputStream::Update() +{ + if (plugin.update != nullptr) + plugin.update(this); +} + +void +InputStream::WaitReady() +{ + while (true) { + Update(); + if (ready) + break; + + cond.wait(mutex); + } +} + +void +InputStream::LockWaitReady() +{ + const ScopeLock protect(mutex); + WaitReady(); +} + +bool +InputStream::CheapSeeking() const +{ + return IsSeekable() && !uri_has_scheme(uri.c_str()); +} + +bool +InputStream::Seek(offset_type _offset, int whence, Error &error) +{ + if (plugin.seek == nullptr) + return false; + + return plugin.seek(this, _offset, whence, error); +} + +bool +InputStream::LockSeek(offset_type _offset, int whence, Error &error) +{ + if (plugin.seek == nullptr) + return false; + + const ScopeLock protect(mutex); + return Seek(_offset, whence, error); +} + +bool +InputStream::Rewind(Error &error) +{ + return Seek(0, SEEK_SET, error); +} + +bool +InputStream::LockRewind(Error &error) +{ + return LockSeek(0, SEEK_SET, error); +} + +Tag * +InputStream::ReadTag() +{ + return plugin.tag != nullptr + ? plugin.tag(this) + : nullptr; +} + +Tag * +InputStream::LockReadTag() +{ + if (plugin.tag == nullptr) + return nullptr; + + const ScopeLock protect(mutex); + return ReadTag(); +} + +bool +InputStream::IsAvailable() +{ + return plugin.available != nullptr + ? plugin.available(this) + : true; +} + +size_t +InputStream::Read(void *ptr, size_t _size, Error &error) +{ + assert(ptr != nullptr); + assert(_size > 0); + + return plugin.read(this, ptr, _size, error); +} + +size_t +InputStream::LockRead(void *ptr, size_t _size, Error &error) +{ + assert(ptr != nullptr); + assert(_size > 0); + + const ScopeLock protect(mutex); + return Read(ptr, _size, error); +} + +void +InputStream::Close() +{ + plugin.close(this); +} + +bool +InputStream::IsEOF() +{ + return plugin.eof(this); +} + +bool +InputStream::LockIsEOF() +{ + const ScopeLock protect(mutex); + return IsEOF(); +} + diff --git a/src/input/InputStream.hxx b/src/input/InputStream.hxx new file mode 100644 index 000000000..c66091687 --- /dev/null +++ b/src/input/InputStream.hxx @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.h" + +#include <string> + +#include <assert.h> +#include <stdint.h> + +class Cond; +class Error; +struct Tag; +struct InputPlugin; + +struct InputStream { + typedef int64_t offset_type; + + /** + * the plugin which implements this input stream + */ + const InputPlugin &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 nullptr. + * + * 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 + */ + offset_type size; + + /** + * the current offset within the stream + */ + offset_type offset; + + /** + * the MIME content type of the resource, or empty if unknown. + */ + std::string mime; + + InputStream(const InputPlugin &_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 != nullptr); + } + + /** + * 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 nullptr if the caller doesn't want to get + * notifications + * @return an #InputStream object on success, nullptr on error + */ + gcc_nonnull_all + gcc_malloc + static InputStream *Open(const char *uri, Mutex &mutex, Cond &cond, + Error &error); + + /** + * Just like Open(), but waits for the stream to become ready. + * It is a wrapper for Open(), WaitReady() and Check(). + */ + gcc_malloc gcc_nonnull_all + static InputStream *OpenReady(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 + offset_type GetSize() const { + assert(ready); + + return size; + } + + gcc_pure + offset_type 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(offset_type offset, int whence, Error &error); + + /** + * Wrapper for Seek() which locks and unlocks the mutex; the + * caller must not be holding it already. + */ + bool LockSeek(offset_type offset, int whence, Error &error); + + /** + * Rewind to the beginning of the stream. This is a wrapper + * for Seek(0, SEEK_SET, error). + */ + bool Rewind(Error &error); + bool LockRewind(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 InputStream 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/input/MmsInputPlugin.cxx b/src/input/MmsInputPlugin.cxx deleted file mode 100644 index e97c1eb3f..000000000 --- a/src/input/MmsInputPlugin.cxx +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "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 { - InputStream 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 = "audio/x-ms-wma"; - - base.ready = true; - } - - ~MmsInputStream() { - mmsx_close(mms); - } -}; - -static constexpr Domain mms_domain("mms"); - -static InputStream * -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(InputStream *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(InputStream *is) -{ - MmsInputStream *m = (MmsInputStream *)is; - - delete m; -} - -static bool -input_mms_eof(InputStream *is) -{ - MmsInputStream *m = (MmsInputStream *)is; - - return m->eof; -} - -const InputPlugin 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/MmsInputPlugin.hxx b/src/input/MmsInputPlugin.hxx deleted file mode 100644 index e3d3ba3c8..000000000 --- a/src/input/MmsInputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License 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_MMS_H -#define INPUT_MMS_H - -extern const struct InputPlugin input_plugin_mms; - -#endif diff --git a/src/input/Registry.cxx b/src/input/Registry.cxx new file mode 100644 index 000000000..30c6a67e0 --- /dev/null +++ b/src/input/Registry.cxx @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Registry.hxx" +#include "util/Macros.hxx" +#include "plugins/FileInputPlugin.hxx" + +#ifdef HAVE_ALSA +#include "plugins/AlsaInputPlugin.hxx" +#endif + +#ifdef ENABLE_ARCHIVE +#include "plugins/ArchiveInputPlugin.hxx" +#endif + +#ifdef ENABLE_CURL +#include "plugins/CurlInputPlugin.hxx" +#endif + +#ifdef HAVE_FFMPEG +#include "plugins/FfmpegInputPlugin.hxx" +#endif + +#ifdef ENABLE_SMBCLIENT +#include "plugins/SmbclientInputPlugin.hxx" +#endif + +#ifdef ENABLE_MMS +#include "plugins/MmsInputPlugin.hxx" +#endif + +#ifdef ENABLE_CDIO_PARANOIA +#include "plugins/CdioParanoiaInputPlugin.hxx" +#endif + +#ifdef ENABLE_DESPOTIFY +#include "plugins/DespotifyInputPlugin.hxx" +#endif + +const InputPlugin *const input_plugins[] = { + &input_plugin_file, +#ifdef HAVE_ALSA + &input_plugin_alsa, +#endif +#ifdef ENABLE_ARCHIVE + &input_plugin_archive, +#endif +#ifdef ENABLE_CURL + &input_plugin_curl, +#endif +#ifdef HAVE_FFMPEG + &input_plugin_ffmpeg, +#endif +#ifdef ENABLE_SMBCLIENT + &input_plugin_smbclient, +#endif +#ifdef ENABLE_MMS + &input_plugin_mms, +#endif +#ifdef ENABLE_CDIO_PARANOIA + &input_plugin_cdio_paranoia, +#endif +#ifdef ENABLE_DESPOTIFY + &input_plugin_despotify, +#endif + nullptr +}; + +bool input_plugins_enabled[ARRAY_SIZE(input_plugins) - 1]; diff --git a/src/input/Registry.hxx b/src/input/Registry.hxx new file mode 100644 index 000000000..1b81f8f06 --- /dev/null +++ b/src/input/Registry.hxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 InputPlugin *const input_plugins[]; + +extern bool input_plugins_enabled[]; + +#define input_plugins_for_each(plugin) \ + for (const InputPlugin *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/RewindInputPlugin.cxx b/src/input/RewindInputPlugin.cxx deleted file mode 100644 index e11f56631..000000000 --- a/src/input/RewindInputPlugin.cxx +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "InputStream.hxx" -#include "InputPlugin.hxx" -#include "tag/Tag.hxx" - -#include <assert.h> -#include <string.h> -#include <stdio.h> - -extern const InputPlugin rewind_input_plugin; - -struct RewindInputStream { - InputStream base; - - InputStream *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(InputStream *_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() { - InputStream *dest = &base; - const InputStream *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(InputStream *is) -{ - RewindInputStream *r = (RewindInputStream *)is; - - delete r; -} - -static bool -input_rewind_check(InputStream *is, Error &error) -{ - RewindInputStream *r = (RewindInputStream *)is; - - return r->input->Check(error); -} - -static void -input_rewind_update(InputStream *is) -{ - RewindInputStream *r = (RewindInputStream *)is; - - if (!r->ReadingFromBuffer()) - r->CopyAttributes(); -} - -static Tag * -input_rewind_tag(InputStream *is) -{ - RewindInputStream *r = (RewindInputStream *)is; - - return r->input->ReadTag(); -} - -static bool -input_rewind_available(InputStream *is) -{ - RewindInputStream *r = (RewindInputStream *)is; - - return r->input->IsAvailable(); -} - -static size_t -input_rewind_read(InputStream *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 > (InputPlugin::offset_type)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(InputStream *is) -{ - RewindInputStream *r = (RewindInputStream *)is; - - return !r->ReadingFromBuffer() && r->input->IsEOF(); -} - -static bool -input_rewind_seek(InputStream *is, InputPlugin::offset_type offset, - int whence, - Error &error) -{ - RewindInputStream *r = (RewindInputStream *)is; - - assert(is->ready); - - if (whence == SEEK_SET && r->tail > 0 && - offset <= (InputPlugin::offset_type)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 InputPlugin 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, -}; - -InputStream * -input_rewind_open(InputStream *is) -{ - assert(is != nullptr); - 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 deleted file mode 100644 index 2d461970a..000000000 --- a/src/input/RewindInputPlugin.hxx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 InputStream; - -InputStream * -input_rewind_open(InputStream *is); - -#endif diff --git a/src/input/TextInputStream.cxx b/src/input/TextInputStream.cxx new file mode 100644 index 000000000..25b9b42fe --- /dev/null +++ b/src/input/TextInputStream.cxx @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/CharUtil.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> + +bool TextInputStream::ReadLine(std::string &line) +{ + const char *src, *p; + + do { + size_t nbytes; + auto dest = buffer.Write(); + if (dest.size >= 2) { + /* reserve one byte for the null terminator if + the last line is not terminated by a + newline character */ + --dest.size; + + Error error; + nbytes = is.LockRead(dest.data, dest.size, error); + if (nbytes > 0) + buffer.Append(nbytes); + else if (error.IsDefined()) { + LogError(error); + return false; + } + } else + nbytes = 0; + + auto src_p = buffer.Read(); + if (src_p.IsEmpty()) + return false; + + src = src_p.data; + + p = reinterpret_cast<const char*>(memchr(src, '\n', src_p.size)); + if (p == nullptr && nbytes == 0) { + /* end of file (or line too long): terminate + the current line */ + dest = buffer.Write(); + assert(!dest.IsEmpty()); + dest.data[0] = '\n'; + buffer.Append(1); + } + } while (p == nullptr); + + size_t length = p - src + 1; + while (p > src && IsWhitespaceOrNull(p[-1])) + --p; + + line = std::string(src, p - src); + buffer.Consume(length); + return true; +} diff --git a/src/input/TextInputStream.hxx b/src/input/TextInputStream.hxx new file mode 100644 index 000000000..86ae16e41 --- /dev/null +++ b/src/input/TextInputStream.hxx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "util/FifoBuffer.hxx" + +#include <string> + +struct InputStream; + +class TextInputStream { + InputStream &is; + FifoBuffer<char, 4096> 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(InputStream &_is) + :is(_is) {} + + 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/input/plugins/AlsaInputPlugin.cxx b/src/input/plugins/AlsaInputPlugin.cxx new file mode 100644 index 000000000..da2d9a926 --- /dev/null +++ b/src/input/plugins/AlsaInputPlugin.cxx @@ -0,0 +1,431 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * ALSA code based on an example by Paul Davis released under GPL here: + * http://equalarea.com/paul/alsa-audio.html + * and one by Matthias Nagorni, also GPL, here: + * http://alsamodular.sourceforge.net/alsa_programming_howto.html + */ + +#include "config.h" +#include "AlsaInputPlugin.hxx" +#include "../InputPlugin.hxx" +#include "../InputStream.hxx" +#include "util/Domain.hxx" +#include "util/Error.hxx" +#include "util/StringUtil.hxx" +#include "util/ReusableArray.hxx" +#include "util/Cast.hxx" +#include "Log.hxx" +#include "event/MultiSocketMonitor.hxx" +#include "event/DeferredMonitor.hxx" +#include "event/Call.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "IOThread.hxx" + +#include <alsa/asoundlib.h> + +#include <assert.h> +#include <string.h> + +static constexpr Domain alsa_input_domain("alsa"); + +static constexpr const char *default_device = "hw:0,0"; + +// the following defaults are because the PcmDecoderPlugin forces CD format +static constexpr snd_pcm_format_t default_format = SND_PCM_FORMAT_S16; +static constexpr int default_channels = 2; // stereo +static constexpr unsigned int default_rate = 44100; // cd quality + +/** + * This value should be the same as the read buffer size defined in + * PcmDecoderPlugin.cxx:pcm_stream_decode(). + * We use it to calculate how many audio frames to buffer in the alsa driver + * before reading from the device. snd_pcm_readi() blocks until that many + * frames are ready. + */ +static constexpr size_t read_buffer_size = 4096; + +class AlsaInputStream final : MultiSocketMonitor, DeferredMonitor { + InputStream base; + snd_pcm_t *capture_handle; + size_t frame_size; + int frames_to_read; + bool eof; + + /** + * Is somebody waiting for data? This is set by method + * Available(). + */ + std::atomic_bool waiting; + + ReusableArray<pollfd> pfd_buffer; + +public: + AlsaInputStream(EventLoop &loop, + const char *uri, Mutex &mutex, Cond &cond, + snd_pcm_t *_handle, int _frame_size) + :MultiSocketMonitor(loop), + DeferredMonitor(loop), + base(input_plugin_alsa, uri, mutex, cond), + capture_handle(_handle), + frame_size(_frame_size), + eof(false) + { + assert(uri != nullptr); + assert(_handle != nullptr); + + /* this mime type forces use of the PcmDecoderPlugin. + Needs to be generalised when/if that decoder is + updated to support other audio formats */ + base.mime = "audio/x-mpd-cdda-pcm"; + base.seekable = false; + base.size = -1; + base.ready = true; + frames_to_read = read_buffer_size / frame_size; + + snd_pcm_start(capture_handle); + + DeferredMonitor::Schedule(); + } + + ~AlsaInputStream() { + snd_pcm_close(capture_handle); + } + + using DeferredMonitor::GetEventLoop; + + static InputStream *Create(const char *uri, Mutex &mutex, Cond &cond, + Error &error); + +#if GCC_CHECK_VERSION(4,6) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#endif + + static constexpr AlsaInputStream *Cast(InputStream *is) { + return ContainerCast(is, AlsaInputStream, base); + } + +#if GCC_CHECK_VERSION(4,6) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + + bool Available() { + if (snd_pcm_avail(capture_handle) > frames_to_read) + return true; + + if (!waiting.exchange(true)) + SafeInvalidateSockets(); + + return false; + } + + size_t Read(void *ptr, size_t size, Error &error); + + bool IsEOF() { + return eof; + } + +private: + static snd_pcm_t *OpenDevice(const char *device, int rate, + snd_pcm_format_t format, int channels, + Error &error); + + int Recover(int err); + + void SafeInvalidateSockets() { + DeferredMonitor::Schedule(); + } + + virtual void RunDeferred() override { + InvalidateSockets(); + } + + virtual int PrepareSockets() override; + virtual void DispatchSockets() override; +}; + +inline InputStream * +AlsaInputStream::Create(const char *uri, Mutex &mutex, Cond &cond, + Error &error) +{ + const char *const scheme = "alsa://"; + if (!StringStartsWith(uri, scheme)) + return nullptr; + + const char *device = uri + strlen(scheme); + if (strlen(device) == 0) + device = default_device; + + /* placeholders - eventually user-requested audio format will + be passed via the URI. For now we just force the + defaults */ + int rate = default_rate; + snd_pcm_format_t format = default_format; + int channels = default_channels; + + snd_pcm_t *handle = OpenDevice(device, rate, format, channels, + error); + if (handle == nullptr) + return nullptr; + + int frame_size = snd_pcm_format_width(format) / 8 * channels; + AlsaInputStream *stream = new AlsaInputStream(io_thread_get(), + uri, mutex, cond, + handle, frame_size); + return &stream->base; +} + +inline size_t +AlsaInputStream::Read(void *ptr, size_t size, Error &error) +{ + assert(ptr != nullptr); + + int num_frames = size / frame_size; + int ret; + while ((ret = snd_pcm_readi(capture_handle, ptr, num_frames)) < 0) { + if (Recover(ret) < 0) { + eof = true; + error.Format(alsa_input_domain, + "PCM error - stream aborted"); + return 0; + } + } + + size_t nbytes = ret * frame_size; + base.offset += nbytes; + return nbytes; +} + +int +AlsaInputStream::PrepareSockets() +{ + if (!waiting) { + ClearSocketList(); + return -1; + } + + int count = snd_pcm_poll_descriptors_count(capture_handle); + if (count < 0) { + ClearSocketList(); + return -1; + } + + struct pollfd *pfds = pfd_buffer.Get(count); + + count = snd_pcm_poll_descriptors(capture_handle, pfds, count); + if (count < 0) + count = 0; + + ReplaceSocketList(pfds, count); + return -1; +} + +void +AlsaInputStream::DispatchSockets() +{ + waiting = false; + + const ScopeLock protect(base.mutex); + /* wake up the thread that is waiting for more data */ + base.cond.broadcast(); +} + +inline int +AlsaInputStream::Recover(int err) +{ + switch(err) { + case -EPIPE: + LogDebug(alsa_input_domain, "Buffer Overrun"); + // drop through + case -ESTRPIPE: + case -EINTR: + err = snd_pcm_recover(capture_handle, err, 1); + break; + default: + // something broken somewhere, give up + err = -1; + } + return err; +} + +inline snd_pcm_t * +AlsaInputStream::OpenDevice(const char *device, + int rate, snd_pcm_format_t format, int channels, + Error &error) +{ + snd_pcm_t *capture_handle; + int err; + if ((err = snd_pcm_open(&capture_handle, device, + SND_PCM_STREAM_CAPTURE, 0)) < 0) { + error.Format(alsa_input_domain, "Failed to open device: %s (%s)", device, snd_strerror(err)); + return nullptr; + } + + snd_pcm_hw_params_t *hw_params; + if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) { + error.Format(alsa_input_domain, "Cannot allocate hardware parameter structure (%s)", snd_strerror(err)); + snd_pcm_close(capture_handle); + return nullptr; + } + + if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) { + error.Format(alsa_input_domain, "Cannot initialize hardware parameter structure (%s)", snd_strerror(err)); + snd_pcm_hw_params_free(hw_params); + snd_pcm_close(capture_handle); + return nullptr; + } + + if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + error.Format(alsa_input_domain, "Cannot set access type (%s)", snd_strerror (err)); + snd_pcm_hw_params_free(hw_params); + snd_pcm_close(capture_handle); + return nullptr; + } + + if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, format)) < 0) { + snd_pcm_hw_params_free(hw_params); + snd_pcm_close(capture_handle); + error.Format(alsa_input_domain, "Cannot set sample format (%s)", snd_strerror (err)); + return nullptr; + } + + if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, channels)) < 0) { + snd_pcm_hw_params_free(hw_params); + snd_pcm_close(capture_handle); + error.Format(alsa_input_domain, "Cannot set channels (%s)", snd_strerror (err)); + return nullptr; + } + + if ((err = snd_pcm_hw_params_set_rate(capture_handle, hw_params, rate, 0)) < 0) { + snd_pcm_hw_params_free(hw_params); + snd_pcm_close(capture_handle); + error.Format(alsa_input_domain, "Cannot set sample rate (%s)", snd_strerror (err)); + return nullptr; + } + + /* period needs to be big enough so that poll() doesn't fire too often, + * but small enough that buffer overruns don't occur if Read() is not + * invoked often enough. + * the calculation here is empirical; however all measurements were + * done using 44100:16:2. When we extend this plugin to support + * other audio formats then this may need to be revisited */ + snd_pcm_uframes_t period = read_buffer_size * 2; + int direction = -1; + if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params, + &period, &direction)) < 0) { + error.Format(alsa_input_domain, "Cannot set period size (%s)", + snd_strerror(err)); + snd_pcm_hw_params_free(hw_params); + snd_pcm_close(capture_handle); + return nullptr; + } + + if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0) { + error.Format(alsa_input_domain, "Cannot set parameters (%s)", + snd_strerror(err)); + snd_pcm_hw_params_free(hw_params); + snd_pcm_close(capture_handle); + return nullptr; + } + + snd_pcm_hw_params_free (hw_params); + + snd_pcm_sw_params_t *sw_params; + + snd_pcm_sw_params_malloc(&sw_params); + snd_pcm_sw_params_current(capture_handle, sw_params); + + if ((err = snd_pcm_sw_params_set_start_threshold(capture_handle, sw_params, + period)) < 0) { + error.Format(alsa_input_domain, + "unable to set start threshold (%s)", snd_strerror(err)); + snd_pcm_sw_params_free(sw_params); + snd_pcm_close(capture_handle); + return nullptr; + } + + if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0) { + error.Format(alsa_input_domain, + "unable to install sw params (%s)", snd_strerror(err)); + snd_pcm_sw_params_free(sw_params); + snd_pcm_close(capture_handle); + return nullptr; + } + + snd_pcm_sw_params_free(sw_params); + + snd_pcm_prepare(capture_handle); + + return capture_handle; +} + +/*######################### Plugin Functions ##############################*/ + +static InputStream * +alsa_input_open(const char *uri, Mutex &mutex, Cond &cond, Error &error) +{ + return AlsaInputStream::Create(uri, mutex, cond, error); +} + +static void +alsa_input_close(InputStream *is) +{ + AlsaInputStream *ais = AlsaInputStream::Cast(is); + delete ais; +} + +static bool +alsa_input_available(InputStream *is) +{ + AlsaInputStream *ais = AlsaInputStream::Cast(is); + return ais->Available(); +} + +static size_t +alsa_input_read(InputStream *is, void *ptr, size_t size, Error &error) +{ + AlsaInputStream *ais = AlsaInputStream::Cast(is); + return ais->Read(ptr, size, error); +} + +static bool +alsa_input_eof(gcc_unused InputStream *is) +{ + AlsaInputStream *ais = AlsaInputStream::Cast(is); + return ais->IsEOF(); +} + +const struct InputPlugin input_plugin_alsa = { + "alsa", + nullptr, + nullptr, + alsa_input_open, + alsa_input_close, + nullptr, + nullptr, + nullptr, + alsa_input_available, + alsa_input_read, + alsa_input_eof, + nullptr, +}; diff --git a/src/input/plugins/AlsaInputPlugin.hxx b/src/input/plugins/AlsaInputPlugin.hxx new file mode 100644 index 000000000..dddf7dfd7 --- /dev/null +++ b/src/input/plugins/AlsaInputPlugin.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_INPUT_PLUGIN_HXX +#define MPD_ALSA_INPUT_PLUGIN_HXX + +#include "../InputPlugin.hxx" + +extern const struct InputPlugin input_plugin_alsa; + + +#endif diff --git a/src/input/plugins/ArchiveInputPlugin.cxx b/src/input/plugins/ArchiveInputPlugin.cxx new file mode 100644 index 000000000..df1a72585 --- /dev/null +++ b/src/input/plugins/ArchiveInputPlugin.cxx @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "archive/ArchiveDomain.hxx" +#include "archive/ArchiveLookup.hxx" +#include "archive/ArchiveList.hxx" +#include "archive/ArchivePlugin.hxx" +#include "archive/ArchiveFile.hxx" +#include "../InputPlugin.hxx" +#include "fs/Traits.hxx" +#include "util/Alloc.hxx" +#include "Log.hxx" + +#include <stdlib.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 InputStream * +input_archive_open(const char *pathname, + Mutex &mutex, Cond &cond, + Error &error) +{ + const struct archive_plugin *arplug; + InputStream *is; + + if (!PathTraitsFS::IsAbsolute(pathname)) + return nullptr; + + char *pname = strdup(pathname); + // archive_lookup will modify pname when true is returned + const char *archive, *filename, *suffix; + if (!archive_lookup(pname, &archive, &filename, &suffix)) { + FormatDebug(archive_domain, + "not an archive, lookup %s failed", pname); + free(pname); + return nullptr; + } + + //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); + free(pname); + return nullptr; + } + + auto file = archive_file_open(arplug, archive, error); + if (file == nullptr) { + free(pname); + return nullptr; + } + + //setup fileops + is = file->OpenStream(filename, mutex, cond, error); + free(pname); + file->Close(); + + return is; +} + +const InputPlugin input_plugin_archive = { + "archive", + nullptr, + nullptr, + input_archive_open, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, +}; diff --git a/src/input/plugins/ArchiveInputPlugin.hxx b/src/input/plugins/ArchiveInputPlugin.hxx new file mode 100644 index 000000000..024723726 --- /dev/null +++ b/src/input/plugins/ArchiveInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 InputPlugin input_plugin_archive; + +#endif diff --git a/src/input/plugins/CdioParanoiaInputPlugin.cxx b/src/input/plugins/CdioParanoiaInputPlugin.cxx new file mode 100644 index 000000000..9267b50e1 --- /dev/null +++ b/src/input/plugins/CdioParanoiaInputPlugin.cxx @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "../InputStream.hxx" +#include "../InputPlugin.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "system/ByteOrder.hxx" +#include "fs/AllocatedPath.hxx" +#include "Log.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigError.hxx" + +#include <stdio.h> +#include <stdint.h> +#include <stddef.h> +#include <string.h> +#include <stdlib.h> +#include <glib.h> +#include <assert.h> + +#ifdef HAVE_CDIO_PARANOIA_PARANOIA_H +#include <cdio/paranoia/paranoia.h> +#else +#include <cdio/paranoia.h> +#endif + +#include <cdio/cd_types.h> + +struct CdioParanoiaInputStream { + InputStream 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 bool default_reverse_endian; + +static bool +input_cdio_init(const config_param ¶m, Error &error) +{ + const char *value = param.GetBlockValue("default_byte_order"); + if (value != nullptr) { + if (strcmp(value, "little_endian") == 0) + default_reverse_endian = IsBigEndian(); + else if (strcmp(value, "big_endian") == 0) + default_reverse_endian = IsLittleEndian(); + else { + error.Format(config_domain, 0, + "Unrecognized 'default_byte_order' setting: %s", + value); + return false; + } + } + + return true; +} + +static void +input_cdio_close(InputStream *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 (!StringStartsWith(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 AllocatedPath +cdio_detect_device(void) +{ + char **devices = cdio_get_devices_with_cap(nullptr, CDIO_FS_AUDIO, + false); + if (devices == nullptr) + return AllocatedPath::Null(); + + AllocatedPath path = AllocatedPath::FromFS(devices[0]); + cdio_free_device_list(devices); + return path; +} + +static InputStream * +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 */ + const AllocatedPath device = parsed_uri.device[0] != 0 + ? AllocatedPath::FromFS(parsed_uri.device) + : cdio_detect_device(); + if (device.IsNull()) { + 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.c_str(), DRIVER_UNKNOWN); + + 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 = default_reverse_endian; + break; + + case 0: + LogDebug(cdio_domain, "drive returns audio data Little Endian"); + reverse_endian = IsBigEndian(); + break; + + case 1: + LogDebug(cdio_domain, "drive returns audio data Big Endian"); + reverse_endian = IsLittleEndian(); + 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 = reverse_endian + ? "audio/x-mpd-cdda-pcm-reverse" + : "audio/x-mpd-cdda-pcm"; + + return &i->base; +} + +static bool +input_cdio_seek(InputStream *is, + InputPlugin::offset_type 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(InputStream *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(InputStream *is) +{ + CdioParanoiaInputStream *cis = (CdioParanoiaInputStream *)is; + + return (cis->lsn_from + cis->lsn_relofs > cis->lsn_to); +} + +const InputPlugin input_plugin_cdio_paranoia = { + "cdio_paranoia", + input_cdio_init, + 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/plugins/CdioParanoiaInputPlugin.hxx b/src/input/plugins/CdioParanoiaInputPlugin.hxx new file mode 100644 index 000000000..e2804e8c7 --- /dev/null +++ b/src/input/plugins/CdioParanoiaInputPlugin.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 InputPlugin input_plugin_cdio_paranoia; + +#endif diff --git a/src/input/plugins/CurlInputPlugin.cxx b/src/input/plugins/CurlInputPlugin.cxx new file mode 100644 index 000000000..a3c6c1d1a --- /dev/null +++ b/src/input/plugins/CurlInputPlugin.cxx @@ -0,0 +1,1170 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "../InputStream.hxx" +#include "../InputPlugin.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigData.hxx" +#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" +#include "IcyMetaDataParser.hxx" +#include "event/SocketMonitor.hxx" +#include "event/TimeoutMonitor.hxx" +#include "event/Call.hxx" +#include "IOThread.hxx" +#include "util/ASCII.hxx" +#include "util/CharUtil.hxx" +#include "util/NumberParser.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 <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 { + InputStream base; + + /* some buffers which were passed to libcurl, which we have + too free */ + char range[32]; + 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 */ + std::string meta_name; + + /** the tag object ready to be requested via + InputStream::ReadTag() */ + Tag *tag; + + Error postponed_error; + + input_curl(const char *url, Mutex &mutex, Cond &cond) + :base(input_plugin_curl, url, mutex, cond), + request_headers(nullptr), + paused(false), + tag(nullptr) {} + + ~input_curl(); + + input_curl(const input_curl &) = delete; + input_curl &operator=(const input_curl &) = delete; +}; + +class CurlMulti; + +/** + * Monitor for one socket created by CURL. + */ +class CurlSocket final : SocketMonitor { + CurlMulti &multi; + +public: + CurlSocket(CurlMulti &_multi, EventLoop &_loop, int _fd) + :SocketMonitor(_fd, _loop), multi(_multi) {} + + ~CurlSocket() { + /* TODO: sometimes, CURL uses CURL_POLL_REMOVE after + closing the socket, and sometimes, it uses + CURL_POLL_REMOVE just to move the (still open) + connection to the pool; in the first case, + Abandon() would be most appropriate, but it breaks + the second case - is that a CURL bug? is there a + better solution? */ + } + + /** + * Callback function for CURLMOPT_SOCKETFUNCTION. + */ + static int SocketFunction(CURL *easy, + curl_socket_t s, int action, + void *userp, void *socketp); + + virtual bool OnSocketReady(unsigned flags) override; + +private: + static constexpr int FlagsToCurlCSelect(unsigned flags) { + return (flags & (READ | HANGUP) ? CURL_CSELECT_IN : 0) | + (flags & WRITE ? CURL_CSELECT_OUT : 0) | + (flags & ERROR ? CURL_CSELECT_ERR : 0); + } + + gcc_const + static unsigned CurlPollToFlags(int action) { + switch (action) { + case CURL_POLL_NONE: + return 0; + + case CURL_POLL_IN: + return READ; + + case CURL_POLL_OUT: + return WRITE; + + case CURL_POLL_INOUT: + return READ|WRITE; + } + + assert(false); + gcc_unreachable(); + } +}; + +/** + * Manager for the global CURLM object. + */ +class CurlMulti final : private TimeoutMonitor { + CURLM *const multi; + +public: + CurlMulti(EventLoop &_loop, CURLM *_multi); + + ~CurlMulti() { + curl_multi_cleanup(multi); + } + + bool Add(input_curl *c, Error &error); + void Remove(input_curl *c); + + /** + * Check for finished HTTP responses. + * + * Runs in the I/O thread. The caller must not hold locks. + */ + void ReadInfo(); + + void Assign(curl_socket_t fd, CurlSocket &cs) { + curl_multi_assign(multi, fd, &cs); + } + + void SocketAction(curl_socket_t fd, int ev_bitmask); + + void InvalidateSockets() { + SocketAction(CURL_SOCKET_TIMEOUT, 0); + } + + /** + * This is a kludge to allow pausing/resuming a stream with + * libcurl < 7.32.0. Read the curl_easy_pause manpage for + * more information. + */ + void ResumeSockets() { + int running_handles; + curl_multi_socket_all(multi, &running_handles); + } + +private: + static int TimerFunction(CURLM *multi, long timeout_ms, void *userp); + + virtual void OnTimeout() override; +}; + +/** + * libcurl version number encoded in a 24 bit integer. + */ +static unsigned curl_version_num; + +/** 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 CurlMulti *curl_multi; + +static constexpr Domain http_domain("http"); +static constexpr Domain curl_domain("curl"); +static constexpr Domain curlm_domain("curlm"); + +CurlMulti::CurlMulti(EventLoop &_loop, CURLM *_multi) + :TimeoutMonitor(_loop), multi(_multi) +{ + curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, + CurlSocket::SocketFunction); + curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, this); + + curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, TimerFunction); + curl_multi_setopt(multi, CURLMOPT_TIMERDATA, this); +} + +/** + * Find a request by its CURL "easy" handle. + * + * Runs in the I/O thread. No lock needed. + */ +gcc_pure +static struct input_curl * +input_curl_find_request(CURL *easy) +{ + assert(io_thread_inside()); + + void *p; + CURLcode code = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &p); + if (code != CURLE_OK) + return nullptr; + + return (input_curl *)p; +} + +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); + + if (curl_version_num < 0x072000) + /* libcurl older than 7.32.0 does not update + its sockets after curl_easy_pause(); force + libcurl to do it now */ + curl_multi->ResumeSockets(); + + curl_multi->InvalidateSockets(); + } +} + +int +CurlSocket::SocketFunction(gcc_unused CURL *easy, + curl_socket_t s, int action, + void *userp, void *socketp) { + CurlMulti &multi = *(CurlMulti *)userp; + CurlSocket *cs = (CurlSocket *)socketp; + + assert(io_thread_inside()); + + if (action == CURL_POLL_REMOVE) { + delete cs; + return 0; + } + + if (cs == nullptr) { + cs = new CurlSocket(multi, io_thread_get(), s); + multi.Assign(s, *cs); + } else { +#ifdef USE_EPOLL + /* when using epoll, we need to unregister the socket + each time this callback is invoked, because older + CURL versions may omit the CURL_POLL_REMOVE call + when the socket has been closed and recreated with + the same file number (bug found in CURL 7.26, CURL + 7.33 not affected); in that case, epoll refuses the + EPOLL_CTL_MOD because it does not know the new + socket yet */ + cs->Cancel(); +#endif + } + + unsigned flags = CurlPollToFlags(action); + if (flags != 0) + cs->Schedule(flags); + return 0; +} + +bool +CurlSocket::OnSocketReady(unsigned flags) +{ + assert(io_thread_inside()); + + multi.SocketAction(Get(), FlagsToCurlCSelect(flags)); + return true; +} + +/** + * Runs in the I/O thread. No lock needed. + */ +inline bool +CurlMulti::Add(struct input_curl *c, Error &error) +{ + assert(io_thread_inside()); + assert(c != nullptr); + assert(c->easy != nullptr); + + CURLMcode mcode = curl_multi_add_handle(multi, c->easy); + if (mcode != CURLM_OK) { + error.Format(curlm_domain, mcode, + "curl_multi_add_handle() failed: %s", + curl_multi_strerror(mcode)); + return false; + } + + 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 != nullptr); + assert(c->easy != nullptr); + + bool result; + BlockingCall(io_thread_get(), [c, &error, &result](){ + result = curl_multi->Add(c, error); + }); + return result; +} + +inline void +CurlMulti::Remove(input_curl *c) +{ + curl_multi_remove_handle(multi, c->easy); +} + +/** + * 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 != nullptr); + + if (c->easy == nullptr) + return; + + curl_multi->Remove(c); + + curl_easy_cleanup(c->easy); + c->easy = nullptr; + + curl_slist_free_all(c->request_headers); + c->request_headers = nullptr; +} + +/** + * 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_multi->InvalidateSockets(); + }); + + assert(c->easy == nullptr); +} + +/** + * 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 != nullptr); + assert(c->easy == nullptr); + 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 != nullptr); + + long status = 0; + curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status); + + input_curl_easy_free(c); + input_curl_request_done(c, result, status); +} + +void +CurlMulti::SocketAction(curl_socket_t fd, int ev_bitmask) +{ + int running_handles; + CURLMcode mcode = curl_multi_socket_action(multi, fd, ev_bitmask, + &running_handles); + if (mcode != CURLM_OK) + FormatError(curlm_domain, + "curl_multi_socket_action() failed: %s", + curl_multi_strerror(mcode)); + + ReadInfo(); +} + +/** + * Check for finished HTTP responses. + * + * Runs in the I/O thread. The caller must not hold locks. + */ +inline void +CurlMulti::ReadInfo() +{ + assert(io_thread_inside()); + + CURLMsg *msg; + int msgs_in_queue; + + while ((msg = curl_multi_info_read(multi, + &msgs_in_queue)) != nullptr) { + if (msg->msg == CURLMSG_DONE) + input_curl_handle_done(msg->easy_handle, msg->data.result); + } +} + +int +CurlMulti::TimerFunction(gcc_unused CURLM *_multi, long timeout_ms, void *userp) +{ + CurlMulti &multi = *(CurlMulti *)userp; + assert(_multi == multi.multi); + + if (timeout_ms < 0) { + multi.Cancel(); + return 0; + } + + if (timeout_ms >= 0 && timeout_ms < 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. */ + timeout_ms = 10; + + multi.Schedule(timeout_ms); + return 0; +} + +void +CurlMulti::OnTimeout() +{ + SocketAction(CURL_SOCKET_TIMEOUT, 0); +} + +/* + * InputPlugin 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; + } + + const auto version_info = curl_version_info(CURLVERSION_FIRST); + if (version_info != nullptr) { + FormatDebug(curl_domain, "version %s", version_info->version); + if (version_info->features & CURL_VERSION_SSL) + FormatDebug(curl_domain, "with %s", + version_info->ssl_version); + + curl_version_num = version_info->version_num; + } + + 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 == nullptr) { + /* deprecated proxy configuration */ + proxy = config_get_string(CONF_HTTP_PROXY_HOST, nullptr); + proxy_port = config_get_positive(CONF_HTTP_PROXY_PORT, 0); + proxy_user = config_get_string(CONF_HTTP_PROXY_USER, nullptr); + proxy_password = config_get_string(CONF_HTTP_PROXY_PASSWORD, + ""); + } + + CURLM *multi = curl_multi_init(); + if (multi == nullptr) { + error.Set(curl_domain, 0, "curl_multi_init() failed"); + return false; + } + + curl_multi = new CurlMulti(io_thread_get(), multi); + return true; +} + +static void +input_curl_finish(void) +{ + BlockingCall(io_thread_get(), [](){ + delete 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; + + input_curl_easy_free_indirect(this); +} + +static bool +input_curl_check(InputStream *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(InputStream *is) +{ + struct input_curl *c = (struct input_curl *)is; + Tag *tag = c->tag; + + c->tag = nullptr; + return tag; +} + +static bool +fill_buffer(struct input_curl *c, Error &error) +{ + while (c->easy != nullptr && 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 == nullptr) + return; + + delete c->tag; + + if (!c->meta_name.empty() && !tag->HasType(TAG_NAME)) { + TagBuilder tag_builder(std::move(*tag)); + tag_builder.AddItem(TAG_NAME, c->meta_name.c_str()); + *tag = tag_builder.Commit(); + } + + c->tag = tag; +} + +static bool +input_curl_available(InputStream *is) +{ + struct input_curl *c = (struct input_curl *)is; + + return c->postponed_error.IsDefined() || c->easy == nullptr || + !c->buffers.empty(); +} + +static size_t +input_curl_read(InputStream *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 += (InputPlugin::offset_type)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(InputStream *is) +{ + struct input_curl *c = (struct input_curl *)is; + + delete c; +} + +static bool +input_curl_eof(gcc_unused InputStream *is) +{ + struct input_curl *c = (struct input_curl *)is; + + return c->easy == nullptr && 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 == nullptr || (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 && IsWhitespaceOrNull(*value)) + ++value; + + while (end > value && IsWhitespaceOrNull(end[-1])) + --end; + + if (StringEqualsCaseASCII(name, "accept-ranges")) { + /* a stream with icy-metadata is not seekable */ + if (!c->icy.IsDefined()) + c->base.seekable = true; + } else if (StringEqualsCaseASCII(name, "content-length")) { + 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 + ParseUint64(buffer); + } else if (StringEqualsCaseASCII(name, "content-type")) { + c->base.mime.assign(value, end); + } else if (StringEqualsCaseASCII(name, "icy-name") || + StringEqualsCaseASCII(name, "ice-name") || + StringEqualsCaseASCII(name, "x-audiocast-name")) { + c->meta_name.assign(value, end); + + delete c->tag; + + TagBuilder tag_builder; + tag_builder.AddItem(TAG_NAME, c->meta_name.c_str()); + + c->tag = tag_builder.CommitNew(); + } else if (StringEqualsCaseASCII(name, "icy-metaint")) { + 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 = ParseUint64(buffer); + 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 == nullptr) { + error.Set(curl_domain, "curl_easy_init() failed"); + return false; + } + + curl_easy_setopt(c->easy, CURLOPT_PRIVATE, (void *)c); + 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 != nullptr) + 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 != nullptr && proxy_password != nullptr) { + char proxy_auth_str[1024]; + snprintf(proxy_auth_str, sizeof(proxy_auth_str), + "%s:%s", + proxy_user, proxy_password); + curl_easy_setopt(c->easy, CURLOPT_PROXYUSERPWD, 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 = nullptr; + 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(InputStream *is, InputPlugin::offset_type 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 < (InputPlugin::offset_type)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) { + sprintf(c->range, "%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 InputStream * +input_curl_open(const char *url, Mutex &mutex, Cond &cond, + Error &error) +{ + if (memcmp(url, "http://", 7) != 0 && + memcmp(url, "https://", 8) != 0) + return nullptr; + + struct input_curl *c = new input_curl(url, mutex, cond); + + if (!input_curl_easy_init(c, error)) { + delete c; + return nullptr; + } + + if (!input_curl_easy_add_indirect(c, error)) { + delete c; + return nullptr; + } + + return &c->base; +} + +const struct InputPlugin 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/plugins/CurlInputPlugin.hxx b/src/input/plugins/CurlInputPlugin.hxx new file mode 100644 index 000000000..4acb18bfc --- /dev/null +++ b/src/input/plugins/CurlInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 + +extern const struct InputPlugin input_plugin_curl; + +#endif diff --git a/src/input/plugins/DespotifyInputPlugin.cxx b/src/input/plugins/DespotifyInputPlugin.cxx new file mode 100644 index 000000000..152fda95f --- /dev/null +++ b/src/input/plugins/DespotifyInputPlugin.cxx @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "lib/despotify/DespotifyUtils.hxx" +#include "../InputStream.hxx" +#include "../InputPlugin.hxx" +#include "tag/Tag.hxx" +#include "util/StringUtil.hxx" +#include "Log.hxx" + +extern "C" { +#include <despotify.h> +} + +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include <stdio.h> + +class DespotifyInputStream { + InputStream 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 = "audio/x-mpd-cdda-pcm"; + base.ready = true; + } + +public: + ~DespotifyInputStream() { + despotify_free_track(track); + } + + static InputStream *Open(const char *url, Mutex &mutex, Cond &cond, + Error &error); + + bool IsEOF() const { + return eof; + } + + size_t Read(void *ptr, size_t size, Error &error); + + Tag *ReadTag() { + if (tag.IsEmpty()) + return nullptr; + + Tag *result = new Tag(std::move(tag)); + tag.Clear(); + return result; + } + + void Callback(int sig); + +private: + void FillBuffer(); +}; + +inline void +DespotifyInputStream::FillBuffer() +{ + /* Wait until there is data */ + while (1) { + int rc = despotify_get_pcm(session, &pcm); + + if (rc == 0 && pcm.len) { + len_available = pcm.len; + break; + } + + if (eof == true) + break; + + if (rc < 0) { + LogDebug(despotify_domain, "despotify_get_pcm error"); + eof = true; + break; + } + + /* Wait a while until next iteration */ + usleep(50 * 1000); + } +} + +inline void +DespotifyInputStream::Callback(int sig) +{ + switch (sig) { + case DESPOTIFY_NEW_TRACK: + break; + + case DESPOTIFY_TIME_TELL: + break; + + case DESPOTIFY_TRACK_PLAY_ERROR: + LogWarning(despotify_domain, "Track play error"); + eof = true; + len_available = 0; + break; + + case DESPOTIFY_END_OF_PLAYLIST: + eof = true; + LogDebug(despotify_domain, "End of playlist"); + break; + } +} + +static void callback(gcc_unused struct despotify_session* ds, + int sig, gcc_unused void* data, void* callback_data) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)callback_data; + + ctx->Callback(sig); +} + +inline InputStream * +DespotifyInputStream::Open(const char *url, + Mutex &mutex, Cond &cond, + gcc_unused Error &error) +{ + if (!StringStartsWith(url, "spt://")) + return nullptr; + + despotify_session *session = mpd_despotify_get_session(); + if (session == nullptr) + return nullptr; + + ds_link *ds_link = despotify_link_from_uri(url + 6); + if (!ds_link) { + FormatDebug(despotify_domain, "Can't find %s", url); + return nullptr; + } + if (ds_link->type != LINK_TYPE_TRACK) { + despotify_free_link(ds_link); + return nullptr; + } + + ds_track *track = despotify_link_get_track(session, ds_link); + despotify_free_link(ds_link); + if (!track) + return nullptr; + + DespotifyInputStream *ctx = + new DespotifyInputStream(url, mutex, cond, + session, track); + + if (!mpd_despotify_register_callback(callback, ctx)) { + delete ctx; + return nullptr; + } + + if (despotify_play(ctx->session, ctx->track, false) == false) { + mpd_despotify_unregister_callback(callback); + delete ctx; + return nullptr; + } + + return &ctx->base; +} + +static InputStream * +input_despotify_open(const char *url, Mutex &mutex, Cond &cond, Error &error) +{ + return DespotifyInputStream::Open(url, mutex, cond, error); +} + +inline size_t +DespotifyInputStream::Read(void *ptr, size_t size, gcc_unused Error &error) +{ + if (len_available == 0) + FillBuffer(); + + size_t to_cpy = std::min(size, len_available); + memcpy(ptr, pcm.buf, to_cpy); + len_available -= to_cpy; + + base.offset += to_cpy; + + return to_cpy; +} + +static size_t +input_despotify_read(InputStream *is, void *ptr, size_t size, Error &error) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)is; + return ctx->Read(ptr, size, error); +} + +static void +input_despotify_close(InputStream *is) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)is; + + mpd_despotify_unregister_callback(callback); + delete ctx; +} + +static bool +input_despotify_eof(InputStream *is) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)is; + + return ctx->IsEOF(); +} + +static Tag * +input_despotify_tag(InputStream *is) +{ + DespotifyInputStream *ctx = (DespotifyInputStream *)is; + + return ctx->ReadTag(); +} + +const InputPlugin input_plugin_despotify = { + "despotify", + 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/plugins/DespotifyInputPlugin.hxx b/src/input/plugins/DespotifyInputPlugin.hxx new file mode 100644 index 000000000..83f963520 --- /dev/null +++ b/src/input/plugins/DespotifyInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License 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 InputPlugin input_plugin_despotify; + +#endif diff --git a/src/input/plugins/FfmpegInputPlugin.cxx b/src/input/plugins/FfmpegInputPlugin.cxx new file mode 100644 index 000000000..0d0a4375e --- /dev/null +++ b/src/input/plugins/FfmpegInputPlugin.cxx @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "../InputStream.hxx" +#include "../InputPlugin.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +extern "C" { +#include <libavformat/avio.h> +#include <libavformat/avformat.h> +} + +struct FfmpegInputStream { + InputStream 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 = "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 InputStream * +input_ffmpeg_open(const char *uri, + Mutex &mutex, Cond &cond, + Error &error) +{ + if (!StringStartsWith(uri, "gopher://") && + !StringStartsWith(uri, "rtp://") && + !StringStartsWith(uri, "rtsp://") && + !StringStartsWith(uri, "rtmp://") && + !StringStartsWith(uri, "rtmpt://") && + !StringStartsWith(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(InputStream *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(InputStream *is) +{ + FfmpegInputStream *i = (FfmpegInputStream *)is; + + delete i; +} + +static bool +input_ffmpeg_eof(InputStream *is) +{ + FfmpegInputStream *i = (FfmpegInputStream *)is; + + return i->eof; +} + +static bool +input_ffmpeg_seek(InputStream *is, InputPlugin::offset_type 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 InputPlugin 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/plugins/FfmpegInputPlugin.hxx b/src/input/plugins/FfmpegInputPlugin.hxx new file mode 100644 index 000000000..43f829e89 --- /dev/null +++ b/src/input/plugins/FfmpegInputPlugin.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 InputPlugin input_plugin_ffmpeg; + +#endif diff --git a/src/input/plugins/FileInputPlugin.cxx b/src/input/plugins/FileInputPlugin.cxx new file mode 100644 index 000000000..780e93263 --- /dev/null +++ b/src/input/plugins/FileInputPlugin.cxx @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with 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 "../InputStream.hxx" +#include "../InputPlugin.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "fs/Traits.hxx" +#include "system/fd_util.h" +#include "open.h" + +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> + +static constexpr Domain file_domain("file"); + +struct FileInputStream { + InputStream 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 InputStream * +input_file_open(const char *filename, + Mutex &mutex, Cond &cond, + Error &error) +{ + int fd, ret; + struct stat st; + + if (!PathTraitsFS::IsAbsolute(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(InputStream *is, InputPlugin::offset_type offset, + int whence, + Error &error) +{ + FileInputStream *fis = (FileInputStream *)is; + + offset = (InputPlugin::offset_type)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(InputStream *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(InputStream *is) +{ + FileInputStream *fis = (FileInputStream *)is; + + delete fis; +} + +static bool +input_file_eof(InputStream *is) +{ + return is->offset >= is->size; +} + +const InputPlugin 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/plugins/FileInputPlugin.hxx b/src/input/plugins/FileInputPlugin.hxx new file mode 100644 index 000000000..4aef94637 --- /dev/null +++ b/src/input/plugins/FileInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 InputPlugin input_plugin_file; + +#endif diff --git a/src/input/plugins/MmsInputPlugin.cxx b/src/input/plugins/MmsInputPlugin.cxx new file mode 100644 index 000000000..a7068cb2b --- /dev/null +++ b/src/input/plugins/MmsInputPlugin.cxx @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "../InputStream.hxx" +#include "../InputPlugin.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <libmms/mmsx.h> + +struct MmsInputStream { + InputStream 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 = "audio/x-ms-wma"; + + base.ready = true; + } + + ~MmsInputStream() { + mmsx_close(mms); + } +}; + +static constexpr Domain mms_domain("mms"); + +static InputStream * +input_mms_open(const char *url, + Mutex &mutex, Cond &cond, + Error &error) +{ + if (!StringStartsWith(url, "mms://") && + !StringStartsWith(url, "mmsh://") && + !StringStartsWith(url, "mmst://") && + !StringStartsWith(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(InputStream *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(InputStream *is) +{ + MmsInputStream *m = (MmsInputStream *)is; + + delete m; +} + +static bool +input_mms_eof(InputStream *is) +{ + MmsInputStream *m = (MmsInputStream *)is; + + return m->eof; +} + +const InputPlugin 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/plugins/MmsInputPlugin.hxx b/src/input/plugins/MmsInputPlugin.hxx new file mode 100644 index 000000000..b4017ffd6 --- /dev/null +++ b/src/input/plugins/MmsInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License 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_MMS_H +#define INPUT_MMS_H + +extern const struct InputPlugin input_plugin_mms; + +#endif diff --git a/src/input/plugins/RewindInputPlugin.cxx b/src/input/plugins/RewindInputPlugin.cxx new file mode 100644 index 000000000..1a930ac53 --- /dev/null +++ b/src/input/plugins/RewindInputPlugin.cxx @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "../InputStream.hxx" +#include "../InputPlugin.hxx" + +#include <assert.h> +#include <string.h> +#include <stdio.h> + +extern const InputPlugin rewind_input_plugin; + +struct RewindInputStream { + InputStream base; + + InputStream *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(InputStream *_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() { + InputStream *dest = &base; + const InputStream *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(InputStream *is) +{ + RewindInputStream *r = (RewindInputStream *)is; + + delete r; +} + +static bool +input_rewind_check(InputStream *is, Error &error) +{ + RewindInputStream *r = (RewindInputStream *)is; + + return r->input->Check(error); +} + +static void +input_rewind_update(InputStream *is) +{ + RewindInputStream *r = (RewindInputStream *)is; + + if (!r->ReadingFromBuffer()) + r->CopyAttributes(); +} + +static Tag * +input_rewind_tag(InputStream *is) +{ + RewindInputStream *r = (RewindInputStream *)is; + + return r->input->ReadTag(); +} + +static bool +input_rewind_available(InputStream *is) +{ + RewindInputStream *r = (RewindInputStream *)is; + + return r->input->IsAvailable(); +} + +static size_t +input_rewind_read(InputStream *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 > (InputPlugin::offset_type)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(InputStream *is) +{ + RewindInputStream *r = (RewindInputStream *)is; + + return !r->ReadingFromBuffer() && r->input->IsEOF(); +} + +static bool +input_rewind_seek(InputStream *is, InputPlugin::offset_type offset, + int whence, + Error &error) +{ + RewindInputStream *r = (RewindInputStream *)is; + + assert(is->ready); + + if (whence == SEEK_SET && r->tail > 0 && + offset <= (InputPlugin::offset_type)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 InputPlugin 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, +}; + +InputStream * +input_rewind_open(InputStream *is) +{ + assert(is != nullptr); + 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/plugins/RewindInputPlugin.hxx b/src/input/plugins/RewindInputPlugin.hxx new file mode 100644 index 000000000..f19705154 --- /dev/null +++ b/src/input/plugins/RewindInputPlugin.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 InputStream; + +InputStream * +input_rewind_open(InputStream *is); + +#endif diff --git a/src/input/plugins/SmbclientInputPlugin.cxx b/src/input/plugins/SmbclientInputPlugin.cxx new file mode 100644 index 000000000..561e6f8fd --- /dev/null +++ b/src/input/plugins/SmbclientInputPlugin.cxx @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SmbclientInputPlugin.hxx" +#include "lib/smbclient/Init.hxx" +#include "../InputStream.hxx" +#include "../InputPlugin.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" + +#include <libsmbclient.h> + +class SmbclientInputStream { + InputStream base; + + SMBCCTX *ctx; + int fd; + +public: + SmbclientInputStream(const char *uri, + Mutex &mutex, Cond &cond, + SMBCCTX *_ctx, int _fd, const struct stat &st) + :base(input_plugin_smbclient, uri, mutex, cond), + ctx(_ctx), fd(_fd) { + base.ready = true; + base.seekable = true; + base.size = st.st_size; + } + + ~SmbclientInputStream() { + smbc_close(fd); + smbc_free_context(ctx, 1); + } + + InputStream *GetBase() { + return &base; + } + + bool IsEOF() const { + return base.offset >= base.size; + } + + size_t Read(void *ptr, size_t size, Error &error) { + ssize_t nbytes = smbc_read(fd, ptr, size); + if (nbytes < 0) { + error.SetErrno("smbc_read() failed"); + nbytes = 0; + } + + return nbytes; + } + + bool Seek(InputStream::offset_type offset, int whence, Error &error) { + off_t result = smbc_lseek(fd, offset, whence); + if (result < 0) { + error.SetErrno("smbc_lseek() failed"); + return false; + } + + base.offset = result; + return true; + } +}; + +/* + * InputPlugin methods + * + */ + +static bool +input_smbclient_init(gcc_unused const config_param ¶m, Error &error) +{ + if (!SmbclientInit(error)) + return false; + + // TODO: create one global SMBCCTX here? + + // TODO: evaluate config_param, call smbc_setOption*() + + return true; +} + +static InputStream * +input_smbclient_open(const char *uri, + Mutex &mutex, Cond &cond, + Error &error) +{ + if (!StringStartsWith(uri, "smb://")) + return nullptr; + + SMBCCTX *ctx = smbc_new_context(); + if (ctx == nullptr) { + error.SetErrno("smbc_new_context() failed"); + return nullptr; + } + + SMBCCTX *ctx2 = smbc_init_context(ctx); + if (ctx2 == nullptr) { + error.SetErrno("smbc_init_context() failed"); + smbc_free_context(ctx, 1); + return nullptr; + } + + ctx = ctx2; + + int fd = smbc_open(uri, O_RDONLY, 0); + if (fd < 0) { + error.SetErrno("smbc_open() failed"); + smbc_free_context(ctx, 1); + return nullptr; + } + + struct stat st; + if (smbc_fstat(fd, &st) < 0) { + error.SetErrno("smbc_fstat() failed"); + smbc_close(fd); + smbc_free_context(ctx, 1); + return nullptr; + } + + auto s = new SmbclientInputStream(uri, mutex, cond, ctx, fd, st); + return s->GetBase(); +} + +static size_t +input_smbclient_read(InputStream *is, void *ptr, size_t size, + Error &error) +{ + SmbclientInputStream &s = *(SmbclientInputStream *)is; + return s.Read(ptr, size, error); +} + +static void +input_smbclient_close(InputStream *is) +{ + SmbclientInputStream *s = (SmbclientInputStream *)is; + delete s; +} + +static bool +input_smbclient_eof(InputStream *is) +{ + SmbclientInputStream &s = *(SmbclientInputStream *)is; + return s.IsEOF(); +} + +static bool +input_smbclient_seek(InputStream *is, + InputPlugin::offset_type offset, int whence, + Error &error) +{ + SmbclientInputStream &s = *(SmbclientInputStream *)is; + return s.Seek(offset, whence, error); +} + +const InputPlugin input_plugin_smbclient = { + "smbclient", + input_smbclient_init, + nullptr, + input_smbclient_open, + input_smbclient_close, + nullptr, + nullptr, + nullptr, + nullptr, + input_smbclient_read, + input_smbclient_eof, + input_smbclient_seek, +}; diff --git a/src/input/plugins/SmbclientInputPlugin.hxx b/src/input/plugins/SmbclientInputPlugin.hxx new file mode 100644 index 000000000..a0539d020 --- /dev/null +++ b/src/input/plugins/SmbclientInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_SMBCLIENT_H +#define MPD_INPUT_SMBCLIENT_H + +extern const struct InputPlugin input_plugin_smbclient; + +#endif diff --git a/src/lib/despotify/DespotifyUtils.cxx b/src/lib/despotify/DespotifyUtils.cxx new file mode 100644 index 000000000..47a83e49b --- /dev/null +++ b/src/lib/despotify/DespotifyUtils.cxx @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License 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 "tag/TagBuilder.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +extern "C" { +#include <despotify.h> +} + +#include <stdio.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] = nullptr; + } + } +} + +Tag +mpd_despotify_tag_from_track(const ds_track &track) +{ + char tracknum[20]; + char comment[80]; + char date[20]; + + if (!track.has_meta_data) + return Tag(); + + TagBuilder 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.SetTime(track.length / 1000); + + return tag.Commit(); +} + +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, nullptr); + passwd = config_get_string(CONF_DESPOTIFY_PASSWORD, nullptr); + high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true); + + if (user == nullptr || passwd == nullptr) { + 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, nullptr, + 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/lib/despotify/DespotifyUtils.hxx b/src/lib/despotify/DespotifyUtils.hxx new file mode 100644 index 000000000..835b901a2 --- /dev/null +++ b/src/lib/despotify/DespotifyUtils.hxx @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 nullptr 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 filled in #Tag structure + */ +Tag +mpd_despotify_tag_from_track(const 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/lib/expat/ExpatParser.cxx b/src/lib/expat/ExpatParser.cxx new file mode 100644 index 000000000..b69dc55b4 --- /dev/null +++ b/src/lib/expat/ExpatParser.cxx @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ExpatParser.hxx" +#include "input/InputStream.hxx" +#include "util/ASCII.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <string.h> + +static constexpr Domain expat_domain("expat"); + +void +ExpatParser::SetError(Error &error) +{ + XML_Error code = XML_GetErrorCode(parser); + error.Format(expat_domain, int(code), "XML parser failed: %s", + XML_ErrorString(code)); +} + +bool +ExpatParser::Parse(const char *data, size_t length, bool is_final, + Error &error) +{ + bool success = XML_Parse(parser, data, length, + is_final) == XML_STATUS_OK; + if (!success) + SetError(error); + + return success; +} + +bool +ExpatParser::Parse(InputStream &is, Error &error) +{ + assert(is.ready); + + while (true) { + char buffer[4096]; + size_t nbytes = is.LockRead(buffer, sizeof(buffer), error); + if (nbytes == 0) + break; + + if (!Parse(buffer, nbytes, false, error)) + return false; + } + + if (error.IsDefined()) + return false; + + return Parse("", 0, true, error); +} + +const char * +ExpatParser::GetAttribute(const XML_Char **atts, + const char *name) +{ + for (unsigned i = 0; atts[i] != nullptr; i += 2) + if (strcmp(atts[i], name) == 0) + return atts[i + 1]; + + return nullptr; +} + +const char * +ExpatParser::GetAttributeCase(const XML_Char **atts, + const char *name) +{ + for (unsigned i = 0; atts[i] != nullptr; i += 2) + if (StringEqualsCaseASCII(atts[i], name)) + return atts[i + 1]; + + return nullptr; +} diff --git a/src/lib/expat/ExpatParser.hxx b/src/lib/expat/ExpatParser.hxx new file mode 100644 index 000000000..d57a85533 --- /dev/null +++ b/src/lib/expat/ExpatParser.hxx @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_EXPAT_HXX +#define MPD_EXPAT_HXX + +#include "check.h" +#include "Compiler.h" + +#include <expat.h> + +struct InputStream; +class Error; + +class ExpatParser final { + const XML_Parser parser; + +public: + ExpatParser(void *userData) + :parser(XML_ParserCreate(nullptr)) { + XML_SetUserData(parser, userData); + } + + ~ExpatParser() { + XML_ParserFree(parser); + } + + void SetElementHandler(XML_StartElementHandler start, + XML_EndElementHandler end) { + XML_SetElementHandler(parser, start, end); + } + + void SetCharacterDataHandler(XML_CharacterDataHandler charhndl) { + XML_SetCharacterDataHandler(parser, charhndl); + } + + bool Parse(const char *data, size_t length, bool is_final, + Error &error); + + bool Parse(InputStream &is, Error &error); + + gcc_pure + static const char *GetAttribute(const XML_Char **atts, + const char *name); + + gcc_pure + static const char *GetAttributeCase(const XML_Char **atts, + const char *name); + +private: + void SetError(Error &error); +}; + +/** + * A specialization of #ExpatParser that provides the most common + * callbacks as virtual methods. + */ +class CommonExpatParser { + ExpatParser parser; + +public: + CommonExpatParser():parser(this) { + parser.SetElementHandler(StartElement, EndElement); + parser.SetCharacterDataHandler(CharacterData); + } + + bool Parse(const char *data, size_t length, bool is_final, + Error &error) { + return parser.Parse(data, length, is_final, error); + } + + bool Parse(InputStream &is, Error &error) { + return parser.Parse(is, error); + } + + gcc_pure + static const char *GetAttribute(const XML_Char **atts, + const char *name) { + return ExpatParser::GetAttribute(atts, name); + } + + gcc_pure + static const char *GetAttributeCase(const XML_Char **atts, + const char *name) { + return ExpatParser::GetAttributeCase(atts, name); + } + +protected: + virtual void StartElement(const XML_Char *name, + const XML_Char **atts) = 0; + virtual void EndElement(const XML_Char *name) = 0; + virtual void CharacterData(const XML_Char *s, int len) = 0; + +private: + static void XMLCALL StartElement(void *user_data, const XML_Char *name, + const XML_Char **atts) { + CommonExpatParser &p = *(CommonExpatParser *)user_data; + p.StartElement(name, atts); + } + + static void XMLCALL EndElement(void *user_data, const XML_Char *name) { + CommonExpatParser &p = *(CommonExpatParser *)user_data; + p.EndElement(name); + } + + static void XMLCALL CharacterData(void *user_data, + const XML_Char *s, int len) { + CommonExpatParser &p = *(CommonExpatParser *)user_data; + p.CharacterData(s, len); + } +}; + +#endif diff --git a/src/lib/smbclient/Init.cxx b/src/lib/smbclient/Init.cxx new file mode 100644 index 000000000..56e196364 --- /dev/null +++ b/src/lib/smbclient/Init.cxx @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Init.hxx" +#include "util/Error.hxx" + +#include <libsmbclient.h> + +#include <string.h> + +static void +mpd_smbc_get_auth_data(gcc_unused const char *srv, + gcc_unused const char *shr, + char *wg, gcc_unused int wglen, + char *un, gcc_unused int unlen, + char *pw, gcc_unused int pwlen) +{ + // TODO: implement + strcpy(wg, "WORKGROUP"); + strcpy(un, ""); + strcpy(pw, ""); +} + +bool +SmbclientInit(Error &error) +{ + constexpr int debug = 0; + if (smbc_init(mpd_smbc_get_auth_data, debug) < 0) { + error.SetErrno("smbc_init() failed"); + return false; + } + + return true; +} diff --git a/src/lib/smbclient/Init.hxx b/src/lib/smbclient/Init.hxx new file mode 100644 index 000000000..21014ec8d --- /dev/null +++ b/src/lib/smbclient/Init.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SMBCLIENT_INIT_HXX +#define MPD_SMBCLIENT_INIT_HXX + +#include "check.h" + +class Error; + +/** + * Initialize libsmbclient. + */ +bool +SmbclientInit(Error &error); + +#endif diff --git a/src/lib/upnp/Action.hxx b/src/lib/upnp/Action.hxx new file mode 100644 index 000000000..28c88be92 --- /dev/null +++ b/src/lib/upnp/Action.hxx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPNP_ACTION_HXX +#define MPD_UPNP_ACTION_HXX + +#include "Compiler.h" + +#include <upnp/upnptools.h> + +static inline constexpr unsigned +CountNameValuePairs() +{ + return 0; +} + +template<typename... Args> +static inline constexpr unsigned +CountNameValuePairs(gcc_unused const char *name, gcc_unused const char *value, + Args... args) +{ + return 1 + CountNameValuePairs(args...); +} + +/** + * A wrapper for UpnpMakeAction() that counts the number of name/value + * pairs and adds the nullptr sentinel. + */ +template<typename... Args> +static inline IXML_Document * +MakeActionHelper(const char *action_name, const char *service_type, + Args... args) +{ + const unsigned n = CountNameValuePairs(args...); + return UpnpMakeAction(action_name, service_type, n, + args..., + nullptr, nullptr); +} + +#endif diff --git a/src/lib/upnp/Callback.hxx b/src/lib/upnp/Callback.hxx new file mode 100644 index 000000000..85daf0a7e --- /dev/null +++ b/src/lib/upnp/Callback.hxx @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPNP_CALLBACK_HXX +#define MPD_UPNP_CALLBACK_HXX + +#include <upnp/upnp.h> + +/** + * A class that is supposed to be used for libupnp asynchronous + * callbacks. + */ +class UpnpCallback { +public: + /** + * Pass this value as "cookie" pointer to libupnp asynchronous + * functions. + */ + void *GetUpnpCookie() { + return this; + } + + static UpnpCallback &FromUpnpCookie(void *cookie) { + return *(UpnpCallback *)cookie; + } + + virtual int Invoke(Upnp_EventType et, void *evp) = 0; +}; + +#endif diff --git a/src/lib/upnp/ClientInit.cxx b/src/lib/upnp/ClientInit.cxx new file mode 100644 index 000000000..77d9cf03d --- /dev/null +++ b/src/lib/upnp/ClientInit.cxx @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ClientInit.hxx" +#include "Init.hxx" +#include "Callback.hxx" +#include "Domain.hxx" +#include "thread/Mutex.hxx" +#include "util/Error.hxx" + +#include <upnp/upnptools.h> + +static Mutex upnp_client_init_mutex; +static unsigned upnp_client_ref; +static UpnpClient_Handle upnp_client_handle; + +static int +UpnpClientCallback(Upnp_EventType et, void *evp, void *cookie) +{ + if (cookie == nullptr) + /* this is the cookie passed to UpnpRegisterClient(); + but can this ever happen? Will libupnp ever invoke + the registered callback without that cookie? */ + return UPNP_E_SUCCESS; + + UpnpCallback &callback = UpnpCallback::FromUpnpCookie(cookie); + return callback.Invoke(et, evp); +} + +static bool +DoInit(Error &error) +{ + auto code = UpnpRegisterClient(UpnpClientCallback, nullptr, + &upnp_client_handle); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpRegisterClient() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + return true; +} + +bool +UpnpClientGlobalInit(UpnpClient_Handle &handle, Error &error) +{ + if (!UpnpGlobalInit(error)) + return false; + + upnp_client_init_mutex.lock(); + bool success = upnp_client_ref > 0 || DoInit(error); + upnp_client_init_mutex.unlock(); + + if (success) { + ++upnp_client_ref; + handle = upnp_client_handle; + } else + UpnpGlobalFinish(); + + return success; +} + +void +UpnpClientGlobalFinish() +{ + upnp_client_init_mutex.lock(); + + assert(upnp_client_ref > 0); + if (--upnp_client_ref == 0) + UpnpUnRegisterClient(upnp_client_handle); + + upnp_client_init_mutex.unlock(); + + UpnpGlobalFinish(); +} diff --git a/src/lib/upnp/ClientInit.hxx b/src/lib/upnp/ClientInit.hxx new file mode 100644 index 000000000..645e64ca6 --- /dev/null +++ b/src/lib/upnp/ClientInit.hxx @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPNP_CLIENT_INIT_HXX +#define MPD_UPNP_CLIENT_INIT_HXX + +#include "check.h" + +#include <upnp/upnp.h> + +class Error; + +bool +UpnpClientGlobalInit(UpnpClient_Handle &handle, Error &error); + +void +UpnpClientGlobalFinish(); + +#endif diff --git a/src/lib/upnp/ContentDirectoryService.cxx b/src/lib/upnp/ContentDirectoryService.cxx new file mode 100644 index 000000000..ef8757ec5 --- /dev/null +++ b/src/lib/upnp/ContentDirectoryService.cxx @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ContentDirectoryService.hxx" +#include "Domain.hxx" +#include "Device.hxx" +#include "ixmlwrap.hxx" +#include "Util.hxx" +#include "Action.hxx" +#include "util/Error.hxx" + +ContentDirectoryService::ContentDirectoryService(const UPnPDevice &device, + const UPnPService &service) + :m_actionURL(caturl(device.URLBase, service.controlURL)), + m_serviceType(service.serviceType), + m_deviceId(device.UDN), + m_friendlyName(device.friendlyName), + m_manufacturer(device.manufacturer), + m_modelName(device.modelName), + m_rdreqcnt(200) +{ + if (!m_modelName.compare("MediaTomb")) { + // Readdir by 200 entries is good for most, but MediaTomb likes + // them really big. Actually 1000 is better but I don't dare + m_rdreqcnt = 500; + } +} + +ContentDirectoryService::~ContentDirectoryService() +{ + /* this destructor exists here just so it won't get inlined */ +} + +bool +ContentDirectoryService::getSearchCapabilities(UpnpClient_Handle hdl, + std::list<std::string> &result, + Error &error) const +{ + assert(result.empty()); + + IXML_Document *request = + UpnpMakeAction("GetSearchCapabilities", m_serviceType.c_str(), + 0, + nullptr, nullptr); + if (request == 0) { + error.Set(upnp_domain, "UpnpMakeAction() failed"); + return false; + } + + IXML_Document *response; + auto code = UpnpSendAction(hdl, m_actionURL.c_str(), + m_serviceType.c_str(), + 0 /*devUDN*/, request, &response); + ixmlDocument_free(request); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpSendAction() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + const char *s = ixmlwrap::getFirstElementValue(response, "SearchCaps"); + if (s == nullptr || *s == 0) { + ixmlDocument_free(response); + return true; + } + + bool success = true; + if (!csvToStrings(s, result)) { + error.Set(upnp_domain, "Bad response"); + success = false; + } + + ixmlDocument_free(response); + return success; +} diff --git a/src/lib/upnp/ContentDirectoryService.hxx b/src/lib/upnp/ContentDirectoryService.hxx new file mode 100644 index 000000000..0b03df2e7 --- /dev/null +++ b/src/lib/upnp/ContentDirectoryService.hxx @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _UPNPDIR_HXX_INCLUDED_ +#define _UPNPDIR_HXX_INCLUDED_ + +#include "Compiler.h" + +#include <upnp/upnp.h> + +#include <string> +#include <list> + +class Error; +class UPnPDevice; +struct UPnPService; +class UPnPDirContent; + +/** + * Content Directory Service class. + * + * This stores identity data from a directory service + * and the device it belongs to, and has methods to query + * the directory, using libupnp for handling the UPnP protocols. + * + * Note: m_rdreqcnt: number of entries requested per directory read. + * 0 means all entries. The device can still return less entries than + * requested, depending on its own limits. In general it's not optimal + * becauses it triggers issues, and is sometimes actually slower, e.g. on + * a D-Link NAS 327 + * + * The value chosen may affect by the UpnpSetMaxContentLength + * (2000*1024) done during initialization, but this should be ample + */ +class ContentDirectoryService { + std::string m_actionURL; + std::string m_serviceType; + std::string m_deviceId; + std::string m_friendlyName; + std::string m_manufacturer; + std::string m_modelName; + + int m_rdreqcnt; // Slice size to use when reading + +public: + /** + * Construct by copying data from device and service objects. + * + * The discovery service does this: use getDirServices() + */ + ContentDirectoryService(const UPnPDevice &device, + const UPnPService &service); + + /** An empty one */ + ContentDirectoryService() = default; + + ~ContentDirectoryService(); + + /** Read a container's children list into dirbuf. + * + * @param objectId the UPnP object Id for the container. Root has Id "0" + * @param[out] dirbuf stores the entries we read. + */ + bool readDir(UpnpClient_Handle handle, + const char *objectId, UPnPDirContent &dirbuf, + Error &error) const; + + bool readDirSlice(UpnpClient_Handle handle, + const char *objectId, unsigned offset, + unsigned count, UPnPDirContent& dirbuf, + unsigned &didread, unsigned &total, + Error &error) const; + + /** Search the content directory service. + * + * @param objectId the UPnP object Id under which the search + * should be done. Not all servers actually support this below + * root. Root has Id "0" + * @param searchstring an UPnP searchcriteria string. Check the + * UPnP document: UPnP-av-ContentDirectory-v1-Service-20020625.pdf + * section 2.5.5. Maybe we'll provide an easier way some day... + * @param[out] dirbuf stores the entries we read. + */ + bool search(UpnpClient_Handle handle, + const char *objectId, const char *searchstring, + UPnPDirContent &dirbuf, + Error &error) const; + + /** Read metadata for a given node. + * + * @param objectId the UPnP object Id. Root has Id "0" + * @param[out] dirbuf stores the entries we read. At most one entry will be + * returned. + */ + bool getMetadata(UpnpClient_Handle handle, + const char *objectId, UPnPDirContent &dirbuf, + Error &error) const; + + /** Retrieve search capabilities + * + * @param[out] result an empty vector: no search, or a single '*' element: + * any tag can be used in a search, or a list of usable tag names. + */ + bool getSearchCapabilities(UpnpClient_Handle handle, + std::list<std::string> &result, + Error &error) const; + + gcc_pure + std::string GetURI() const { + return "upnp://" + m_deviceId + "/" + m_serviceType; + } + + /** Retrieve the "friendly name" for this server, useful for display. */ + const char *getFriendlyName() const { + return m_friendlyName.c_str(); + } +}; + +#endif /* _UPNPDIR_HXX_INCLUDED_ */ diff --git a/src/lib/upnp/Device.cxx b/src/lib/upnp/Device.cxx new file mode 100644 index 000000000..26bffd0f0 --- /dev/null +++ b/src/lib/upnp/Device.cxx @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Device.hxx" +#include "Util.hxx" +#include "lib/expat/ExpatParser.hxx" +#include "util/Error.hxx" + +#include <stdlib.h> + +#include <string.h> + +UPnPDevice::~UPnPDevice() +{ + /* this destructor exists here just so it won't get inlined */ +} + +/** + * An XML parser which constructs an UPnP device object from the + * device descriptor. + */ +class UPnPDeviceParser final : public CommonExpatParser { + UPnPDevice &m_device; + + std::string *value; + + UPnPService m_tservice; + +public: + UPnPDeviceParser(UPnPDevice& device) + :m_device(device), + value(nullptr) {} + +protected: + virtual void StartElement(const XML_Char *name, const XML_Char **) { + value = nullptr; + + switch (name[0]) { + case 'c': + if (strcmp(name, "controlURL") == 0) + value = &m_tservice.controlURL; + break; + case 'd': + if (strcmp(name, "deviceType") == 0) + value = &m_device.deviceType; + break; + case 'f': + if (strcmp(name, "friendlyName") == 0) + value = &m_device.friendlyName; + break; + case 'm': + if (strcmp(name, "manufacturer") == 0) + value = &m_device.manufacturer; + else if (strcmp(name, "modelName") == 0) + value = &m_device.modelName; + break; + case 's': + if (strcmp(name, "serviceType") == 0) + value = &m_tservice.serviceType; + break; + case 'U': + if (strcmp(name, "UDN") == 0) + value = &m_device.UDN; + else if (strcmp(name, "URLBase") == 0) + value = &m_device.URLBase; + break; + } + } + + virtual void EndElement(const XML_Char *name) { + if (value != nullptr) { + trimstring(*value); + value = nullptr; + } else if (!strcmp(name, "service")) { + m_device.services.emplace_back(std::move(m_tservice)); + m_tservice.clear(); + } + } + + virtual void CharacterData(const XML_Char *s, int len) { + if (value != nullptr) + value->append(s, len); + } +}; + +bool +UPnPDevice::Parse(const std::string &url, const char *description, + Error &error) +{ + { + UPnPDeviceParser mparser(*this); + if (!mparser.Parse(description, strlen(description), + true, error)) + return false; + } + + if (URLBase.empty()) { + // The standard says that if the URLBase value is empty, we should use + // the url the description was retrieved from. However this is + // sometimes something like http://host/desc.xml, sometimes something + // like http://host/ + + if (url.size() < 8) { + // ??? + URLBase = url; + } else { + auto hostslash = url.find_first_of("/", 7); + if (hostslash == std::string::npos || hostslash == url.size()-1) { + URLBase = url; + } else { + URLBase = path_getfather(url); + } + } + } + + return true; +} diff --git a/src/lib/upnp/Device.hxx b/src/lib/upnp/Device.hxx new file mode 100644 index 000000000..dd7ecac2d --- /dev/null +++ b/src/lib/upnp/Device.hxx @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _UPNPDEV_HXX_INCLUDED_ +#define _UPNPDEV_HXX_INCLUDED_ + +#include <vector> +#include <string> + +class Error; + +/** + * UPnP Description phase: interpreting the device description which we + * downloaded from the URL obtained by the discovery phase. + */ + +/** + * Data holder for a UPnP service, parsed from the XML description + * downloaded after discovery yielded its URL. + */ +struct UPnPService { + // e.g. urn:schemas-upnp-org:service:ConnectionManager:1 + std::string serviceType; + std::string controlURL; // e.g.: /upnp/control/cm + + void clear() + { + serviceType.clear(); + controlURL.clear(); + } +}; + +/** + * Data holder for a UPnP device, parsed from the XML description obtained + * during discovery. + * A device may include several services. To be of interest to us, + * one of them must be a ContentDirectory. + */ +class UPnPDevice { +public: + // e.g. urn:schemas-upnp-org:device:MediaServer:1 + std::string deviceType; + // e.g. MediaTomb + std::string friendlyName; + // Unique device number. This should match the deviceID in the + // discovery message. e.g. uuid:a7bdcd12-e6c1-4c7e-b588-3bbc959eda8d + std::string UDN; + // Base for all relative URLs. e.g. http://192.168.4.4:49152/ + std::string URLBase; + // Manufacturer: e.g. D-Link, PacketVideo ("manufacturer") + std::string manufacturer; + // Model name: e.g. MediaTomb, DNS-327L ("modelName") + std::string modelName; + // Services provided by this device. + std::vector<UPnPService> services; + + UPnPDevice() = default; + UPnPDevice(const UPnPDevice &) = delete; + UPnPDevice(UPnPDevice &&) = default; + UPnPDevice &operator=(UPnPDevice &&) = default; + + ~UPnPDevice(); + + /** Build device from xml description downloaded from discovery + * @param url where the description came from + * @param description the xml device description + */ + bool Parse(const std::string &url, const char *description, + Error &error); +}; + +#endif /* _UPNPDEV_HXX_INCLUDED_ */ diff --git a/src/lib/upnp/Discovery.cxx b/src/lib/upnp/Discovery.cxx new file mode 100644 index 000000000..9ea78c624 --- /dev/null +++ b/src/lib/upnp/Discovery.cxx @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Discovery.hxx" +#include "Domain.hxx" +#include "ContentDirectoryService.hxx" +#include "system/Clock.hxx" +#include "Log.hxx" + +#include <upnp/upnptools.h> + +#include <string.h> + +// The service type string we are looking for. +static constexpr char ContentDirectorySType[] = "urn:schemas-upnp-org:service:ContentDirectory:1"; + +// We don't include a version in comparisons, as we are satisfied with +// version 1 +gcc_pure +static bool +isCDService(const char *st) +{ + constexpr size_t sz = sizeof(ContentDirectorySType) - 3; + return memcmp(ContentDirectorySType, st, sz) == 0; +} + +// The type of device we're asking for in search +static constexpr char MediaServerDType[] = "urn:schemas-upnp-org:device:MediaServer:1"; + +gcc_pure +static bool +isMSDevice(const char *st) +{ + constexpr size_t sz = sizeof(MediaServerDType) - 3; + return memcmp(MediaServerDType, st, sz) == 0; +} + +static void +AnnounceFoundUPnP(UPnPDiscoveryListener &listener, const UPnPDevice &device) +{ + for (const auto &service : device.services) + if (isCDService(service.serviceType.c_str())) + listener.FoundUPnP(ContentDirectoryService(device, + service)); +} + +static void +AnnounceLostUPnP(UPnPDiscoveryListener &listener, const UPnPDevice &device) +{ + for (const auto &service : device.services) + if (isCDService(service.serviceType.c_str())) + listener.LostUPnP(ContentDirectoryService(device, + service)); +} + +inline void +UPnPDeviceDirectory::LockAdd(ContentDirectoryDescriptor &&d) +{ + const ScopeLock protect(mutex); + + for (auto &i : directories) { + if (i.id == d.id) { + i = std::move(d); + return; + } + } + + directories.emplace_back(std::move(d)); + + if (listener != nullptr) + AnnounceFoundUPnP(*listener, directories.back().device); +} + +inline void +UPnPDeviceDirectory::LockRemove(const std::string &id) +{ + const ScopeLock protect(mutex); + + for (auto i = directories.begin(), end = directories.end(); + i != end; ++i) { + if (i->id == id) { + if (listener != nullptr) + AnnounceLostUPnP(*listener, i->device); + + directories.erase(i); + break; + } + } +} + +inline void +UPnPDeviceDirectory::discoExplorer() +{ + for (;;) { + DiscoveredTask *tsk = 0; + if (!discoveredQueue.take(tsk)) { + discoveredQueue.workerExit(); + return; + } + + // Device signals its existence and well-being. Perform the + // UPnP "description" phase by downloading and decoding the + // description document. + char *buf; + // LINE_SIZE is defined by libupnp's upnp.h... + char contentType[LINE_SIZE]; + int code = UpnpDownloadUrlItem(tsk->url.c_str(), &buf, contentType); + if (code != UPNP_E_SUCCESS) { + continue; + } + + // Update or insert the device + ContentDirectoryDescriptor d(std::move(tsk->deviceId), + MonotonicClockS(), tsk->expires); + + { + Error error2; + bool success = d.Parse(tsk->url, buf, error2); + free(buf); + if (!success) { + delete tsk; + LogError(error2); + continue; + } + } + + LockAdd(std::move(d)); + delete tsk; + } +} + +void * +UPnPDeviceDirectory::discoExplorer(void *ctx) +{ + UPnPDeviceDirectory &directory = *(UPnPDeviceDirectory *)ctx; + directory.discoExplorer(); + return (void*)1; +} + +inline int +UPnPDeviceDirectory::OnAlive(Upnp_Discovery *disco) +{ + if (isMSDevice(disco->DeviceType) || + isCDService(disco->ServiceType)) { + DiscoveredTask *tp = new DiscoveredTask(disco); + if (discoveredQueue.put(tp)) + return UPNP_E_FINISH; + } + + return UPNP_E_SUCCESS; +} + +inline int +UPnPDeviceDirectory::OnByeBye(Upnp_Discovery *disco) +{ + if (isMSDevice(disco->DeviceType) || + isCDService(disco->ServiceType)) { + // Device signals it is going off. + LockRemove(disco->DeviceId); + } + + return UPNP_E_SUCCESS; +} + +// This gets called for all libupnp asynchronous events, in a libupnp +// thread context. +// Example: ContentDirectories appearing and disappearing from the network +// We queue a task for our worker thread(s) +int +UPnPDeviceDirectory::Invoke(Upnp_EventType et, void *evp) +{ + switch (et) { + case UPNP_DISCOVERY_SEARCH_RESULT: + case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE: + { + Upnp_Discovery *disco = (Upnp_Discovery *)evp; + return OnAlive(disco); + } + + case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE: + { + Upnp_Discovery *disco = (Upnp_Discovery *)evp; + return OnByeBye(disco); + } + + default: + // Ignore other events for now + break; + } + + return UPNP_E_SUCCESS; +} + +bool +UPnPDeviceDirectory::expireDevices(Error &error) +{ + const ScopeLock protect(mutex); + const unsigned now = MonotonicClockS(); + bool didsomething = false; + + for (auto it = directories.begin(); + it != directories.end();) { + if (now > it->expires) { + it = directories.erase(it); + didsomething = true; + } else { + it++; + } + } + + if (didsomething) + return search(error); + + return true; +} + +UPnPDeviceDirectory::UPnPDeviceDirectory(UpnpClient_Handle _handle, + UPnPDiscoveryListener *_listener) + :handle(_handle), + listener(_listener), + discoveredQueue("DiscoveredQueue"), + m_searchTimeout(2), m_lastSearch(0) +{ +} + +UPnPDeviceDirectory::~UPnPDeviceDirectory() +{ + /* this destructor exists here just so it won't get inlined */ +} + +bool +UPnPDeviceDirectory::Start(Error &error) +{ + if (!discoveredQueue.start(1, discoExplorer, this)) { + error.Set(upnp_domain, "Discover work queue start failed"); + return false; + } + + return search(error); +} + +bool +UPnPDeviceDirectory::search(Error &error) +{ + const unsigned now = MonotonicClockS(); + if (now - m_lastSearch < 10) + return true; + m_lastSearch = now; + + // We search both for device and service just in case. + int code = UpnpSearchAsync(handle, m_searchTimeout, + ContentDirectorySType, GetUpnpCookie()); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpSearchAsync() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + code = UpnpSearchAsync(handle, m_searchTimeout, + MediaServerDType, GetUpnpCookie()); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpSearchAsync() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + return true; +} + +bool +UPnPDeviceDirectory::getDirServices(std::vector<ContentDirectoryService> &out, + Error &error) +{ + // Has locking, do it before our own lock + if (!expireDevices(error)) + return false; + + const ScopeLock protect(mutex); + + for (auto dit = directories.begin(); + dit != directories.end(); dit++) { + for (const auto &service : dit->device.services) { + if (isCDService(service.serviceType.c_str())) { + out.emplace_back(dit->device, service); + } + } + } + + return true; +} + +bool +UPnPDeviceDirectory::getServer(const char *friendlyName, + ContentDirectoryService &server, + Error &error) +{ + // Has locking, do it before our own lock + if (!expireDevices(error)) + return false; + + const ScopeLock protect(mutex); + + for (const auto &i : directories) { + const auto &device = i.device; + + if (device.friendlyName != friendlyName) + continue; + + for (const auto &service : device.services) { + if (isCDService(service.serviceType.c_str())) { + server = ContentDirectoryService(device, + service); + return true; + } + } + } + + error.Set(upnp_domain, "Server not found"); + return false; +} diff --git a/src/lib/upnp/Discovery.hxx b/src/lib/upnp/Discovery.hxx new file mode 100644 index 000000000..af07daf61 --- /dev/null +++ b/src/lib/upnp/Discovery.hxx @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _UPNPPDISC_H_X_INCLUDED_ +#define _UPNPPDISC_H_X_INCLUDED_ + +#include "Callback.hxx" +#include "Device.hxx" +#include "WorkQueue.hxx" +#include "thread/Mutex.hxx" +#include "util/Error.hxx" + +#include <upnp/upnp.h> + +#include <list> +#include <vector> +#include <string> + +class ContentDirectoryService; + +class UPnPDiscoveryListener { +public: + virtual void FoundUPnP(const ContentDirectoryService &service) = 0; + virtual void LostUPnP(const ContentDirectoryService &service) = 0; +}; + +/** + * Manage UPnP discovery and maintain a directory of active devices. Singleton. + * + * We are only interested in MediaServers with a ContentDirectory service + * for now, but this could be made more general, by removing the filtering. + */ +class UPnPDeviceDirectory final : UpnpCallback { + /** + * Each appropriate discovery event (executing in a libupnp thread + * context) queues the following task object for processing by the + * discovery thread. + */ + struct DiscoveredTask { + std::string url; + std::string deviceId; + unsigned expires; // Seconds valid + + DiscoveredTask(const Upnp_Discovery *disco) + :url(disco->Location), + deviceId(disco->DeviceId), + expires(disco->Expires) {} + }; + + /** + * Descriptor for one device having a Content Directory + * service found on the network. + */ + class ContentDirectoryDescriptor { + public: + std::string id; + + UPnPDevice device; + + /** + * The MonotonicClockS() time stamp when this device + * expires. + */ + unsigned expires; + + ContentDirectoryDescriptor() = default; + + ContentDirectoryDescriptor(std::string &&_id, + unsigned last, int exp) + :id(std::move(_id)), expires(last + exp + 20) {} + + bool Parse(const std::string &url, const char *description, + Error &_error) { + return device.Parse(url, description, _error); + } + }; + + const UpnpClient_Handle handle; + UPnPDiscoveryListener *const listener; + + Mutex mutex; + std::list<ContentDirectoryDescriptor> directories; + WorkQueue<DiscoveredTask *> discoveredQueue; + + /** + * The UPnP device search timeout, which should actually be + * called delay because it's the base of a random delay that + * the devices apply to avoid responding all at the same time. + */ + int m_searchTimeout; + + /** + * The MonotonicClockS() time stamp of the last search. + */ + unsigned m_lastSearch; + +public: + UPnPDeviceDirectory(UpnpClient_Handle _handle, + UPnPDiscoveryListener *_listener=nullptr); + ~UPnPDeviceDirectory(); + + UPnPDeviceDirectory(const UPnPDeviceDirectory &) = delete; + UPnPDeviceDirectory& operator=(const UPnPDeviceDirectory &) = delete; + + bool Start(Error &error); + + /** Retrieve the directory services currently seen on the network */ + bool getDirServices(std::vector<ContentDirectoryService> &, Error &); + + /** + * Get server by friendly name. + */ + bool getServer(const char *friendlyName, + ContentDirectoryService &server, + Error &error); + +private: + bool search(Error &error); + + /** + * Look at the devices and get rid of those which have not + * been seen for too long. We do this when listing the top + * directory. + */ + bool expireDevices(Error &error); + + void LockAdd(ContentDirectoryDescriptor &&d); + void LockRemove(const std::string &id); + + /** + * Worker routine for the discovery queue. Get messages about + * devices appearing and disappearing, and update the + * directory pool accordingly. + */ + static void *discoExplorer(void *); + void discoExplorer(); + + int OnAlive(Upnp_Discovery *disco); + int OnByeBye(Upnp_Discovery *disco); + int cluCallBack(Upnp_EventType et, void *evp); + + /* virtual methods from class UpnpCallback */ + virtual int Invoke(Upnp_EventType et, void *evp) override; +}; + + +#endif /* _UPNPPDISC_H_X_INCLUDED_ */ diff --git a/src/lib/upnp/Domain.cxx b/src/lib/upnp/Domain.cxx new file mode 100644 index 000000000..010d4c7c2 --- /dev/null +++ b/src/lib/upnp/Domain.cxx @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "Domain.hxx" +#include "util/Domain.hxx" + +const Domain upnp_domain("upnp"); diff --git a/src/lib/upnp/Domain.hxx b/src/lib/upnp/Domain.hxx new file mode 100644 index 000000000..ec01ef735 --- /dev/null +++ b/src/lib/upnp/Domain.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPNP_DOMAIN_HXX +#define MPD_UPNP_DOMAIN_HXX + +class Domain; + +extern const Domain upnp_domain; + +#endif diff --git a/src/lib/upnp/Init.cxx b/src/lib/upnp/Init.cxx new file mode 100644 index 000000000..4fc606de9 --- /dev/null +++ b/src/lib/upnp/Init.cxx @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Init.hxx" +#include "Domain.hxx" +#include "thread/Mutex.hxx" +#include "util/Error.hxx" + +#include <upnp/upnp.h> +#include <upnp/upnptools.h> +#include <upnp/ixml.h> + +static Mutex upnp_init_mutex; +static unsigned upnp_ref; + +static bool +DoInit(Error &error) +{ + auto code = UpnpInit(0, 0); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpInit() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + UpnpSetMaxContentLength(2000*1024); + + // Servers sometimes make error (e.g.: minidlna returns bad utf-8) + ixmlRelaxParser(1); + + return true; +} + +bool +UpnpGlobalInit(Error &error) +{ + const ScopeLock protect(upnp_init_mutex); + + if (upnp_ref == 0 && !DoInit(error)) + return false; + + ++upnp_ref; + return true; +} + +void +UpnpGlobalFinish() +{ + const ScopeLock protect(upnp_init_mutex); + + assert(upnp_ref > 0); + + if (--upnp_ref == 0) + UpnpFinish(); +} diff --git a/src/lib/upnp/Init.hxx b/src/lib/upnp/Init.hxx new file mode 100644 index 000000000..b23f8e2ab --- /dev/null +++ b/src/lib/upnp/Init.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPNP_INIT_HXX +#define MPD_UPNP_INIT_HXX + +#include "check.h" + +class Error; + +bool +UpnpGlobalInit(Error &error); + +void +UpnpGlobalFinish(); + +#endif diff --git a/src/lib/upnp/Util.cxx b/src/lib/upnp/Util.cxx new file mode 100644 index 000000000..cf34a47d3 --- /dev/null +++ b/src/lib/upnp/Util.cxx @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "Util.hxx" + +#include <upnp/ixml.h> + +#include <assert.h> + +/** Get rid of white space at both ends */ +void +trimstring(std::string &s, const char *ws) +{ + auto pos = s.find_first_not_of(ws); + if (pos == std::string::npos) { + s.clear(); + return; + } + s.replace(0, pos, std::string()); + + pos = s.find_last_not_of(ws); + if (pos != std::string::npos && pos != s.length()-1) + s.replace(pos + 1, std::string::npos, std::string()); +} + +std::string +caturl(const std::string &s1, const std::string &s2) +{ + if (s2.front() == '/') { + /* absolute path: replace the whole URI path in s1 */ + + auto i = s1.find("://"); + if (i == s1.npos) + /* no scheme: override s1 completely */ + return s2; + + /* find the first slash after the host part */ + i = s1.find('/', i + 3); + if (i == s1.npos) + /* there's no URI path - simply append s2 */ + i = s1.length(); + + return s1.substr(0, i) + s2; + } + + std::string out(s1); + if (out.back() != '/') + out.push_back('/'); + + out += s2; + return out; +} + +static void +path_catslash(std::string &s) +{ + if (s.empty() || s.back() != '/') + s += '/'; +} + +std::string +path_getfather(const std::string &s) +{ + std::string father = s; + + // ?? + if (father.empty()) + return "./"; + + if (father.back() == '/') { + // Input ends with /. Strip it, handle special case for root + if (father.length() == 1) + return father; + father.erase(father.length()-1); + } + + auto slp = father.rfind('/'); + if (slp == std::string::npos) + return "./"; + + father.erase(slp); + path_catslash(father); + return father; +} + +std::list<std::string> +stringToTokens(const std::string &str, + const char *delims, bool skipinit) +{ + std::list<std::string> tokens; + + std::string::size_type startPos = 0; + + // Skip initial delims, return empty if this eats all. + if (skipinit && + (startPos = str.find_first_not_of(delims, 0)) == std::string::npos) + return tokens; + + while (startPos < str.size()) { + // Find next delimiter or end of string (end of token) + auto pos = str.find_first_of(delims, startPos); + + // Add token to the vector and adjust start + if (pos == std::string::npos) { + tokens.emplace_back(str, startPos); + break; + } else if (pos == startPos) { + // Dont' push empty tokens after first + if (tokens.empty()) + tokens.emplace_back(); + startPos = ++pos; + } else { + tokens.emplace_back(str, startPos, pos - startPos); + startPos = ++pos; + } + } + + return tokens; +} + +template <class T> +bool +csvToStrings(const char *s, T &tokens) +{ + assert(tokens.empty()); + + std::string current; + + while (true) { + char ch = *s++; + if (ch == 0) { + tokens.emplace_back(std::move(current)); + return true; + } + + if (ch == '\\') { + ch = *s++; + if (ch == 0) + return false; + } else if (ch == ',') { + tokens.emplace_back(std::move(current)); + current.clear(); + continue; + } + + current.push_back(ch); + } +} + +template bool csvToStrings<std::list<std::string>>(const char *, std::list<std::string> &); diff --git a/src/lib/upnp/Util.hxx b/src/lib/upnp/Util.hxx new file mode 100644 index 000000000..58e382faa --- /dev/null +++ b/src/lib/upnp/Util.hxx @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPNP_UTIL_HXX +#define MPD_UPNP_UTIL_HXX + +#include "Compiler.h" + +#include <string> +#include <list> + +std::string +caturl(const std::string& s1, const std::string& s2); + +void +trimstring(std::string &s, const char *ws = " \t\n"); + +std::string +path_getfather(const std::string &s); + +gcc_pure +std::list<std::string> +stringToTokens(const std::string &str, + const char *delims = "/", bool skipinit = true); + +template <class T> +bool +csvToStrings(const char *s, T &tokens); + +#endif /* _UPNPP_H_X_INCLUDED_ */ diff --git a/src/lib/upnp/WorkQueue.hxx b/src/lib/upnp/WorkQueue.hxx new file mode 100644 index 000000000..fe8ce53f9 --- /dev/null +++ b/src/lib/upnp/WorkQueue.hxx @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _WORKQUEUE_H_INCLUDED_ +#define _WORKQUEUE_H_INCLUDED_ + +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" + +#include <assert.h> +#include <pthread.h> + +#include <string> +#include <queue> + +#define LOGINFO(X) +#define LOGERR(X) + +/** + * A WorkQueue manages the synchronisation around a queue of work items, + * where a number of client threads queue tasks and a number of worker + * threads take and execute them. The goal is to introduce some level + * of parallelism between the successive steps of a previously single + * threaded pipeline. For example data extraction / data preparation / index + * update, but this could have other uses. + * + * There is no individual task status return. In case of fatal error, + * the client or worker sets an end condition on the queue. A second + * queue could conceivably be used for returning individual task + * status. + */ +template <class T> +class WorkQueue { + // Configuration + const std::string name; + + // Status + // Worker threads having called exit + unsigned n_workers_exited; + bool ok; + + unsigned n_threads; + pthread_t *threads; + + // Synchronization + std::queue<T> queue; + Cond client_cond; + Cond worker_cond; + Mutex mutex; + +public: + /** Create a WorkQueue + * @param name for message printing + * @param hi number of tasks on queue before clients blocks. Default 0 + * meaning no limit. hi == -1 means that the queue is disabled. + * @param lo minimum count of tasks before worker starts. Default 1. + */ + WorkQueue(const char *_name) + :name(_name), + n_workers_exited(0), + ok(false), + n_threads(0), threads(nullptr) + { + } + + ~WorkQueue() { + setTerminateAndWait(); + } + + /** Start the worker threads. + * + * @param nworkers number of threads copies to start. + * @param start_routine thread function. It should loop + * taking (QueueWorker::take()) and executing tasks. + * @param arg initial parameter to thread function. + * @return true if ok. + */ + bool start(unsigned nworkers, void *(*workproc)(void *), void *arg) + { + const ScopeLock protect(mutex); + + assert(nworkers > 0); + assert(!ok); + assert(n_threads == 0); + assert(threads == nullptr); + + n_threads = nworkers; + threads = new pthread_t[n_threads]; + + for (unsigned i = 0; i < nworkers; i++) { + int err; + if ((err = pthread_create(&threads[i], 0, workproc, arg))) { + LOGERR(("WorkQueue:%s: pthread_create failed, err %d\n", + name.c_str(), err)); + return false; + } + } + + ok = true; + return true; + } + + /** Add item to work queue, called from client. + * + * Sleeps if there are already too many. + */ + template<typename U> + bool put(U &&u) + { + const ScopeLock protect(mutex); + + queue.emplace(std::forward<U>(u)); + + // Just wake one worker, there is only one new task. + worker_cond.signal(); + + return true; + } + + + /** Tell the workers to exit, and wait for them. + */ + void setTerminateAndWait() + { + const ScopeLock protect(mutex); + + // Wait for all worker threads to have called workerExit() + ok = false; + while (n_workers_exited < n_threads) { + worker_cond.broadcast(); + client_cond.wait(mutex); + } + + // Perform the thread joins and compute overall status + // Workers return (void*)1 if ok + for (unsigned i = 0; i < n_threads; ++i) { + void *status; + pthread_join(threads[i], &status); + } + + delete[] threads; + threads = nullptr; + n_threads = 0; + + // Reset to start state. + n_workers_exited = 0; + } + + /** Take task from queue. Called from worker. + * + * Sleeps if there are not enough. Signal if we go to sleep on empty + * queue: client may be waiting for our going idle. + */ + bool take(T &tp) + { + const ScopeLock protect(mutex); + + if (!ok) + return false; + + while (queue.empty()) { + worker_cond.wait(mutex); + if (!ok) + return false; + } + + tp = std::move(queue.front()); + queue.pop(); + return true; + } + + /** Advertise exit and abort queue. Called from worker + * + * This would happen after an unrecoverable error, or when + * the queue is terminated by the client. Workers never exit normally, + * except when the queue is shut down (at which point ok is set to + * false by the shutdown code anyway). The thread must return/exit + * immediately after calling this. + */ + void workerExit() + { + const ScopeLock protect(mutex); + + n_workers_exited++; + ok = false; + client_cond.broadcast(); + } +}; + +#endif /* _WORKQUEUE_H_INCLUDED_ */ diff --git a/src/lib/upnp/ixmlwrap.cxx b/src/lib/upnp/ixmlwrap.cxx new file mode 100644 index 000000000..6a2829cf9 --- /dev/null +++ b/src/lib/upnp/ixmlwrap.cxx @@ -0,0 +1,44 @@ +/* Copyright (C) 2013 J.F.Dockes + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "ixmlwrap.hxx" + +namespace ixmlwrap { + +const char * +getFirstElementValue(IXML_Document *doc, const char *name) +{ + const char *ret = nullptr; + IXML_NodeList *nodes = + ixmlDocument_getElementsByTagName(doc, name); + + if (nodes) { + IXML_Node *first = ixmlNodeList_item(nodes, 0); + if (first) { + IXML_Node *dnode = ixmlNode_getFirstChild(first); + if (dnode) { + ret = ixmlNode_getNodeValue(dnode); + } + } + + ixmlNodeList_free(nodes); + } + + return ret; +} + +} diff --git a/src/lib/upnp/ixmlwrap.hxx b/src/lib/upnp/ixmlwrap.hxx new file mode 100644 index 000000000..0d519a323 --- /dev/null +++ b/src/lib/upnp/ixmlwrap.hxx @@ -0,0 +1,35 @@ +/* Copyright (C) 2013 J.F.Dockes + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef _IXMLWRAP_H_INCLUDED_ +#define _IXMLWRAP_H_INCLUDED_ + +#include <upnp/ixml.h> + +#include <string> + +namespace ixmlwrap { + /** + * Retrieve the text content for the first element of given + * name. Returns nullptr if the element does not + * contain a text node + */ + const char *getFirstElementValue(IXML_Document *doc, + const char *name); + +}; + +#endif /* _IXMLWRAP_H_INCLUDED_ */ diff --git a/src/ls.cxx b/src/ls.cxx index b1a636416..7d8a3a447 100644 --- a/src/ls.cxx +++ b/src/ls.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,14 +19,11 @@ #include "config.h" #include "ls.hxx" +#include "util/StringUtil.hxx" #include "util/UriUtil.hxx" -#include "Client.hxx" - -#include <glib.h> +#include "client/Client.hxx" #include <assert.h> -#include <string.h> - /** * file:// is not included in remoteUrlPrefixes, the connection method @@ -52,12 +49,18 @@ static const char *remoteUrlPrefixes[] = { "rtmpt://", "rtmps://", #endif +#ifdef ENABLE_SMBCLIENT + "smb://", +#endif #ifdef ENABLE_CDIO_PARANOIA "cdda://", #endif #ifdef ENABLE_DESPOTIFY "spt://", #endif +#ifdef HAVE_ALSA + "alsa://", +#endif NULL }; @@ -92,7 +95,7 @@ bool uri_supported_scheme(const char *uri) assert(uri_has_scheme(uri)); while (*urlPrefixes) { - if (g_str_has_prefix(uri, *urlPrefixes)) + if (StringStartsWith(uri, *urlPrefixes)) return true; urlPrefixes++; } diff --git a/src/ls.hxx b/src/ls.hxx index 3879563ee..f4b9be967 100644 --- a/src/ls.hxx +++ b/src/ls.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,8 @@ #ifndef MPD_LS_HXX #define MPD_LS_HXX +#include "Compiler.h" + #include <stdio.h> class Client; @@ -29,6 +31,7 @@ class Client; * It is not allowed to pass an URI without a scheme, check with * uri_has_scheme() first. */ +gcc_pure bool uri_supported_scheme(const char *url); /** diff --git a/src/mixer/AlsaMixerPlugin.cxx b/src/mixer/AlsaMixerPlugin.cxx deleted file mode 100644 index 4a4ca433c..000000000 --- a/src/mixer/AlsaMixerPlugin.cxx +++ /dev/null @@ -1,404 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "event/Call.hxx" -#include "util/ASCII.hxx" -#include "util/ReusableArray.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#include <algorithm> - -#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) { -#ifdef USE_EPOLL - _loop.AddCall([this](){ InvalidateSockets(); }); -#else - _loop.AddIdle(InitAlsaMixerMonitor, this); -#endif - } - -private: -#ifndef USE_EPOLL - static gboolean InitAlsaMixerMonitor(gpointer data) { - AlsaMixerMonitor &amm = *(AlsaMixerMonitor *)data; - amm.InvalidateSockets(); - return false; - } -#endif - - 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 != nullptr; elem = snd_mixer_elem_next(elem)) { - if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE && - StringEqualsCaseASCII(snd_mixer_selem_get_name(elem), - name) && - snd_mixer_selem_get_index(elem) == idx) - return elem; - } - - return nullptr; -} - -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, nullptr, - nullptr)) < 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 == nullptr) { - 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 != nullptr); - - delete monitor; - - snd_mixer_elem_set_callback(elem, nullptr); - 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 != nullptr); - - 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 != nullptr); - - 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/MixerAll.cxx b/src/mixer/MixerAll.cxx new file mode 100644 index 000000000..3cc92baee --- /dev/null +++ b/src/mixer/MixerAll.cxx @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "output/OutputAll.hxx" +#include "output/OutputInternal.hxx" +#include "pcm/Volume.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 == nullptr) + 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 == nullptr) + 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 == nullptr || !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 != nullptr && + output->mixer->plugin == &software_mixer_plugin) + mixer_set_volume(output->mixer, volume, IgnoreError()); + } +} diff --git a/src/mixer/MixerAll.hxx b/src/mixer/MixerAll.hxx new file mode 100644 index 000000000..f868f64b5 --- /dev/null +++ b/src/mixer/MixerAll.hxx @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + +#include "Compiler.h" + +/** + * Returns the average volume of all available mixers (range 0..100). + * Returns -1 if no mixer can be queried. + */ +gcc_pure +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. + */ +gcc_pure +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/MixerControl.cxx b/src/mixer/MixerControl.cxx new file mode 100644 index 000000000..bdf6fcc2b --- /dev/null +++ b/src/mixer/MixerControl.cxx @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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> + +Mixer * +mixer_new(const struct mixer_plugin *plugin, void *ao, + const config_param ¶m, + Error &error) +{ + Mixer *mixer; + + assert(plugin != nullptr); + + mixer = plugin->init(ao, param, error); + + assert(mixer == nullptr || mixer->IsPlugin(*plugin)); + + return mixer; +} + +void +mixer_free(Mixer *mixer) +{ + assert(mixer != nullptr); + assert(mixer->plugin != nullptr); + + /* 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 != nullptr); + assert(mixer->plugin != nullptr); + + const ScopeLock protect(mixer->mutex); + + if (mixer->open) + success = true; + else if (mixer->plugin->open == nullptr) + 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 != nullptr); + assert(mixer->plugin != nullptr); + assert(mixer->open); + + if (mixer->plugin->close != nullptr) + mixer->plugin->close(mixer); + + mixer->open = false; +} + +void +mixer_close(Mixer *mixer) +{ + assert(mixer != nullptr); + assert(mixer->plugin != nullptr); + + 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 != nullptr); + + 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 != nullptr); + 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/mixer/MixerControl.hxx b/src/mixer/MixerControl.hxx new file mode 100644 index 000000000..caa1c3054 --- /dev/null +++ b/src/mixer/MixerControl.hxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 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/mixer/MixerInternal.hxx b/src/mixer/MixerInternal.hxx new file mode 100644 index 000000000..1732b4f3b --- /dev/null +++ b/src/mixer/MixerInternal.hxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/mixer/MixerList.hxx b/src/mixer/MixerList.hxx new file mode 100644 index 000000000..eb3dba938 --- /dev/null +++ b/src/mixer/MixerList.hxx @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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/mixer/MixerPlugin.hxx b/src/mixer/MixerPlugin.hxx new file mode 100644 index 000000000..b29ecbab8 --- /dev/null +++ b/src/mixer/MixerPlugin.hxx @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + * nullptr to ignore errors + * @return a mixer object, or nullptr 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 + * nullptr 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 + * nullptr 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 + * nullptr 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/mixer/MixerType.cxx b/src/mixer/MixerType.cxx new file mode 100644 index 000000000..cd45db0d9 --- /dev/null +++ b/src/mixer/MixerType.cxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/mixer/MixerType.hxx b/src/mixer/MixerType.hxx new file mode 100644 index 000000000..bfa2637b7 --- /dev/null +++ b/src/mixer/MixerType.hxx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/mixer/OssMixerPlugin.cxx b/src/mixer/OssMixerPlugin.cxx deleted file mode 100644 index 0a459bc97..000000000 --- a/src/mixer/OssMixerPlugin.cxx +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/ASCII.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#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 (StringEqualsCaseASCII(name, labels[i], name_length) && - (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 deleted file mode 100644 index ff10256cb..000000000 --- a/src/mixer/PulseMixerPlugin.cxx +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 <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 == nullptr) { - 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 != nullptr); - assert(stream != nullptr); - 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 == nullptr) { - 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 != nullptr); - - o = pa_context_subscribe(context, - (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT, - nullptr, nullptr); - if (o == nullptr) { - 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 == nullptr) { - 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 deleted file mode 100644 index fa73e0f5e..000000000 --- a/src/mixer/PulseMixerPlugin.hxx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 6bd700551..000000000 --- a/src/mixer/RoarMixerPlugin.cxx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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 deleted file mode 100644 index 193e68f23..000000000 --- a/src/mixer/SoftwareMixerPlugin.cxx +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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; - - /** - * If this is true, then this object "owns" the #Filter - * instance (see above). It will be set to false by - * software_mixer_get_filter(); after that, the caller will be - * responsible for the #Filter. - */ - bool owns_filter; - - unsigned volume; - - SoftwareMixer() - :Mixer(software_mixer_plugin), - filter(CreateVolumeFilter()), - owns_filter(true), - volume(100) - { - assert(filter != nullptr); - } - - ~SoftwareMixer() { - if (owns_filter) - 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)); - assert(sm->owns_filter); - - sm->owns_filter = false; - return sm->filter; -} diff --git a/src/mixer/SoftwareMixerPlugin.hxx b/src/mixer/SoftwareMixerPlugin.hxx deleted file mode 100644 index be59c08db..000000000 --- a/src/mixer/SoftwareMixerPlugin.hxx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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/Volume.cxx b/src/mixer/Volume.cxx new file mode 100644 index 000000000..4c897b21f --- /dev/null +++ b/src/mixer/Volume.cxx @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/StringUtil.hxx" +#include "util/Domain.hxx" +#include "system/PeriodClock.hxx" +#include "Log.hxx" + +#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 PeriodClock hardware_volume_clock; + +/** + * 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_init(void) +{ + GlobalEvents::Register(GlobalEvents::MIXER, mixer_event_callback); +} + +int volume_level_get(void) +{ + if (last_hardware_volume >= 0 && + !hardware_volume_clock.CheckUpdate(1000)) + /* throttle access to hardware mixers */ + return last_hardware_volume; + + last_hardware_volume = mixer_all_get_volume(); + 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 = nullptr; + long int sv; + + if (!StringStartsWith(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/mixer/Volume.hxx b/src/mixer/Volume.hxx new file mode 100644 index 000000000..06d3551eb --- /dev/null +++ b/src/mixer/Volume.hxx @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.h" + +#include <stdio.h> + +void volume_init(void); + +gcc_pure +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. + */ +gcc_pure +unsigned +sw_volume_state_get_hash(void); + +#endif diff --git a/src/mixer/WinmmMixerPlugin.cxx b/src/mixer/WinmmMixerPlugin.cxx deleted file mode 100644 index dbb43dce4..000000000 --- a/src/mixer/WinmmMixerPlugin.cxx +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/plugins/AlsaMixerPlugin.cxx b/src/mixer/plugins/AlsaMixerPlugin.cxx new file mode 100644 index 000000000..a15820fc7 --- /dev/null +++ b/src/mixer/plugins/AlsaMixerPlugin.cxx @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/MixerInternal.hxx" +#include "output/OutputAPI.hxx" +#include "GlobalEvents.hxx" +#include "Main.hxx" +#include "event/MultiSocketMonitor.hxx" +#include "event/DeferredMonitor.hxx" +#include "event/Loop.hxx" +#include "util/ASCII.hxx" +#include "util/ReusableArray.hxx" +#include "util/Clamp.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <algorithm> + +#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 : MultiSocketMonitor, DeferredMonitor { + snd_mixer_t *mixer; + + ReusableArray<pollfd> pfd_buffer; + +public: + AlsaMixerMonitor(EventLoop &_loop, snd_mixer_t *_mixer) + :MultiSocketMonitor(_loop), DeferredMonitor(_loop), + mixer(_mixer) { + DeferredMonitor::Schedule(); + } + +private: + virtual void RunDeferred() override { + InvalidateSockets(); + } + + 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) { + ClearSocketList(); + 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; + + ReplaceSocketList(pfds, count); + 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 != nullptr; elem = snd_mixer_elem_next(elem)) { + if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE && + StringEqualsCaseASCII(snd_mixer_selem_get_name(elem), + name) && + snd_mixer_selem_get_index(elem) == idx) + return elem; + } + + return nullptr; +} + +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, nullptr, + nullptr)) < 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 == nullptr) { + 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 != nullptr); + + delete monitor; + + snd_mixer_elem_set_callback(elem, nullptr); + 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 != nullptr); + + 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 != nullptr); + + vol = volume; + + volume_set = vol + 0.5; + + level = (long)(((vol / 100.0) * (volume_max - volume_min) + + volume_min) + 0.5); + level = Clamp(level, volume_min, volume_max); + + 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/plugins/OssMixerPlugin.cxx b/src/mixer/plugins/OssMixerPlugin.cxx new file mode 100644 index 000000000..095ace015 --- /dev/null +++ b/src/mixer/plugins/OssMixerPlugin.cxx @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/MixerInternal.hxx" +#include "config/ConfigData.hxx" +#include "system/fd_util.h" +#include "util/ASCII.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> +#include <sys/ioctl.h> +#include <fcntl.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 (StringEqualsCaseASCII(name, labels[i], name_length) && + (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/plugins/PulseMixerPlugin.cxx b/src/mixer/plugins/PulseMixerPlugin.cxx new file mode 100644 index 000000000..4df88d06b --- /dev/null +++ b/src/mixer/plugins/PulseMixerPlugin.cxx @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "mixer/MixerInternal.hxx" +#include "output/plugins/PulseOutputPlugin.hxx" +#include "GlobalEvents.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <pulse/context.h> +#include <pulse/introspect.h> +#include <pulse/stream.h> +#include <pulse/subscribe.h> +#include <pulse/error.h> + +#include <assert.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 == nullptr) { + 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 != nullptr); + assert(stream != nullptr); + 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 == nullptr) { + 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 != nullptr); + + o = pa_context_subscribe(context, + (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT, + nullptr, nullptr); + if (o == nullptr) { + 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 == nullptr) { + 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/plugins/PulseMixerPlugin.hxx b/src/mixer/plugins/PulseMixerPlugin.hxx new file mode 100644 index 000000000..993945e9b --- /dev/null +++ b/src/mixer/plugins/PulseMixerPlugin.hxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 + +struct PulseMixer; +struct pa_context; +struct pa_stream; + +void +pulse_mixer_on_connect(PulseMixer *pm, pa_context *context); + +void +pulse_mixer_on_disconnect(PulseMixer *pm); + +void +pulse_mixer_on_change(PulseMixer *pm, pa_context *context, pa_stream *stream); + +#endif diff --git a/src/mixer/plugins/RoarMixerPlugin.cxx b/src/mixer/plugins/RoarMixerPlugin.cxx new file mode 100644 index 000000000..958688cd7 --- /dev/null +++ b/src/mixer/plugins/RoarMixerPlugin.cxx @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2014 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/MixerInternal.hxx" +#include "output/plugins/RoarOutputPlugin.hxx" +#include "Compiler.h" + +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/plugins/SoftwareMixerPlugin.cxx b/src/mixer/plugins/SoftwareMixerPlugin.cxx new file mode 100644 index 000000000..67ddee1a4 --- /dev/null +++ b/src/mixer/plugins/SoftwareMixerPlugin.cxx @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "mixer/MixerInternal.hxx" +#include "filter/FilterPlugin.hxx" +#include "filter/FilterRegistry.hxx" +#include "filter/FilterInternal.hxx" +#include "filter/plugins/VolumeFilterPlugin.hxx" +#include "pcm/Volume.hxx" +#include "config/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; + + /** + * If this is true, then this object "owns" the #Filter + * instance (see above). It will be set to false by + * software_mixer_get_filter(); after that, the caller will be + * responsible for the #Filter. + */ + bool owns_filter; + + unsigned volume; + + SoftwareMixer() + :Mixer(software_mixer_plugin), + filter(CreateVolumeFilter()), + owns_filter(true), + volume(100) + { + assert(filter != nullptr); + } + + ~SoftwareMixer() { + if (owns_filter) + 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)); + assert(sm->owns_filter); + + sm->owns_filter = false; + return sm->filter; +} diff --git a/src/mixer/plugins/SoftwareMixerPlugin.hxx b/src/mixer/plugins/SoftwareMixerPlugin.hxx new file mode 100644 index 000000000..581d2ac17 --- /dev/null +++ b/src/mixer/plugins/SoftwareMixerPlugin.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/WinmmMixerPlugin.cxx b/src/mixer/plugins/WinmmMixerPlugin.cxx new file mode 100644 index 000000000..093a026a9 --- /dev/null +++ b/src/mixer/plugins/WinmmMixerPlugin.cxx @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/MixerInternal.hxx" +#include "output/OutputAPI.hxx" +#include "output/plugins/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/neighbor/Explorer.hxx b/src/neighbor/Explorer.hxx new file mode 100644 index 000000000..84a54840c --- /dev/null +++ b/src/neighbor/Explorer.hxx @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NEIGHBOR_EXPLORER_HXX +#define MPD_NEIGHBOR_EXPLORER_HXX + +#include <forward_list> + +class Error; +class NeighborListener; +struct NeighborInfo; + +/** + * An object that explores the neighborhood for music servers. + * + * As soon as this object is opened, it will start exploring, and + * notify the #NeighborListener when it found or lost something. + * + * The implementation is supposed to be non-blocking. This can be + * implemented either using the #EventLoop instance that was passed to + * the NeighborPlugin or by moving the blocking parts in a dedicated + * thread. + */ +class NeighborExplorer { +protected: + NeighborListener &listener; + + explicit NeighborExplorer(NeighborListener &_listener) + :listener(_listener) {} + +public: + typedef std::forward_list<NeighborInfo> List; + + /** + * Free instance data. + */ + virtual ~NeighborExplorer() {} + + /** + * Start exploring the neighborhood. + */ + virtual bool Open(Error &error) = 0; + + /** + * Stop exploring. + */ + virtual void Close() = 0; + + /** + * Obtain a list of currently known neighbors. + */ + virtual List GetList() const = 0; +}; + +#endif diff --git a/src/neighbor/Glue.cxx b/src/neighbor/Glue.cxx new file mode 100644 index 000000000..7e29b8410 --- /dev/null +++ b/src/neighbor/Glue.cxx @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Glue.hxx" +#include "Registry.hxx" +#include "Explorer.hxx" +#include "NeighborPlugin.hxx" +#include "Info.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigError.hxx" +#include "util/Error.hxx" + +NeighborGlue::Explorer::~Explorer() +{ + delete explorer; +} + +NeighborGlue::~NeighborGlue() {} + +static NeighborExplorer * +CreateNeighborExplorer(EventLoop &loop, NeighborListener &listener, + const config_param ¶m, Error &error) +{ + const char *plugin_name = param.GetBlockValue("plugin"); + if (plugin_name == nullptr) { + error.Set(config_domain, + "Missing \"plugin\" configuration"); + return nullptr; + } + + const NeighborPlugin *plugin = GetNeighborPluginByName(plugin_name); + if (plugin == nullptr) { + error.Format(config_domain, "No such neighbor plugin: %s", + plugin_name); + return nullptr; + } + + return plugin->create(loop, listener, param, error); +} + +bool +NeighborGlue::Init(EventLoop &loop, NeighborListener &listener, Error &error) +{ + const config_param *param = nullptr; + while ((param = config_get_next_param(CONF_NEIGHBORS, param))) { + NeighborExplorer *explorer = + CreateNeighborExplorer(loop, listener, *param, error); + if (explorer == nullptr) { + error.FormatPrefix("Line %i: ", param->line); + return false; + } + + explorers.emplace_front(explorer); + } + + return true; +} + +bool +NeighborGlue::Open(Error &error) +{ + for (auto i = explorers.begin(), end = explorers.end(); + i != end; ++i) { + if (!i->explorer->Open(error)) { + /* roll back */ + for (auto k = explorers.begin(); k != i; ++k) + k->explorer->Close(); + return false; + } + } + + return true; +} + +void +NeighborGlue::Close() +{ + for (auto i = explorers.begin(), end = explorers.end(); i != end; ++i) + i->explorer->Close(); +} + +NeighborGlue::List +NeighborGlue::GetList() const +{ + List result; + + for (const auto &i : explorers) + result.splice_after(result.before_begin(), + i.explorer->GetList()); + + return result; +} + diff --git a/src/neighbor/Glue.hxx b/src/neighbor/Glue.hxx new file mode 100644 index 000000000..92c612d22 --- /dev/null +++ b/src/neighbor/Glue.hxx @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NEIGHBOR_ALL_HXX +#define MPD_NEIGHBOR_ALL_HXX + +#include "check.h" +#include "Compiler.h" +#include "thread/Mutex.hxx" + +#include <forward_list> + +struct config_param; +class Error; +class EventLoop; +class NeighborExplorer; +class NeighborListener; +struct NeighborInfo; + +/** + * A class that initializes and opens all configured neighbor plugins. + */ +class NeighborGlue { + struct Explorer { + NeighborExplorer *const explorer; + + Explorer(NeighborExplorer *_explorer):explorer(_explorer) {} + Explorer(const Explorer &) = delete; + ~Explorer(); + }; + + Mutex mutex; + + std::forward_list<Explorer> explorers; + +public: + typedef std::forward_list<NeighborInfo> List; + + NeighborGlue() = default; + NeighborGlue(const NeighborGlue &) = delete; + ~NeighborGlue(); + + bool IsEmpty() const { + return explorers.empty(); + } + + bool Init(EventLoop &loop, NeighborListener &listener, Error &error); + + bool Open(Error &error); + void Close(); + + /** + * Get the combined list of all neighbors from all active + * plugins. + */ + gcc_pure + List GetList() const; +}; + +#endif diff --git a/src/neighbor/Info.hxx b/src/neighbor/Info.hxx new file mode 100644 index 000000000..ac4806f14 --- /dev/null +++ b/src/neighbor/Info.hxx @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NEIGHBOR_INFO_HXX +#define MPD_NEIGHBOR_INFO_HXX + +#include <string> + +struct NeighborInfo { + std::string uri; + std::string display_name; + + template<typename U, typename DN> + NeighborInfo(U &&_uri, DN &&_display_name) + :uri(std::forward<U>(_uri)), + display_name(std::forward<DN>(_display_name)) {} +}; + +#endif diff --git a/src/neighbor/Listener.hxx b/src/neighbor/Listener.hxx new file mode 100644 index 000000000..20295f5a9 --- /dev/null +++ b/src/neighbor/Listener.hxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NEIGHBOR_LISTENER_HXX +#define MPD_NEIGHBOR_LISTENER_HXX + +struct NeighborInfo; +class NeighborExplorer; + +/** + * An interface that listens on events from neighbor plugins. The + * methods must be thread-safe and non-blocking. + */ +class NeighborListener { +public: + virtual void FoundNeighbor(const NeighborInfo &info) = 0; + virtual void LostNeighbor(const NeighborInfo &info) = 0; +}; + +#endif diff --git a/src/neighbor/NeighborPlugin.hxx b/src/neighbor/NeighborPlugin.hxx new file mode 100644 index 000000000..0d4ebaa7b --- /dev/null +++ b/src/neighbor/NeighborPlugin.hxx @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NEIGHBOR_PLUGIN_HXX +#define MPD_NEIGHBOR_PLUGIN_HXX + +struct config_param; +class Error; +class EventLoop; +class NeighborListener; +class NeighborExplorer; + +struct NeighborPlugin { + const char *name; + + /** + * Allocates and configures a #NeighborExplorer instance. + */ + NeighborExplorer *(*create)(EventLoop &loop, NeighborListener &listener, + const config_param ¶m, + Error &error); +}; + +#endif diff --git a/src/neighbor/Registry.cxx b/src/neighbor/Registry.cxx new file mode 100644 index 000000000..f6d1f97b3 --- /dev/null +++ b/src/neighbor/Registry.cxx @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Registry.hxx" +#include "NeighborPlugin.hxx" +#include "plugins/SmbclientNeighborPlugin.hxx" +#include "plugins/UpnpNeighborPlugin.hxx" + +#include <string.h> + +const NeighborPlugin *const neighbor_plugins[] = { +#ifdef ENABLE_SMBCLIENT + &smbclient_neighbor_plugin, +#endif +#ifdef HAVE_LIBUPNP + &upnp_neighbor_plugin, +#endif + nullptr +}; + +const NeighborPlugin * +GetNeighborPluginByName(const char *name) +{ + for (auto i = neighbor_plugins; *i != nullptr; ++i) + if (strcmp((*i)->name, name) == 0) + return *i; + + return nullptr; +} diff --git a/src/neighbor/Registry.hxx b/src/neighbor/Registry.hxx new file mode 100644 index 000000000..0b89e537d --- /dev/null +++ b/src/neighbor/Registry.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NEIGHBOR_REGISTRY_HXX +#define MPD_NEIGHBOR_REGISTRY_HXX + +#include "Compiler.h" + +struct NeighborPlugin; + +/** + * nullptr terminated list of all neighbor plugins which were enabled at + * compile time. + */ +extern const NeighborPlugin *const neighbor_plugins[]; + +gcc_pure +const NeighborPlugin * +GetNeighborPluginByName(const char *name); + +#endif diff --git a/src/neighbor/plugins/SmbclientNeighborPlugin.cxx b/src/neighbor/plugins/SmbclientNeighborPlugin.cxx new file mode 100644 index 000000000..f7085c03d --- /dev/null +++ b/src/neighbor/plugins/SmbclientNeighborPlugin.cxx @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SmbclientNeighborPlugin.hxx" +#include "lib/smbclient/Init.hxx" +#include "neighbor/NeighborPlugin.hxx" +#include "neighbor/Explorer.hxx" +#include "neighbor/Listener.hxx" +#include "neighbor/Info.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "thread/Thread.hxx" +#include "thread/Name.hxx" +#include "util/Macros.hxx" +#include "util/Domain.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <libsmbclient.h> + +#include <list> +#include <algorithm> + +static constexpr Domain smbclient_domain("smbclient"); + +class SmbclientNeighborExplorer final : public NeighborExplorer { + struct Server { + std::string name, comment; + + bool alive; + + Server(std::string &&_name, std::string &&_comment) + :name(std::move(_name)), comment(std::move(_comment)), + alive(true) {} + Server(const Server &) = delete; + + gcc_pure + bool operator==(const Server &other) const { + return name == other.name; + } + + gcc_pure + NeighborInfo Export() const { + return { "smb://" + name + "/", comment }; + } + }; + + Thread thread; + + mutable Mutex mutex; + Cond cond; + + List list; + + bool quit; + +public: + SmbclientNeighborExplorer(NeighborListener &_listener) + :NeighborExplorer(_listener) {} + + /* virtual methods from class NeighborExplorer */ + virtual bool Open(Error &error) override; + virtual void Close() override; + virtual List GetList() const override; + +private: + void Run(); + void ThreadFunc(); + static void ThreadFunc(void *ctx); +}; + +bool +SmbclientNeighborExplorer::Open(Error &error) +{ + quit = false; + return thread.Start(ThreadFunc, this, error); +} + +void +SmbclientNeighborExplorer::Close() +{ + mutex.lock(); + quit = true; + cond.signal(); + mutex.unlock(); + + thread.Join(); +} + +NeighborExplorer::List +SmbclientNeighborExplorer::GetList() const +{ + const ScopeLock protect(mutex); + /* + List list; + for (const auto &i : servers) + list.emplace_front(i.Export()); + */ + return list; +} + +static void +ReadServer(NeighborExplorer::List &list, const smbc_dirent &e) +{ + const std::string name(e.name, e.namelen); + const std::string comment(e.comment, e.commentlen); + + list.emplace_front("smb://" + name, name + " (" + comment + ")"); +} + +static void +ReadServers(NeighborExplorer::List &list, const char *uri); + +static void +ReadWorkgroup(NeighborExplorer::List &list, const std::string &name) +{ + std::string uri = "smb://" + name; + ReadServers(list, uri.c_str()); +} + +static void +ReadEntry(NeighborExplorer::List &list, const smbc_dirent &e) +{ + switch (e.smbc_type) { + case SMBC_WORKGROUP: + ReadWorkgroup(list, std::string(e.name, e.namelen)); + break; + + case SMBC_SERVER: + ReadServer(list, e); + break; + } +} + +static void +ReadServers(NeighborExplorer::List &list, int fd) +{ + smbc_dirent *e; + while ((e = smbc_readdir(fd)) != nullptr) + ReadEntry(list, *e); + + smbc_closedir(fd); +} + +static void +ReadServers(NeighborExplorer::List &list, const char *uri) +{ + int fd = smbc_opendir(uri); + if (fd >= 0) { + ReadServers(list, fd); + smbc_closedir(fd); + } else + FormatErrno(smbclient_domain, "smbc_opendir('%s') failed", + uri); +} + +gcc_pure +static NeighborExplorer::List +DetectServers() +{ + NeighborExplorer::List list; + ReadServers(list, "smb://"); + return list; +} + +gcc_pure +static NeighborExplorer::List::const_iterator +FindBeforeServerByURI(NeighborExplorer::List::const_iterator prev, + NeighborExplorer::List::const_iterator end, + const std::string &uri) +{ + for (auto i = std::next(prev); i != end; prev = i, i = std::next(prev)) + if (i->uri == uri) + return prev; + + return end; +} + +inline void +SmbclientNeighborExplorer::Run() +{ + List found = DetectServers(), lost; + + mutex.lock(); + + const auto found_before_begin = found.before_begin(); + const auto found_end = found.end(); + + for (auto prev = list.before_begin(), i = std::next(prev), end = list.end(); + i != end; i = std::next(prev)) { + auto f = FindBeforeServerByURI(found_before_begin, found_end, + i->uri); + if (f != found_end) { + /* still visible: remove from "found" so we + don't believe it's a new one */ + *i = std::move(*std::next(f)); + found.erase_after(f); + prev = i; + } else { + /* can't see it anymore: move to "lost" */ + lost.splice_after(lost.before_begin(), list, prev); + } + } + + for (auto prev = found_before_begin, i = std::next(prev); + i != found_end; prev = i, i = std::next(prev)) + list.push_front(*i); + + mutex.unlock(); + + for (auto &i : lost) + listener.LostNeighbor(i); + + for (auto &i : found) + listener.FoundNeighbor(i); +} + +inline void +SmbclientNeighborExplorer::ThreadFunc() +{ + mutex.lock(); + + while (!quit) { + mutex.unlock(); + + Run(); + + mutex.lock(); + if (quit) + break; + + // TODO: sleep for how long? + cond.timed_wait(mutex, 10000); + } + + mutex.unlock(); +} + +void +SmbclientNeighborExplorer::ThreadFunc(void *ctx) +{ + SetThreadName("smbclient"); + + SmbclientNeighborExplorer &e = *(SmbclientNeighborExplorer *)ctx; + e.ThreadFunc(); +} + +static NeighborExplorer * +smbclient_neighbor_create(gcc_unused EventLoop &loop, + NeighborListener &listener, + gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + if (!SmbclientInit(error)) + return nullptr; + + return new SmbclientNeighborExplorer(listener); +} + +const NeighborPlugin smbclient_neighbor_plugin = { + "smbclient", + smbclient_neighbor_create, +}; diff --git a/src/neighbor/plugins/SmbclientNeighborPlugin.hxx b/src/neighbor/plugins/SmbclientNeighborPlugin.hxx new file mode 100644 index 000000000..12ec9c0cd --- /dev/null +++ b/src/neighbor/plugins/SmbclientNeighborPlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NEIGHBOR_SMBCLIENT_HXX +#define MPD_NEIGHBOR_SMBCLIENT_HXX + +struct NeighborPlugin; + +extern const NeighborPlugin smbclient_neighbor_plugin; + +#endif diff --git a/src/neighbor/plugins/UpnpNeighborPlugin.cxx b/src/neighbor/plugins/UpnpNeighborPlugin.cxx new file mode 100644 index 000000000..253e4c13b --- /dev/null +++ b/src/neighbor/plugins/UpnpNeighborPlugin.cxx @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "UpnpNeighborPlugin.hxx" +#include "lib/upnp/Domain.hxx" +#include "lib/upnp/ClientInit.hxx" +#include "lib/upnp/Discovery.hxx" +#include "lib/upnp/ContentDirectoryService.hxx" +#include "neighbor/NeighborPlugin.hxx" +#include "neighbor/Explorer.hxx" +#include "neighbor/Listener.hxx" +#include "neighbor/Info.hxx" +#include "Log.hxx" + +class UpnpNeighborExplorer final + : public NeighborExplorer, UPnPDiscoveryListener { + struct Server { + std::string name, comment; + + bool alive; + + Server(std::string &&_name, std::string &&_comment) + :name(std::move(_name)), comment(std::move(_comment)), + alive(true) {} + Server(const Server &) = delete; + + gcc_pure + bool operator==(const Server &other) const { + return name == other.name; + } + + gcc_pure + NeighborInfo Export() const { + return { "smb://" + name + "/", comment }; + } + }; + + UPnPDeviceDirectory *discovery; + +public: + UpnpNeighborExplorer(NeighborListener &_listener) + :NeighborExplorer(_listener) {} + + /* virtual methods from class NeighborExplorer */ + virtual bool Open(Error &error) override; + virtual void Close() override; + virtual List GetList() const override; + +private: + /* virtual methods from class UPnPDiscoveryListener */ + virtual void FoundUPnP(const ContentDirectoryService &service) override; + virtual void LostUPnP(const ContentDirectoryService &service) override; +}; + +bool +UpnpNeighborExplorer::Open(Error &error) +{ + UpnpClient_Handle handle; + if (!UpnpClientGlobalInit(handle, error)) + return false; + + discovery = new UPnPDeviceDirectory(handle, this); + if (!discovery->Start(error)) { + delete discovery; + UpnpClientGlobalFinish(); + return false; + } + + return true; +} + +void +UpnpNeighborExplorer::Close() +{ + delete discovery; + UpnpClientGlobalFinish(); +} + +NeighborExplorer::List +UpnpNeighborExplorer::GetList() const +{ + std::vector<ContentDirectoryService> tmp; + + { + Error error; + if (!discovery->getDirServices(tmp, error)) + LogError(error); + } + + List result; + for (const auto &i : tmp) + result.emplace_front(i.GetURI(), i.getFriendlyName()); + return result; +} + +void +UpnpNeighborExplorer::FoundUPnP(const ContentDirectoryService &service) +{ + const NeighborInfo n(service.GetURI(), service.getFriendlyName()); + listener.FoundNeighbor(n); +} + +void +UpnpNeighborExplorer::LostUPnP(const ContentDirectoryService &service) +{ + const NeighborInfo n(service.GetURI(), service.getFriendlyName()); + listener.LostNeighbor(n); +} + +static NeighborExplorer * +upnp_neighbor_create(gcc_unused EventLoop &loop, + NeighborListener &listener, + gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new UpnpNeighborExplorer(listener); +} + +const NeighborPlugin upnp_neighbor_plugin = { + "upnp", + upnp_neighbor_create, +}; diff --git a/src/neighbor/plugins/UpnpNeighborPlugin.hxx b/src/neighbor/plugins/UpnpNeighborPlugin.hxx new file mode 100644 index 000000000..78e4ccf13 --- /dev/null +++ b/src/neighbor/plugins/UpnpNeighborPlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NEIGHBOR_UPNP_HXX +#define MPD_NEIGHBOR_UPNP_HXX + +struct NeighborPlugin; + +extern const NeighborPlugin upnp_neighbor_plugin; + +#endif diff --git a/src/notify.cxx b/src/notify.cxx index 64018968c..5b0b4baa0 100644 --- a/src/notify.cxx +++ b/src/notify.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/notify.hxx b/src/notify.hxx index 6b9e95368..cde29468f 100644 --- a/src/notify.hxx +++ b/src/notify.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/open.h b/src/open.h index 1fe245dfa..b05167188 100644 --- a/src/open.h +++ b/src/open.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/AlsaOutputPlugin.cxx b/src/output/AlsaOutputPlugin.cxx deleted file mode 100644 index 4877d3a46..000000000 --- a/src/output/AlsaOutputPlugin.cxx +++ /dev/null @@ -1,869 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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; - - /** - * Set to non-zero when the Raspberry Pi workaround has been - * activated in alsa_recover(); decremented by each write. - * This will avoid activating it again, leading to an endless - * loop. This problem was observed with a "RME Digi9636/52". - */ - unsigned pi_workaround; - - /** - * 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 nullptr; - } - - 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 = nullptr; - 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, nullptr); - 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, nullptr); - if (err < 0) - goto error; - } else { - err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time, - nullptr); - 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, nullptr); - 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, - nullptr); - 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; - - ad->pi_workaround = 0; - - 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 && ad->pi_workaround == 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); - - /* disable the workaround for some time */ - ad->pi_workaround = 8; - } - - 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; - - if (ad->pi_workaround > 0) - --ad->pi_workaround; - - 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 deleted file mode 100644 index dc7e639a8..000000000 --- a/src/output/AlsaOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index e66969e20..000000000 --- a/src/output/AoOutputPlugin.cxx +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index a44885e56..000000000 --- a/src/output/AoOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index aeb9a6a87..000000000 --- a/src/output/FifoOutputPlugin.cxx +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/AllocatedPath.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; - - AllocatedPath path; - std::string path_utf8; - - int input; - int output; - bool created; - Timer *timer; - - FifoOutput() - :path(AllocatedPath::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 deleted file mode 100644 index dca2886d8..000000000 --- a/src/output/FifoOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 8e13fda38..000000000 --- a/src/output/HttpdClient.cxx +++ /dev/null @@ -1,466 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/ASCII.hxx" -#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; - - if (!head_method) - httpd->SendHeader(*this); -} - -/** - * Handle a line of the HTTP request. - */ -bool -HttpdClient::HandleLine(const char *line) -{ - assert(state != RESPONSE); - - if (state == REQUEST) { - if (memcmp(line, "HEAD /", 6) == 0) { - line += 6; - head_method = true; - } else if (memcmp(line, "GET /", 5) == 0) { - line += 5; - } else { - /* only GET is supported */ - LogWarning(httpd_output_domain, - "malformed request line from client"); - return false; - } - - line = strchr(line, ' '); - if (line == nullptr || memcmp(line + 1, "HTTP/", 5) != 0) { - /* HTTP/0.9 without request headers */ - - if (head_method) - return false; - - 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 (StringEqualsCaseASCII(line, "Icy-MetaData: 1", 15) || - StringEqualsCaseASCII(line, "Icy-MetaData:1", 14)) { - /* Send icy metadata */ - metadata_requested = metadata_supported; - return true; - } - - if (StringEqualsCaseASCII(line, "transferMode.dlna.org: Streaming", 32)) { - /* 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) { - char *metadata_header = - icy_server_metadata_header(httpd->name, httpd->genre, - httpd->website, - httpd->content_type, - metaint); - - g_strlcpy(buffer, metadata_header, sizeof(buffer)); - - delete[] 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), - head_method(false), - 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(void *data, size_t length) -{ - if (state == RESPONSE) { - LogWarning(httpd_output_domain, - "unexpected input from client"); - LockClose(); - return InputResult::CLOSED; - } - - char *line = (char *)data; - char *newline = (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 */ - *newline = 0; - - if (!HandleLine(line)) { - LockClose(); - return InputResult::CLOSED; - } - - if (state == RESPONSE) { - if (!SendResponse()) - return InputResult::CLOSED; - - if (head_method) { - LockClose(); - 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 deleted file mode 100644 index 66a819232..000000000 --- a/src/output/HttpdClient.hxx +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.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; - - /** - * Is this a HEAD request? - */ - bool head_method; - - /** - * 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(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 deleted file mode 100644 index b76493a44..000000000 --- a/src/output/HttpdInternal.hxx +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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" - -#ifdef _LIBCPP_VERSION -/* can't use incomplete template arguments with libc++ */ -#include "HttpdClient.hxx" -#endif - -#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 deleted file mode 100644 index 369c06937..000000000 --- a/src/output/HttpdOutputPlugin.cxx +++ /dev/null @@ -1,565 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 == nullptr) { - 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 != nullptr && - 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 nullptr; - - 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 != nullptr) - 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 != nullptr) - 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 != nullptr); - - 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 != nullptr); - - 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 != nullptr) { - if (header != nullptr) - header->Unref(); - header = page; - BroadcastPage(page); - } - } else { - /* use Icy-Metadata */ - - if (metadata != nullptr) - metadata->Unref(); - - static constexpr TagType types[] = { - TAG_ALBUM, TAG_ARTIST, TAG_TITLE, - TAG_NUM_OF_ITEM_TYPES - }; - - metadata = icy_server_metadata_page(*tag, &types[0]); - if (metadata != nullptr) { - 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 deleted file mode 100644 index c74d2bd4a..000000000 --- a/src/output/HttpdOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 7ed672f95..000000000 --- a/src/output/JackOutputPlugin.cxx +++ /dev/null @@ -1,769 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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) -{ - LogDefault(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 deleted file mode 100644 index 908105ad2..000000000 --- a/src/output/JackOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index e2eec9dbc..000000000 --- a/src/output/NullOutputPlugin.cxx +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index a58f1cb13..000000000 --- a/src/output/NullOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 97ebae056..000000000 --- a/src/output/OSXOutputPlugin.cxx +++ /dev/null @@ -1,433 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "system/ByteOrder.hxx" -#include "Log.hxx" - -#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 (IsBigEndian()) - stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; - - 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.Format(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 deleted file mode 100644 index 2a4172880..000000000 --- a/src/output/OSXOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 268cf17cc..000000000 --- a/src/output/OpenALOutputPlugin.cxx +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 __APPLE__ -#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 deleted file mode 100644 index e1ebf3d4f..000000000 --- a/src/output/OpenALOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 68f2a38aa..000000000 --- a/src/output/OssOutputPlugin.cxx +++ /dev/null @@ -1,776 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "util/Macros.hxx" -#include "system/ByteOrder.hxx" -#include "Log.hxx" - -#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 = ARRAY_SIZE(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[ARRAY_SIZE(default_devices)]; - enum oss_stat ret[ARRAY_SIZE(default_devices)]; - - const config_param empty; - for (int i = ARRAY_SIZE(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 = ARRAY_SIZE(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 && - !IsLittleEndian()); -#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 deleted file mode 100644 index 6c5c9530b..000000000 --- a/src/output/OssOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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/OutputAPI.hxx b/src/output/OutputAPI.hxx new file mode 100644 index 000000000..cfbc43196 --- /dev/null +++ b/src/output/OutputAPI.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 + +// IWYU pragma: begin_exports + +#include "OutputPlugin.hxx" +#include "OutputInternal.hxx" +#include "AudioFormat.hxx" +#include "tag/Tag.hxx" +#include "config/ConfigData.hxx" + +// IWYU pragma: end_exports + +#endif diff --git a/src/output/OutputAll.cxx b/src/output/OutputAll.cxx new file mode 100644 index 000000000..ded9fa1e1 --- /dev/null +++ b/src/output/OutputAll.cxx @@ -0,0 +1,589 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "config/ConfigData.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/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] != nullptr); + + 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 nullptr; +} + +gcc_const +static unsigned +audio_output_config_count(void) +{ + unsigned int nr = 0; + const struct config_param *param = nullptr; + + 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(PlayerControl &pc) +{ + const struct config_param *param = nullptr; + unsigned int i; + Error error; + + num_audio_outputs = audio_output_config_count(); + audio_outputs = new 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 == nullptr) { + if (param != nullptr) + 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]); + } + + delete[] audio_outputs; + audio_outputs = nullptr; + 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); + + ao->fail_timer.Reset(); +} + +/** + * 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(ReplayGainMode 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 != nullptr); + assert(g_mp != nullptr); + assert(chunk != nullptr); + 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 == nullptr || g_music_buffer == &buffer); + assert((g_mp == nullptr) == (g_music_buffer == nullptr)); + + g_music_buffer = &buffer; + + /* the audio format must be the same as existing chunks in the + pipe */ + assert(g_mp == nullptr || g_mp->CheckFormat(audio_format)); + + if (g_mp == nullptr) + 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 == nullptr) + return false; + + assert(chunk == ao->chunk || g_mp->Contains(ao->chunk)); + + if (chunk != ao->chunk) { + assert(chunk->next != nullptr); + return true; + } + + return ao->chunk_finished && chunk->next == nullptr; +} + +/** + * 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 == nullptr); + 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 = nullptr; + } +} + +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 != nullptr); + assert(g_mp != nullptr); + + 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 == nullptr; + 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(PlayerControl &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 != nullptr) + 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 != nullptr) { + assert(g_music_buffer != nullptr); + + g_mp->Clear(*g_music_buffer); + delete g_mp; + g_mp = nullptr; + } + + g_music_buffer = nullptr; + + 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 != nullptr) { + assert(g_music_buffer != nullptr); + + g_mp->Clear(*g_music_buffer); + delete g_mp; + g_mp = nullptr; + } + + g_music_buffer = nullptr; + + 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/output/OutputAll.hxx b/src/output/OutputAll.hxx new file mode 100644 index 000000000..b6166eb48 --- /dev/null +++ b/src/output/OutputAll.hxx @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "Compiler.h" + +struct AudioFormat; +class MusicBuffer; +struct music_chunk; +struct PlayerControl; +class Error; + +/** + * Global initialization: load audio outputs from the configuration + * file and initialize them. + */ +void +audio_output_all_init(PlayerControl &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(ReplayGainMode 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(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(PlayerControl &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/output/OutputCommand.cxx b/src/output/OutputCommand.cxx new file mode 100644 index 000000000..d82b7c696 --- /dev/null +++ b/src/output/OutputCommand.cxx @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "PlayerControl.hxx" +#include "mixer/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 != nullptr) { + 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/output/OutputCommand.hxx b/src/output/OutputCommand.hxx new file mode 100644 index 000000000..4c44dff53 --- /dev/null +++ b/src/output/OutputCommand.hxx @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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/output/OutputControl.cxx b/src/output/OutputControl.cxx new file mode 100644 index 000000000..1532dbd83 --- /dev/null +++ b/src/output/OutputControl.cxx @@ -0,0 +1,325 @@ + +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "mixer/MixerControl.hxx" +#include "notify.hxx" +#include "filter/plugins/ReplayGainFilterPlugin.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <assert.h> + +/** after a failure, wait this number of seconds before + automatically reopening the device */ +static constexpr unsigned 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, + ReplayGainMode mode) +{ + if (ao->replay_gain_filter != nullptr) + replay_gain_filter_set_mode(ao->replay_gain_filter, mode); + if (ao->other_replay_gain_filter != nullptr) + replay_gain_filter_set_mode(ao->other_replay_gain_filter, mode); +} + +void +audio_output_enable(struct audio_output *ao) +{ + if (!ao->thread.IsDefined()) { + if (ao->plugin->enable == nullptr) { + /* 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.IsDefined()) { + if (ao->plugin->disable == nullptr) + 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 != nullptr); + assert(ao->allow_play); + assert(audio_format.IsValid()); + + ao->fail_timer.Reset(); + + if (ao->open && audio_format == ao->in_audio_format) { + assert(ao->pipe == &mp || + (ao->always_on && ao->pause)); + + if (ao->pause) { + ao->chunk = nullptr; + 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 = nullptr; + + ao->pipe = ∓ + + if (!ao->thread.IsDefined()) + audio_output_thread_start(ao); + + ao_command(ao, ao->open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN); + open = ao->open; + + if (open && ao->mixer != nullptr) { + 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 != nullptr); + assert(ao->allow_play); + + if (ao->mixer != nullptr) + mixer_auto_close(ao->mixer); + + assert(!ao->open || !ao->fail_timer.IsDefined()); + + if (ao->open) + ao_command(ao, AO_COMMAND_CLOSE); + else + ao->fail_timer.Reset(); +} + +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.Check(REOPEN_AFTER * 1000)) { + 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->in_playback_loop && + !ao->woken_for_play) { + ao->woken_for_play = true; + ao->cond.signal(); + } +} + +void audio_output_pause(struct audio_output *ao) +{ + if (ao->mixer != nullptr && ao->plugin->pause == nullptr) + /* 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 != nullptr); + assert(!ao->open || !ao->fail_timer.IsDefined()); + + 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.IsDefined()); + + if (ao->thread.IsDefined()) { + assert(ao->allow_play); + ao_lock_command(ao, AO_COMMAND_KILL); + ao->thread.Join(); + } + + audio_output_free(ao); +} diff --git a/src/output/OutputControl.hxx b/src/output/OutputControl.hxx new file mode 100644 index 000000000..7195412ef --- /dev/null +++ b/src/output/OutputControl.hxx @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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; + +void +audio_output_set_replay_gain_mode(audio_output *ao, + ReplayGainMode mode); + +/** + * Enables the device. + */ +void +audio_output_enable(audio_output *ao); + +/** + * Disables the device. + */ +void +audio_output_disable(audio_output *ao); + +/** + * Opens or closes the device, depending on the "enabled" flag. + * + * @return true if the device is open + */ +bool +audio_output_update(audio_output *ao, + AudioFormat audio_format, + const MusicPipe &mp); + +void +audio_output_play(audio_output *ao); + +void +audio_output_pause(audio_output *ao); + +void +audio_output_drain_async(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(audio_output *ao); + +/** + * Set the "allow_play" and signal the thread. + */ +void +audio_output_allow_play(audio_output *ao); + +void +audio_output_close(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(audio_output *ao); + +void +audio_output_finish(audio_output *ao); + +#endif diff --git a/src/output/OutputError.cxx b/src/output/OutputError.cxx new file mode 100644 index 000000000..9d4128912 --- /dev/null +++ b/src/output/OutputError.cxx @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License 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/output/OutputError.hxx b/src/output/OutputError.hxx new file mode 100644 index 000000000..e3a20142f --- /dev/null +++ b/src/output/OutputError.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/output/OutputFinish.cxx b/src/output/OutputFinish.cxx new file mode 100644 index 000000000..16b16f5ce --- /dev/null +++ b/src/output/OutputFinish.cxx @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "mixer/MixerControl.hxx" +#include "filter/FilterInternal.hxx" + +#include <assert.h> + +void +ao_base_finish(struct audio_output *ao) +{ + assert(!ao->open); + assert(!ao->fail_timer.IsDefined()); + assert(!ao->thread.IsDefined()); + + if (ao->mixer != nullptr) + 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.IsDefined()); + assert(!ao->thread.IsDefined()); + + ao_plugin_finish(ao); +} diff --git a/src/output/OutputInit.cxx b/src/output/OutputInit.cxx new file mode 100644 index 000000000..16180a415 --- /dev/null +++ b/src/output/OutputInit.cxx @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "OutputList.hxx" +#include "OutputError.hxx" +#include "OutputAPI.hxx" +#include "filter/FilterConfig.hxx" +#include "AudioParser.hxx" +#include "mixer/MixerList.hxx" +#include "mixer/MixerType.hxx" +#include "mixer/MixerControl.hxx" +#include "mixer/plugins/SoftwareMixerPlugin.hxx" +#include "filter/FilterPlugin.hxx" +#include "filter/FilterRegistry.hxx" +#include "filter/plugins/AutoConvertFilterPlugin.hxx" +#include "filter/plugins/ReplayGainFilterPlugin.hxx" +#include "filter/plugins/ChainFilterPlugin.hxx" +#include "config/ConfigError.hxx" +#include "config/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) +{ + LogDefault(output_domain, "Attempt to detect audio output device"); + + audio_output_plugins_for_each(plugin) { + if (plugin->test_default_device == nullptr) + continue; + + FormatDefault(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 nullptr; +} + +/** + * 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 != nullptr) + 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 nullptr; + + case MIXER_TYPE_HARDWARE: + if (plugin == nullptr) + return nullptr; + + return mixer_new(plugin, ao, param, error); + + case MIXER_TYPE_SOFTWARE: + mixer = mixer_new(&software_mixer_plugin, nullptr, + config_param(), + IgnoreError()); + assert(mixer != nullptr); + + 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 != nullptr); + assert(plugin != nullptr); + assert(plugin->finish != nullptr); + assert(plugin->open != nullptr); + assert(plugin->close != nullptr); + assert(plugin->play != nullptr); + + if (!param.IsNull()) { + ao->name = param.GetBlockValue(AUDIO_OUTPUT_NAME); + if (ao->name == nullptr) { + error.Set(config_domain, + "Missing \"name\" configuration"); + return false; + } + + const char *p = param.GetBlockValue(AUDIO_OUTPUT_FORMAT); + if (p != nullptr) { + 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->in_playback_loop = false; + ao->woken_for_play = false; + + /* set up the filter chain */ + + ao->filter = filter_chain_new(); + assert(ao->filter != nullptr); + + /* 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 != nullptr); + + 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->command = AO_COMMAND_NONE; + + ao->mixer = nullptr; + ao->replay_gain_filter = nullptr; + ao->other_replay_gain_filter = nullptr; + + /* 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 != nullptr); + + ao->replay_gain_serial = 0; + + ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin, + param, + IgnoreError()); + assert(ao->other_replay_gain_filter != nullptr); + + ao->other_replay_gain_serial = 0; + } else { + ao->replay_gain_filter = nullptr; + ao->other_replay_gain_filter = nullptr; + } + + /* 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 == nullptr && 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 != nullptr) + 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 != nullptr) { + 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 != nullptr); + + filter_chain_append(*ao->filter, "convert", ao->convert_filter); + + return true; +} + +struct audio_output * +audio_output_new(const config_param ¶m, + PlayerControl &pc, + Error &error) +{ + const struct audio_output_plugin *plugin; + + if (!param.IsNull()) { + const char *p; + + p = param.GetBlockValue(AUDIO_OUTPUT_TYPE); + if (p == nullptr) { + error.Set(config_domain, + "Missing \"type\" configuration"); + return nullptr; + } + + plugin = audio_output_plugin_get(p); + if (plugin == nullptr) { + 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 == nullptr) + return nullptr; + + FormatDefault(output_domain, + "Successfully detected a %s audio device", + plugin->name); + } + + struct audio_output *ao = ao_plugin_init(plugin, param, error); + if (ao == nullptr) + return nullptr; + + if (!audio_output_setup(ao, param, error)) { + ao_plugin_finish(ao); + return nullptr; + } + + ao->player_control = &pc; + return ao; +} diff --git a/src/output/OutputInternal.hxx b/src/output/OutputInternal.hxx new file mode 100644 index 000000000..18404dc01 --- /dev/null +++ b/src/output/OutputInternal.hxx @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "pcm/PcmDither.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "thread/Thread.hxx" +#include "system/PeriodClock.hxx" + +class Error; +class Filter; +class MusicPipe; +struct config_param; +struct PlayerControl; + +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 nullptr 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; + + /** + * True while the OutputThread is inside ao_play(). This + * means the PlayerThread does not need to wake up the + * OutputThread when new chunks are added to the MusicPipe, + * because the OutputThread is already watching that. + */ + bool in_playback_loop; + + /** + * Has the OutputThread been woken up to play more chunks? + * This is set by audio_output_play() and reset by ao_play() + * to reduce the number of duplicate wakeups. + */ + bool woken_for_play; + + /** + * If not nullptr, the device has failed, and this timer is used + * to estimate how long it should stay disabled (unless + * explicitly reopened with "play"). + */ + PeriodClock 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 dithering state for cross-fading two streams. + */ + PcmDither cross_fade_dither; + + /** + * 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 nullptr if the output thread isn't + * running. + */ + Thread 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 PlayerControl object which "owns" this output. This + * object is needed to signal command completion. + */ + PlayerControl *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, + PlayerControl &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/output/OutputList.cxx b/src/output/OutputList.cxx new file mode 100644 index 000000000..b914e6d2e --- /dev/null +++ b/src/output/OutputList.cxx @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "plugins/AlsaOutputPlugin.hxx" +#include "plugins/AoOutputPlugin.hxx" +#include "plugins/FifoOutputPlugin.hxx" +#include "plugins/HttpdOutputPlugin.hxx" +#include "plugins/JackOutputPlugin.hxx" +#include "plugins/NullOutputPlugin.hxx" +#include "plugins/OpenALOutputPlugin.hxx" +#include "plugins/OssOutputPlugin.hxx" +#include "plugins/OSXOutputPlugin.hxx" +#include "plugins/PipeOutputPlugin.hxx" +#include "plugins/PulseOutputPlugin.hxx" +#include "plugins/RecorderOutputPlugin.hxx" +#include "plugins/RoarOutputPlugin.hxx" +#include "plugins/ShoutOutputPlugin.hxx" +#include "plugins/SolarisOutputPlugin.hxx" +#include "plugins/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 + nullptr +}; + +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 nullptr; +} diff --git a/src/output/OutputList.hxx b/src/output/OutputList.hxx new file mode 100644 index 000000000..dfcf7487c --- /dev/null +++ b/src/output/OutputList.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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) != nullptr; ++output_plugin_iterator) + +#endif diff --git a/src/output/OutputPlugin.cxx b/src/output/OutputPlugin.cxx new file mode 100644 index 000000000..29fd6455a --- /dev/null +++ b/src/output/OutputPlugin.cxx @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 != nullptr); + assert(plugin->init != nullptr); + + 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 != nullptr + ? ao->plugin->enable(ao, error_r) + : true; +} + +void +ao_plugin_disable(struct audio_output *ao) +{ + if (ao->plugin->disable != nullptr) + 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 != nullptr + ? ao->plugin->delay(ao) + : 0; +} + +void +ao_plugin_send_tag(struct audio_output *ao, const Tag *tag) +{ + if (ao->plugin->send_tag != nullptr) + 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 != nullptr) + ao->plugin->drain(ao); +} + +void +ao_plugin_cancel(struct audio_output *ao) +{ + if (ao->plugin->cancel != nullptr) + ao->plugin->cancel(ao); +} + +bool +ao_plugin_pause(struct audio_output *ao) +{ + return ao->plugin->pause != nullptr && ao->plugin->pause(ao); +} diff --git a/src/output/OutputPlugin.hxx b/src/output/OutputPlugin.hxx new file mode 100644 index 000000000..7d77c92b2 --- /dev/null +++ b/src/output/OutputPlugin.hxx @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.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 nullptr if there is + * no configuration + * @return nullptr 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. + * + * @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 + */ + 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. + * + * @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 nullptr 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 != nullptr + ? 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/output/OutputPrint.cxx b/src/output/OutputPrint.cxx new file mode 100644 index 000000000..66826f140 --- /dev/null +++ b/src/output/OutputPrint.cxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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/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/output/OutputPrint.hxx b/src/output/OutputPrint.hxx new file mode 100644 index 000000000..2d94226fd --- /dev/null +++ b/src/output/OutputPrint.hxx @@ -0,0 +1,34 @@ + +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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/output/OutputState.cxx b/src/output/OutputState.cxx new file mode 100644 index 000000000..1141abeda --- /dev/null +++ b/src/output/OutputState.cxx @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "util/StringUtil.hxx" + +#include <assert.h> +#include <stdlib.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 (!StringStartsWith(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/output/OutputState.hxx b/src/output/OutputState.hxx new file mode 100644 index 000000000..a68180ae4 --- /dev/null +++ b/src/output/OutputState.hxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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/output/OutputThread.cxx b/src/output/OutputThread.cxx new file mode 100644 index 000000000..52dfc63ef --- /dev/null +++ b/src/output/OutputThread.cxx @@ -0,0 +1,690 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "filter/FilterInternal.hxx" +#include "filter/plugins/ConvertFilterPlugin.hxx" +#include "filter/plugins/ReplayGainFilterPlugin.hxx" +#include "PlayerControl.hxx" +#include "MusicPipe.hxx" +#include "MusicChunk.hxx" +#include "thread/Util.hxx" +#include "thread/Name.hxx" +#include "system/FatalError.hxx" +#include "util/Error.hxx" +#include "Log.hxx" +#include "Compiler.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 != nullptr && + !ao->replay_gain_filter->Open(format, error_r).IsDefined()) + return AudioFormat::Undefined(); + + if (ao->other_replay_gain_filter != nullptr && + !ao->other_replay_gain_filter->Open(format, error_r).IsDefined()) { + if (ao->replay_gain_filter != nullptr) + ao->replay_gain_filter->Close(); + return AudioFormat::Undefined(); + } + + const AudioFormat af = ao->filter->Open(format, error_r); + if (!af.IsDefined()) { + if (ao->replay_gain_filter != nullptr) + ao->replay_gain_filter->Close(); + if (ao->other_replay_gain_filter != nullptr) + ao->other_replay_gain_filter->Close(); + } + + return af; +} + +static void +ao_filter_close(struct audio_output *ao) +{ + if (ao->replay_gain_filter != nullptr) + ao->replay_gain_filter->Close(); + if (ao->other_replay_gain_filter != nullptr) + 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 != nullptr); + assert(ao->chunk == nullptr); + assert(ao->in_audio_format.IsValid()); + + ao->fail_timer.Reset(); + + /* 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.Update(); + 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.Update(); + return; + } + + if (!convert_filter_set(ao->convert_filter, ao->out_audio_format, + error)) { + FormatError(error, "Failed to convert for \"%s\" [%s]", + ao->name, ao->plugin->name); + + ao_filter_close(ao); + ao->fail_timer.Update(); + return; + } + + 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 = nullptr; + + ao->chunk = nullptr; + 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() || + !convert_filter_set(ao->convert_filter, ao->out_audio_format, + error)) { + 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 = nullptr; + + ao->chunk = nullptr; + ao->open = false; + ao->fail_timer.Update(); + + ao->mutex.unlock(); + ao_plugin_close(ao); + ao->mutex.lock(); + + return; + } +} + +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 != nullptr); + 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 != nullptr) { + 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 + : nullptr); + *replay_gain_serial_p = chunk->replay_gain_serial; + } + + Error error; + data = replay_gain_filter->FilterPCM(data, length, + &length, error); + if (data == nullptr) { + FormatError(error, "\"%s\" [%s] failed to filter", + ao->name, ao->plugin->name); + return nullptr; + } + } + + *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 == nullptr) + return nullptr; + + if (length == 0) { + /* empty chunk, nothing to do */ + *length_r = 0; + return data; + } + + /* cross-fade */ + + if (chunk->other != nullptr) { + 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 == nullptr) + return nullptr; + + 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(ao->cross_fade_dither, 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 nullptr; + } + + data = dest; + length = other_length; + } + + /* apply filter chain */ + + Error error; + data = ao->filter->FilterPCM(data, length, &length, error); + if (data == nullptr) { + FormatError(error, "\"%s\" [%s] failed to filter", + ao->name, ao->plugin->name); + return nullptr; + } + + *length_r = length; + return data; +} + +static bool +ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) +{ + assert(ao != nullptr); + assert(ao->filter != nullptr); + + if (ao->tags && gcc_unlikely(chunk->tag != nullptr)) { + 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 == nullptr) { + ao_close(ao, false); + + /* don't automatically reopen this device for 10 + seconds */ + ao->fail_timer.Update(); + 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.IsDefined()); + ao->fail_timer.Update(); + + 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 != nullptr + /* 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 != nullptr); + + chunk = ao_next_chunk(ao); + if (chunk == nullptr) + /* no chunk available */ + return false; + + ao->chunk_finished = false; + + assert(!ao->in_playback_loop); + ao->in_playback_loop = true; + + while (chunk != nullptr && ao->command == AO_COMMAND_NONE) { + assert(!ao->chunk_finished); + + ao->chunk = chunk; + + success = ao_play_chunk(ao, chunk); + if (!success) { + assert(ao->chunk == nullptr); + break; + } + + assert(ao->chunk == chunk); + chunk = chunk->next; + } + + assert(ao->in_playback_loop); + ao->in_playback_loop = false; + + 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 void +audio_output_task(void *arg) +{ + struct audio_output *ao = (struct audio_output *)arg; + + FormatThreadName("output:%s", ao->name); + + SetThreadRealtime(); + + 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 != nullptr); + + 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 == nullptr); + 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 = nullptr; + + if (ao->open) { + ao->mutex.unlock(); + ao_plugin_cancel(ao); + ao->mutex.lock(); + } + + ao_command_finished(ao); + continue; + + case AO_COMMAND_KILL: + ao->chunk = nullptr; + ao_command_finished(ao); + ao->mutex.unlock(); + return; + } + + 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->woken_for_play = false; + ao->cond.wait(ao->mutex); + } + } +} + +void audio_output_thread_start(struct audio_output *ao) +{ + assert(ao->command == AO_COMMAND_NONE); + + Error error; + if (!ao->thread.Start(audio_output_task, ao, error)) + FatalError(error); +} diff --git a/src/output/OutputThread.hxx b/src/output/OutputThread.hxx new file mode 100644 index 000000000..1cdbd65f2 --- /dev/null +++ b/src/output/OutputThread.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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(audio_output *ao); + +#endif diff --git a/src/output/PipeOutputPlugin.cxx b/src/output/PipeOutputPlugin.cxx deleted file mode 100644 index 34d615284..000000000 --- a/src/output/PipeOutputPlugin.cxx +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 <string> - -#include <stdio.h> - -struct PipeOutput { - struct audio_output base; - - std::string 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.GetBlockValue("command", ""); - if (cmd.empty()) { - 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; - - 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.c_str(), "w"); - if (pd->fh == nullptr) { - error.FormatErrno("Error opening pipe \"%s\"", - pd->cmd.c_str()); - 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 deleted file mode 100644 index f0c29706b..000000000 --- a/src/output/PipeOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index ab797387d..000000000 --- a/src/output/PulseOutputPlugin.cxx +++ /dev/null @@ -1,889 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index 0ed8404bc..000000000 --- a/src/output/PulseOutputPlugin.hxx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 9a7eba01f..000000000 --- a/src/output/RecorderOutputPlugin.cxx +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index a27f51e23..000000000 --- a/src/output/RecorderOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 895a165d1..000000000 --- a/src/output/RoarOutputPlugin.cxx +++ /dev/null @@ -1,428 +0,0 @@ -/* - * 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 <string> - -/* libroar/services.h declares roar_service_stream::new - work around - this C++ problem */ -#define new _new -#include <roaraudio.h> -#undef new - -class RoarOutput { - struct audio_output base; - - std::string host, name; - - roar_vs_t * vss; - int err; - int role; - struct roar_connection con; - struct roar_audio_info info; - mutable Mutex mutex; - volatile bool alive; - -public: - RoarOutput() - :err(ROAR_ERROR_NONE) {} - - operator audio_output *() { - return &base; - } - - bool Initialize(const config_param ¶m, Error &error) { - return ao_base_init(&base, &roar_output_plugin, param, - error); - } - - void Deinitialize() { - ao_base_finish(&base); - } - - void Configure(const config_param ¶m); - - bool Open(AudioFormat &audio_format, Error &error); - void Close(); - - void SendTag(const Tag &tag); - size_t Play(const void *chunk, size_t size, Error &error); - void Cancel(); - - int GetVolume() const; - bool SetVolume(unsigned volume); -}; - -static constexpr Domain roar_output_domain("roar_output"); - -inline int -RoarOutput::GetVolume() const -{ - const ScopeLock protect(mutex); - - if (vss == nullptr || !alive) - return -1; - - float l, r; - int error; - if (roar_vs_volume_get(vss, &l, &r, &error) < 0) - return -1; - - return (l + r) * 50; -} - -int -roar_output_get_volume(RoarOutput *roar) -{ - return roar->GetVolume(); -} - -bool -RoarOutput::SetVolume(unsigned volume) -{ - assert(volume <= 100); - - const ScopeLock protect(mutex); - if (vss == nullptr || !alive) - return false; - - int error; - float level = volume / 100.0; - - roar_vs_volume_mono(vss, level, &error); - return true; -} - -bool -roar_output_set_volume(RoarOutput *roar, unsigned volume) -{ - return roar->SetVolume(volume); -} - -inline void -RoarOutput::Configure(const config_param ¶m) -{ - host = param.GetBlockValue("server", ""); - name = param.GetBlockValue("name", "MPD"); - - const char *_role = param.GetBlockValue("role", "music"); - 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 (!self->Initialize(param, error)) { - delete self; - return nullptr; - } - - self->Configure(param); - return *self; -} - -static void -roar_finish(struct audio_output *ao) -{ - RoarOutput *self = (RoarOutput *)ao; - - self->Deinitialize(); - 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; - } -} - -inline bool -RoarOutput::Open(AudioFormat &audio_format, Error &error) -{ - const ScopeLock protect(mutex); - - if (roar_simple_connect(&con, - host.empty() ? nullptr : host.c_str(), - name.c_str()) < 0) { - error.Set(roar_output_domain, - "Failed to connect to Roar server"); - return false; - } - - vss = roar_vs_new_from_con(&con, &err); - - if (vss == nullptr || err != ROAR_ERROR_NONE) { - error.Set(roar_output_domain, "Failed to connect to server"); - return false; - } - - roar_use_audio_format(&info, audio_format); - - if (roar_vs_stream(vss, &info, ROAR_DIR_PLAY, &err) < 0) { - error.Set(roar_output_domain, "Failed to start stream"); - return false; - } - - roar_vs_role(vss, role, &err); - alive = true; - return true; -} - -static bool -roar_open(struct audio_output *ao, AudioFormat &audio_format, Error &error) -{ - RoarOutput *self = (RoarOutput *)ao; - - return self->Open(audio_format, error); -} - -inline void -RoarOutput::Close() -{ - const ScopeLock protect(mutex); - - alive = false; - - if (vss != nullptr) - roar_vs_close(vss, ROAR_VS_TRUE, &err); - vss = nullptr; - roar_disconnect(&con); -} - -static void -roar_close(struct audio_output *ao) -{ - RoarOutput *self = (RoarOutput *)ao; - self->Close(); -} - -inline void -RoarOutput::Cancel() -{ - const ScopeLock protect(mutex); - - if (vss == nullptr) - return; - - roar_vs_t *_vss = vss; - vss = nullptr; - roar_vs_close(_vss, ROAR_VS_TRUE, &err); - alive = false; - - _vss = roar_vs_new_from_con(&con, &err); - if (_vss == nullptr) - return; - - if (roar_vs_stream(_vss, &info, ROAR_DIR_PLAY, &err) < 0) { - roar_vs_close(_vss, ROAR_VS_TRUE, &err); - LogError(roar_output_domain, "Failed to start stream"); - return; - } - - roar_vs_role(_vss, role, &err); - vss = _vss; - alive = true; -} - -static void -roar_cancel(struct audio_output *ao) -{ - RoarOutput *self = (RoarOutput *)ao; - - self->Cancel(); -} - -inline size_t -RoarOutput::Play(const void *chunk, size_t size, Error &error) -{ - if (vss == nullptr) { - error.Set(roar_output_domain, "Connection is invalid"); - return 0; - } - - ssize_t nbytes = roar_vs_write(vss, chunk, size, &err); - if (nbytes <= 0) { - error.Set(roar_output_domain, "Failed to play data"); - return 0; - } - - return nbytes; -} - -static size_t -roar_play(struct audio_output *ao, const void *chunk, size_t size, - Error &error) -{ - RoarOutput *self = (RoarOutput *)ao; - return self->Play(chunk, size, error); -} - -static const char* -roar_tag_convert(TagType 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; - } -} - -inline void -RoarOutput::SendTag(const Tag &tag) -{ - if (vss == nullptr) - return; - - const ScopeLock protect(mutex); - - size_t cnt = 1; - struct roar_keyval vals[32]; - char uuid_buf[32][64]; - - char timebuf[16]; - snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", - tag.time / 3600, (tag.time % 3600) / 60, tag.time % 60); - - vals[0].key = const_cast<char *>("LENGTH"); - vals[0].value = timebuf; - - for (unsigned i = 0; i < tag.num_items && cnt < 32; i++) - { - bool is_uuid = false; - const char *key = roar_tag_convert(tag.items[i]->type, - &is_uuid); - if (key != nullptr) { - vals[cnt].key = const_cast<char *>(key); - - if (is_uuid) { - snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s", - tag.items[i]->value); - vals[cnt].value = uuid_buf[cnt]; - } else { - vals[cnt].value = tag.items[i]->value; - } - - cnt++; - } - } - - roar_vs_meta(vss, vals, cnt, &(err)); -} - -static void -roar_send_tag(struct audio_output *ao, const Tag *meta) -{ - RoarOutput *self = (RoarOutput *)ao; - self->SendTag(*meta); -} - -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 deleted file mode 100644 index 04949e421..000000000 --- a/src/output/RoarOutputPlugin.hxx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 - -class 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 deleted file mode 100644 index 19f2b61cd..000000000 --- a/src/output/ShoutOutputPlugin.cxx +++ /dev/null @@ -1,544 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index 496b77975..000000000 --- a/src/output/ShoutOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 0836dc2e2..000000000 --- a/src/output/SolarisOutputPlugin.cxx +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index d0fbd32c8..000000000 --- a/src/output/SolarisOutputPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index d2508ee2a..000000000 --- a/src/output/WinmmOutputPlugin.cxx +++ /dev/null @@ -1,353 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "util/Macros.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)) { - delete wo; - return nullptr; - } - - const char *device = param.GetBlockValue("device"); - if (!get_device_id(device, &wo->device_id, error)) { - ao_base_finish(&wo->base); - delete 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 < ARRAY_SIZE(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 < ARRAY_SIZE(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) % - ARRAY_SIZE(wo->buffers); - - return size; -} - -static bool -winmm_drain_all_buffers(WinmmOutput *wo, Error &error) -{ - for (unsigned i = wo->next_buffer; i < ARRAY_SIZE(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 < ARRAY_SIZE(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 deleted file mode 100644 index a6b7733ec..000000000 --- a/src/output/WinmmOutputPlugin.hxx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 "Compiler.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/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx new file mode 100644 index 000000000..f2d6916a5 --- /dev/null +++ b/src/output/plugins/AlsaOutputPlugin.cxx @@ -0,0 +1,868 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "mixer/MixerList.hxx" +#include "pcm/PcmExport.hxx" +#include "util/Manual.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#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; + + /** + * Set to non-zero when the Raspberry Pi workaround has been + * activated in alsa_recover(); decremented by each write. + * This will avoid activating it again, leading to an endless + * loop. This problem was observed with a "RME Digi9636/52". + */ + unsigned pi_workaround; + + /** + * This buffer gets allocated after opening the ALSA device. + * It contains silence samples, enough to fill one period (see + * #period_frames). + */ + uint8_t *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 nullptr; + } + + 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 = nullptr; + 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, nullptr); + 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, nullptr); + if (err < 0) + goto error; + } else { + err = snd_pcm_hw_params_get_buffer_time(hwparams, &buffer_time, + nullptr); + 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, nullptr); + 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, + nullptr); + 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 = new uint8_t[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)); + delete[] 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; + + ad->pi_workaround = 0; + + 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 && ad->pi_workaround == 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); + + /* disable the workaround for some time */ + ad->pi_workaround = 8; + } + + 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); + delete[] 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; + + if (ad->pi_workaround > 0) + --ad->pi_workaround; + + 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/plugins/AlsaOutputPlugin.hxx b/src/output/plugins/AlsaOutputPlugin.hxx new file mode 100644 index 000000000..63508e041 --- /dev/null +++ b/src/output/plugins/AlsaOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/AoOutputPlugin.cxx b/src/output/plugins/AoOutputPlugin.cxx new file mode 100644 index 000000000..efc1e0c6e --- /dev/null +++ b/src/output/plugins/AoOutputPlugin.cxx @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/plugins/AoOutputPlugin.hxx b/src/output/plugins/AoOutputPlugin.hxx new file mode 100644 index 000000000..cbf2fd589 --- /dev/null +++ b/src/output/plugins/AoOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/FifoOutputPlugin.cxx b/src/output/plugins/FifoOutputPlugin.cxx new file mode 100644 index 000000000..6e3a4d332 --- /dev/null +++ b/src/output/plugins/FifoOutputPlugin.cxx @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "config/ConfigError.hxx" +#include "../OutputAPI.hxx" +#include "Timer.hxx" +#include "fs/AllocatedPath.hxx" +#include "fs/FileSystem.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" +#include "open.h" + +#include <sys/stat.h> +#include <errno.h> +#include <unistd.h> + +#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */ + +struct FifoOutput { + struct audio_output base; + + AllocatedPath path; + std::string path_utf8; + + int input; + int output; + bool created; + Timer *timer; + + FifoOutput() + :path(AllocatedPath::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/plugins/FifoOutputPlugin.hxx b/src/output/plugins/FifoOutputPlugin.hxx new file mode 100644 index 000000000..394ec3ae9 --- /dev/null +++ b/src/output/plugins/FifoOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/HttpdClient.cxx b/src/output/plugins/HttpdClient.cxx new file mode 100644 index 000000000..d761bdf57 --- /dev/null +++ b/src/output/plugins/HttpdClient.cxx @@ -0,0 +1,485 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/ASCII.hxx" +#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(); + + ClearQueue(); + } + + if (metadata) + metadata->Unref(); + + if (IsDefined()) + BufferedSocket::Close(); +} + +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; + + if (!head_method) + httpd.SendHeader(*this); +} + +/** + * Handle a line of the HTTP request. + */ +bool +HttpdClient::HandleLine(const char *line) +{ + assert(state != RESPONSE); + + if (state == REQUEST) { + if (memcmp(line, "HEAD /", 6) == 0) { + line += 6; + head_method = true; + } else if (memcmp(line, "GET /", 5) == 0) { + line += 5; + } else { + /* only GET is supported */ + LogWarning(httpd_output_domain, + "malformed request line from client"); + return false; + } + + line = strchr(line, ' '); + if (line == nullptr || memcmp(line + 1, "HTTP/", 5) != 0) { + /* HTTP/0.9 without request headers */ + + if (head_method) + return false; + + 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 (StringEqualsCaseASCII(line, "Icy-MetaData: 1", 15) || + StringEqualsCaseASCII(line, "Icy-MetaData:1", 14)) { + /* Send icy metadata */ + metadata_requested = metadata_supported; + return true; + } + + if (StringEqualsCaseASCII(line, "transferMode.dlna.org: Streaming", 32)) { + /* 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) { + char *metadata_header = + icy_server_metadata_header(httpd.name, httpd.genre, + httpd.website, + httpd.content_type, + metaint); + + g_strlcpy(buffer, metadata_header, sizeof(buffer)); + + delete[] 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), + queue_size(0), + head_method(false), + 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) +{ +} + +void +HttpdClient::ClearQueue() +{ + assert(state == RESPONSE); + + while (!pages.empty()) { + Page *page = pages.front(); + pages.pop(); + +#ifndef NDEBUG + assert(queue_size >= page->size); + queue_size -= page->size; +#endif + + page->Unref(); + } + + assert(queue_size == 0); +} + +void +HttpdClient::CancelQueue() +{ + if (state != RESPONSE) + return; + + ClearQueue(); + + 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(); + current_position = 0; + + assert(queue_size >= current_page->size); + queue_size -= current_page->size; + } + + 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; + + if (queue_size > 256 * 1024) { + FormatDebug(httpd_output_domain, + "client is too slow, flushing its queue"); + ClearQueue(); + } + + page->Ref(); + pages.push(page); + queue_size += page->size; + + 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(void *data, size_t length) +{ + if (state == RESPONSE) { + LogWarning(httpd_output_domain, + "unexpected input from client"); + LockClose(); + return InputResult::CLOSED; + } + + char *line = (char *)data; + char *newline = (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 */ + *newline = 0; + + if (!HandleLine(line)) { + LockClose(); + return InputResult::CLOSED; + } + + if (state == RESPONSE) { + if (!SendResponse()) + return InputResult::CLOSED; + + if (head_method) { + LockClose(); + return InputResult::CLOSED; + } + } + + return InputResult::AGAIN; +} + +void +HttpdClient::OnSocketError(Error &&error) +{ + LogError(error); +} + +void +HttpdClient::OnSocketClosed() +{ + LockClose(); +} diff --git a/src/output/plugins/HttpdClient.hxx b/src/output/plugins/HttpdClient.hxx new file mode 100644 index 000000000..f94f05769 --- /dev/null +++ b/src/output/plugins/HttpdClient.hxx @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.h" + +#include <queue> +#include <list> + +#include <stddef.h> + +class HttpdOutput; +class Page; + +class HttpdClient final : BufferedSocket { + /** + * The httpd output object this client is connected to. + */ + HttpdOutput &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::queue<Page *, std::list<Page *>> pages; + + /** + * The sum of all page sizes in #pages. + */ + size_t queue_size; + + /** + * 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; + + /** + * Is this a HEAD request? + */ + bool head_method; + + /** + * 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(); + + /** + * 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); + +private: + void ClearQueue(); + +protected: + virtual bool OnSocketReady(unsigned flags) override; + virtual InputResult OnSocketInput(void *data, size_t length) override; + virtual void OnSocketError(Error &&error) override; + virtual void OnSocketClosed() override; +}; + +#endif diff --git a/src/output/plugins/HttpdInternal.hxx b/src/output/plugins/HttpdInternal.hxx new file mode 100644 index 000000000..506730d11 --- /dev/null +++ b/src/output/plugins/HttpdInternal.hxx @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "event/DeferredMonitor.hxx" +#include "util/Cast.hxx" + +#ifdef _LIBCPP_VERSION +/* can't use incomplete template arguments with libc++ */ +#include "HttpdClient.hxx" +#endif + +#include <forward_list> +#include <queue> +#include <list> + +struct config_param; +class Error; +class EventLoop; +class ServerSocket; +class HttpdClient; +class Page; +struct Encoder; +struct Tag; + +class HttpdOutput final : ServerSocket, DeferredMonitor { + 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; + +public: + /** + * The MIME type produced by the #encoder. + */ + const char *content_type; + + /** + * This mutex protects the listener socket and the client + * list. + */ + mutable Mutex mutex; + + /** + * This condition gets signalled when an item is removed from + * #pages. + */ + Cond cond; + +private: + /** + * 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 page queue, i.e. pages from the encoder to be + * broadcasted to all clients. This container is necessary to + * pass pages from the OutputThread to the IOThread. It is + * protected by #mutex, and removing signals #cond. + */ + std::queue<Page *, std::list<Page *>> pages; + + public: + /** + * The configured name. + */ + char const *name; + /** + * The configured genre. + */ + char const *genre; + /** + * The configured website address. + */ + char const *website; + +private: + /** + * 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; + +public: + HttpdOutput(EventLoop &_loop); + ~HttpdOutput(); + +#if GCC_CHECK_VERSION(4,6) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#endif + + static constexpr HttpdOutput *Cast(audio_output *ao) { + return ContainerCast(ao, HttpdOutput, base); + } + +#if GCC_CHECK_VERSION(4,6) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + + using DeferredMonitor::GetEventLoop; + + bool Init(const config_param ¶m, Error &error); + + void Finish() { + ao_base_finish(&base); + } + + bool Configure(const config_param ¶m, Error &error); + + audio_output *InitAndConfigure(const config_param ¶m, + Error &error) { + if (!Init(param, error)) + return nullptr; + + if (!Configure(param, error)) { + Finish(); + return nullptr; + } + + return &base; + } + + 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; + + gcc_pure + unsigned Delay() 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); + + size_t Play(const void *chunk, size_t size, Error &error); + + void CancelAllClients(); + +private: + virtual void RunDeferred() override; + + 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/plugins/HttpdOutputPlugin.cxx b/src/output/plugins/HttpdOutputPlugin.cxx new file mode 100644 index 000000000..6921cb808 --- /dev/null +++ b/src/output/plugins/HttpdOutputPlugin.cxx @@ -0,0 +1,601 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "encoder/EncoderPlugin.hxx" +#include "encoder/EncoderList.hxx" +#include "system/Resolver.hxx" +#include "Page.hxx" +#include "IcyMetaDataServer.hxx" +#include "system/fd_util.h" +#include "IOThread.hxx" +#include "event/Call.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#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), DeferredMonitor(_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; + + bool result = false; + BlockingCall(GetEventLoop(), [this, &error, &result](){ + result = ServerSocket::Open(error); + }); + return result; +} + +inline void +HttpdOutput::Unbind() +{ + assert(!open); + + BlockingCall(GetEventLoop(), [this](){ + 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 == nullptr) { + 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 != nullptr && + 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; +} + +inline bool +HttpdOutput::Init(const config_param ¶m, Error &error) +{ + return ao_base_init(&base, &httpd_output_plugin, param, error); +} + +static struct audio_output * +httpd_output_init(const config_param ¶m, Error &error) +{ + HttpdOutput *httpd = new HttpdOutput(io_thread_get()); + + audio_output *result = httpd->InitAndConfigure(param, error); + if (result == nullptr) + delete httpd; + + return result; +} + +static void +httpd_output_finish(struct audio_output *ao) +{ + HttpdOutput *httpd = HttpdOutput::Cast(ao); + + httpd->Finish(); + 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::RunDeferred() +{ + /* this method runs in the IOThread; it broadcasts pages from + our own queue to all clients */ + + const ScopeLock protect(mutex); + + while (!pages.empty()) { + Page *page = pages.front(); + pages.pop(); + + for (auto &client : clients) + client.PushPage(page); + + page->Unref(); + } + + /* wake up the client that may be waiting for the queue to be + flushed */ + cond.broadcast(); +} + +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) { + const auto hostaddr = sockaddr_to_string(&address, + address_length); + // TODO: shall we obtain the program name from argv[0]? + const char *progname = "mpd"; + + 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.c_str()); + close_socket(fd); + return; + } + } +#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 nullptr; + + return Page::Copy(buffer, size); +} + +static bool +httpd_output_enable(struct audio_output *ao, Error &error) +{ + HttpdOutput *httpd = HttpdOutput::Cast(ao); + + return httpd->Bind(error); +} + +static void +httpd_output_disable(struct audio_output *ao) +{ + HttpdOutput *httpd = HttpdOutput::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 = HttpdOutput::Cast(ao); + + const ScopeLock protect(httpd->mutex); + return httpd->Open(audio_format, error); +} + +inline void +HttpdOutput::Close() +{ + assert(open); + + open = false; + + delete timer; + + BlockingCall(GetEventLoop(), [this](){ + clients.clear(); + }); + + if (header != nullptr) + header->Unref(); + + encoder_close(encoder); +} + +static void +httpd_output_close(struct audio_output *ao) +{ + HttpdOutput *httpd = HttpdOutput::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 != nullptr) + client.PushPage(header); +} + +inline unsigned +HttpdOutput::Delay() const +{ + if (!LockHasClients() && 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(); + + /* 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 timer->IsStarted() + ? timer->GetDelay() + : 0; +} + +static unsigned +httpd_output_delay(struct audio_output *ao) +{ + HttpdOutput *httpd = HttpdOutput::Cast(ao); + + return httpd->Delay(); +} + +void +HttpdOutput::BroadcastPage(Page *page) +{ + assert(page != nullptr); + + mutex.lock(); + pages.push(page); + page->Ref(); + mutex.unlock(); + + DeferredMonitor::Schedule(); +} + +void +HttpdOutput::BroadcastFromEncoder() +{ + /* synchronize with the IOThread */ + mutex.lock(); + while (!pages.empty()) + cond.wait(mutex); + + Page *page; + while ((page = ReadPage()) != nullptr) + pages.push(page); + + mutex.unlock(); + + DeferredMonitor::Schedule(); +} + +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; +} + +inline size_t +HttpdOutput::Play(const void *chunk, size_t size, Error &error) +{ + if (LockHasClients()) { + if (!EncodeAndPlay(chunk, size, error)) + return 0; + } + + if (!timer->IsStarted()) + timer->Start(); + timer->Add(size); + + return size; +} + +static size_t +httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + HttpdOutput *httpd = HttpdOutput::Cast(ao); + + return httpd->Play(chunk, size, error); +} + +static bool +httpd_output_pause(struct audio_output *ao) +{ + HttpdOutput *httpd = HttpdOutput::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 != nullptr); + + 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 != nullptr) { + if (header != nullptr) + header->Unref(); + header = page; + BroadcastPage(page); + } + } else { + /* use Icy-Metadata */ + + if (metadata != nullptr) + metadata->Unref(); + + static constexpr TagType types[] = { + TAG_ALBUM, TAG_ARTIST, TAG_TITLE, + TAG_NUM_OF_ITEM_TYPES + }; + + metadata = icy_server_metadata_page(*tag, &types[0]); + if (metadata != nullptr) { + 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 = HttpdOutput::Cast(ao); + + httpd->SendTag(tag); +} + +inline void +HttpdOutput::CancelAllClients() +{ + const ScopeLock protect(mutex); + + while (!pages.empty()) { + Page *page = pages.front(); + pages.pop(); + page->Unref(); + } + + for (auto &client : clients) + client.CancelQueue(); + + cond.broadcast(); +} + +static void +httpd_output_cancel(struct audio_output *ao) +{ + HttpdOutput *httpd = HttpdOutput::Cast(ao); + + BlockingCall(io_thread_get(), [httpd](){ + httpd->CancelAllClients(); + }); +} + +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/plugins/HttpdOutputPlugin.hxx b/src/output/plugins/HttpdOutputPlugin.hxx new file mode 100644 index 000000000..78218e5f0 --- /dev/null +++ b/src/output/plugins/HttpdOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx new file mode 100644 index 000000000..b981c1292 --- /dev/null +++ b/src/output/plugins/JackOutputPlugin.cxx @@ -0,0 +1,765 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "config/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> + +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) +{ + LogDefault(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/plugins/JackOutputPlugin.hxx b/src/output/plugins/JackOutputPlugin.hxx new file mode 100644 index 000000000..ee3fe9238 --- /dev/null +++ b/src/output/plugins/JackOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/NullOutputPlugin.cxx b/src/output/plugins/NullOutputPlugin.cxx new file mode 100644 index 000000000..c336d86e6 --- /dev/null +++ b/src/output/plugins/NullOutputPlugin.cxx @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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" + +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/plugins/NullOutputPlugin.hxx b/src/output/plugins/NullOutputPlugin.hxx new file mode 100644 index 000000000..05b8ef3d8 --- /dev/null +++ b/src/output/plugins/NullOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/OSXOutputPlugin.cxx b/src/output/plugins/OSXOutputPlugin.cxx new file mode 100644 index 000000000..c247336d7 --- /dev/null +++ b/src/output/plugins/OSXOutputPlugin.cxx @@ -0,0 +1,428 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/DynamicFifoBuffer.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "system/ByteOrder.hxx" +#include "Log.hxx" + +#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; + + DynamicFifoBuffer<uint8_t> *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 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(); + + auto src = od->buffer->Read(); + if (!src.IsEmpty()) { + if (src.size > buffer_size) + src.size = buffer_size; + + memcpy(buffer->mData, src.data, src.size); + od->buffer->Consume(src.size); + } + + od->condition.signal(); + od->mutex.unlock(); + + buffer->mDataByteSize = src.size; + + 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); + od->buffer->Clear(); +} + +static void +osx_output_close(struct audio_output *ao) +{ + OSXOutput *od = (OSXOutput *)ao; + + AudioOutputUnitStop(od->au); + AudioUnitUninitialize(od->au); + + delete 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 (IsBigEndian()) + stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; + + 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.Format(osx_output_domain, status, + "Unable to initialize OS X audio unit: %s", + GetMacOSStatusCommentString(status)); + return false; + } + + /* create a buffer of 1s */ + od->buffer = new DynamicFifoBuffer<uint8_t>(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); + + DynamicFifoBuffer<uint8_t>::Range dest; + while (true) { + dest = od->buffer->Write(); + if (!dest.IsEmpty()) + break; + + /* wait for some free space in the buffer */ + od->condition.wait(od->mutex); + } + + if (size > dest.size) + size = dest.size; + + memcpy(dest.data, chunk, size); + od->buffer->Append(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/plugins/OSXOutputPlugin.hxx b/src/output/plugins/OSXOutputPlugin.hxx new file mode 100644 index 000000000..0de10f83e --- /dev/null +++ b/src/output/plugins/OSXOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/OpenALOutputPlugin.cxx b/src/output/plugins/OpenALOutputPlugin.cxx new file mode 100644 index 000000000..f590f0ea0 --- /dev/null +++ b/src/output/plugins/OpenALOutputPlugin.cxx @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 __APPLE__ +#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/plugins/OpenALOutputPlugin.hxx b/src/output/plugins/OpenALOutputPlugin.hxx new file mode 100644 index 000000000..eb43d1aa5 --- /dev/null +++ b/src/output/plugins/OpenALOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/OssOutputPlugin.cxx b/src/output/plugins/OssOutputPlugin.cxx new file mode 100644 index 000000000..2157a9476 --- /dev/null +++ b/src/output/plugins/OssOutputPlugin.cxx @@ -0,0 +1,776 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "mixer/MixerList.hxx" +#include "system/fd_util.h" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "util/Macros.hxx" +#include "system/ByteOrder.hxx" +#include "Log.hxx" + +#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 = ARRAY_SIZE(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[ARRAY_SIZE(default_devices)]; + enum oss_stat ret[ARRAY_SIZE(default_devices)]; + + const config_param empty; + for (int i = ARRAY_SIZE(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 = ARRAY_SIZE(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 && + !IsLittleEndian()); +#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/plugins/OssOutputPlugin.hxx b/src/output/plugins/OssOutputPlugin.hxx new file mode 100644 index 000000000..4762fa652 --- /dev/null +++ b/src/output/plugins/OssOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/PipeOutputPlugin.cxx b/src/output/plugins/PipeOutputPlugin.cxx new file mode 100644 index 000000000..2eb0c6dd8 --- /dev/null +++ b/src/output/plugins/PipeOutputPlugin.cxx @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "config/ConfigError.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <string> + +#include <stdio.h> + +struct PipeOutput { + struct audio_output base; + + std::string 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.GetBlockValue("command", ""); + if (cmd.empty()) { + 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; + + 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.c_str(), "w"); + if (pd->fh == nullptr) { + error.FormatErrno("Error opening pipe \"%s\"", + pd->cmd.c_str()); + 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/plugins/PipeOutputPlugin.hxx b/src/output/plugins/PipeOutputPlugin.hxx new file mode 100644 index 000000000..42b01b9f7 --- /dev/null +++ b/src/output/plugins/PipeOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/PulseOutputPlugin.cxx b/src/output/plugins/PulseOutputPlugin.cxx new file mode 100644 index 000000000..6bb66450e --- /dev/null +++ b/src/output/plugins/PulseOutputPlugin.cxx @@ -0,0 +1,889 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "mixer/MixerList.hxx" +#include "mixer/plugins/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/plugins/PulseOutputPlugin.hxx b/src/output/plugins/PulseOutputPlugin.hxx new file mode 100644 index 000000000..9df557282 --- /dev/null +++ b/src/output/plugins/PulseOutputPlugin.hxx @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 pa_cvolume *volume, Error &error); + +#endif diff --git a/src/output/plugins/RecorderOutputPlugin.cxx b/src/output/plugins/RecorderOutputPlugin.cxx new file mode 100644 index 000000000..685438009 --- /dev/null +++ b/src/output/plugins/RecorderOutputPlugin.cxx @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "encoder/EncoderPlugin.hxx" +#include "encoder/EncoderList.hxx" +#include "config/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/plugins/RecorderOutputPlugin.hxx b/src/output/plugins/RecorderOutputPlugin.hxx new file mode 100644 index 000000000..4fac911a1 --- /dev/null +++ b/src/output/plugins/RecorderOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/RoarOutputPlugin.cxx b/src/output/plugins/RoarOutputPlugin.cxx new file mode 100644 index 000000000..1192b2625 --- /dev/null +++ b/src/output/plugins/RoarOutputPlugin.cxx @@ -0,0 +1,428 @@ +/* + * Copyright (C) 2003-2014 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 "mixer/MixerList.hxx" +#include "thread/Mutex.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <string> + +/* libroar/services.h declares roar_service_stream::new - work around + this C++ problem */ +#define new _new +#include <roaraudio.h> +#undef new + +class RoarOutput { + struct audio_output base; + + std::string host, name; + + roar_vs_t * vss; + int err; + int role; + struct roar_connection con; + struct roar_audio_info info; + mutable Mutex mutex; + volatile bool alive; + +public: + RoarOutput() + :err(ROAR_ERROR_NONE) {} + + operator audio_output *() { + return &base; + } + + bool Initialize(const config_param ¶m, Error &error) { + return ao_base_init(&base, &roar_output_plugin, param, + error); + } + + void Deinitialize() { + ao_base_finish(&base); + } + + void Configure(const config_param ¶m); + + bool Open(AudioFormat &audio_format, Error &error); + void Close(); + + void SendTag(const Tag &tag); + size_t Play(const void *chunk, size_t size, Error &error); + void Cancel(); + + int GetVolume() const; + bool SetVolume(unsigned volume); +}; + +static constexpr Domain roar_output_domain("roar_output"); + +inline int +RoarOutput::GetVolume() const +{ + const ScopeLock protect(mutex); + + if (vss == nullptr || !alive) + return -1; + + float l, r; + int error; + if (roar_vs_volume_get(vss, &l, &r, &error) < 0) + return -1; + + return (l + r) * 50; +} + +int +roar_output_get_volume(RoarOutput *roar) +{ + return roar->GetVolume(); +} + +bool +RoarOutput::SetVolume(unsigned volume) +{ + assert(volume <= 100); + + const ScopeLock protect(mutex); + if (vss == nullptr || !alive) + return false; + + int error; + float level = volume / 100.0; + + roar_vs_volume_mono(vss, level, &error); + return true; +} + +bool +roar_output_set_volume(RoarOutput *roar, unsigned volume) +{ + return roar->SetVolume(volume); +} + +inline void +RoarOutput::Configure(const config_param ¶m) +{ + host = param.GetBlockValue("server", ""); + name = param.GetBlockValue("name", "MPD"); + + const char *_role = param.GetBlockValue("role", "music"); + 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 (!self->Initialize(param, error)) { + delete self; + return nullptr; + } + + self->Configure(param); + return *self; +} + +static void +roar_finish(struct audio_output *ao) +{ + RoarOutput *self = (RoarOutput *)ao; + + self->Deinitialize(); + 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; + } +} + +inline bool +RoarOutput::Open(AudioFormat &audio_format, Error &error) +{ + const ScopeLock protect(mutex); + + if (roar_simple_connect(&con, + host.empty() ? nullptr : host.c_str(), + name.c_str()) < 0) { + error.Set(roar_output_domain, + "Failed to connect to Roar server"); + return false; + } + + vss = roar_vs_new_from_con(&con, &err); + + if (vss == nullptr || err != ROAR_ERROR_NONE) { + error.Set(roar_output_domain, "Failed to connect to server"); + return false; + } + + roar_use_audio_format(&info, audio_format); + + if (roar_vs_stream(vss, &info, ROAR_DIR_PLAY, &err) < 0) { + error.Set(roar_output_domain, "Failed to start stream"); + return false; + } + + roar_vs_role(vss, role, &err); + alive = true; + return true; +} + +static bool +roar_open(struct audio_output *ao, AudioFormat &audio_format, Error &error) +{ + RoarOutput *self = (RoarOutput *)ao; + + return self->Open(audio_format, error); +} + +inline void +RoarOutput::Close() +{ + const ScopeLock protect(mutex); + + alive = false; + + if (vss != nullptr) + roar_vs_close(vss, ROAR_VS_TRUE, &err); + vss = nullptr; + roar_disconnect(&con); +} + +static void +roar_close(struct audio_output *ao) +{ + RoarOutput *self = (RoarOutput *)ao; + self->Close(); +} + +inline void +RoarOutput::Cancel() +{ + const ScopeLock protect(mutex); + + if (vss == nullptr) + return; + + roar_vs_t *_vss = vss; + vss = nullptr; + roar_vs_close(_vss, ROAR_VS_TRUE, &err); + alive = false; + + _vss = roar_vs_new_from_con(&con, &err); + if (_vss == nullptr) + return; + + if (roar_vs_stream(_vss, &info, ROAR_DIR_PLAY, &err) < 0) { + roar_vs_close(_vss, ROAR_VS_TRUE, &err); + LogError(roar_output_domain, "Failed to start stream"); + return; + } + + roar_vs_role(_vss, role, &err); + vss = _vss; + alive = true; +} + +static void +roar_cancel(struct audio_output *ao) +{ + RoarOutput *self = (RoarOutput *)ao; + + self->Cancel(); +} + +inline size_t +RoarOutput::Play(const void *chunk, size_t size, Error &error) +{ + if (vss == nullptr) { + error.Set(roar_output_domain, "Connection is invalid"); + return 0; + } + + ssize_t nbytes = roar_vs_write(vss, chunk, size, &err); + if (nbytes <= 0) { + error.Set(roar_output_domain, "Failed to play data"); + return 0; + } + + return nbytes; +} + +static size_t +roar_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + RoarOutput *self = (RoarOutput *)ao; + return self->Play(chunk, size, error); +} + +static const char* +roar_tag_convert(TagType 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; + } +} + +inline void +RoarOutput::SendTag(const Tag &tag) +{ + if (vss == nullptr) + return; + + const ScopeLock protect(mutex); + + size_t cnt = 1; + struct roar_keyval vals[32]; + char uuid_buf[32][64]; + + char timebuf[16]; + snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", + tag.time / 3600, (tag.time % 3600) / 60, tag.time % 60); + + vals[0].key = const_cast<char *>("LENGTH"); + vals[0].value = timebuf; + + for (unsigned i = 0; i < tag.num_items && cnt < 32; i++) + { + bool is_uuid = false; + const char *key = roar_tag_convert(tag.items[i]->type, + &is_uuid); + if (key != nullptr) { + vals[cnt].key = const_cast<char *>(key); + + if (is_uuid) { + snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s", + tag.items[i]->value); + vals[cnt].value = uuid_buf[cnt]; + } else { + vals[cnt].value = tag.items[i]->value; + } + + cnt++; + } + } + + roar_vs_meta(vss, vals, cnt, &(err)); +} + +static void +roar_send_tag(struct audio_output *ao, const Tag *meta) +{ + RoarOutput *self = (RoarOutput *)ao; + self->SendTag(*meta); +} + +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/plugins/RoarOutputPlugin.hxx b/src/output/plugins/RoarOutputPlugin.hxx new file mode 100644 index 000000000..27c5dc420 --- /dev/null +++ b/src/output/plugins/RoarOutputPlugin.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 + +class 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/plugins/ShoutOutputPlugin.cxx b/src/output/plugins/ShoutOutputPlugin.cxx new file mode 100644 index 000000000..f86e5ce4b --- /dev/null +++ b/src/output/plugins/ShoutOutputPlugin.cxx @@ -0,0 +1,544 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "encoder/EncoderPlugin.hxx" +#include "encoder/EncoderList.hxx" +#include "config/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/plugins/ShoutOutputPlugin.hxx b/src/output/plugins/ShoutOutputPlugin.hxx new file mode 100644 index 000000000..d437e0b0d --- /dev/null +++ b/src/output/plugins/ShoutOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/SolarisOutputPlugin.cxx b/src/output/plugins/SolarisOutputPlugin.cxx new file mode 100644 index 000000000..38ed2e314 --- /dev/null +++ b/src/output/plugins/SolarisOutputPlugin.cxx @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/plugins/SolarisOutputPlugin.hxx b/src/output/plugins/SolarisOutputPlugin.hxx new file mode 100644 index 000000000..9ce848a40 --- /dev/null +++ b/src/output/plugins/SolarisOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/WinmmOutputPlugin.cxx b/src/output/plugins/WinmmOutputPlugin.cxx new file mode 100644 index 000000000..66d49eb9a --- /dev/null +++ b/src/output/plugins/WinmmOutputPlugin.cxx @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "mixer/MixerList.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "util/Macros.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)) { + delete wo; + return nullptr; + } + + const char *device = param.GetBlockValue("device"); + if (!get_device_id(device, &wo->device_id, error)) { + ao_base_finish(&wo->base); + delete 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 < ARRAY_SIZE(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 < ARRAY_SIZE(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) % + ARRAY_SIZE(wo->buffers); + + return size; +} + +static bool +winmm_drain_all_buffers(WinmmOutput *wo, Error &error) +{ + for (unsigned i = wo->next_buffer; i < ARRAY_SIZE(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 < ARRAY_SIZE(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/plugins/WinmmOutputPlugin.hxx b/src/output/plugins/WinmmOutputPlugin.hxx new file mode 100644 index 000000000..1409a2e8c --- /dev/null +++ b/src/output/plugins/WinmmOutputPlugin.hxx @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.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/pcm/ChannelsConverter.cxx b/src/pcm/ChannelsConverter.cxx new file mode 100644 index 000000000..f93f4f677 --- /dev/null +++ b/src/pcm/ChannelsConverter.cxx @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ChannelsConverter.hxx" +#include "PcmChannels.hxx" +#include "Domain.hxx" +#include "util/ConstBuffer.hxx" +#include "util/Error.hxx" + +#include <assert.h> + +bool +PcmChannelsConverter::Open(SampleFormat _format, + unsigned _src_channels, unsigned _dest_channels, + gcc_unused Error &error) +{ + assert(_format != SampleFormat::UNDEFINED); + + switch (_format) { + case SampleFormat::S16: + case SampleFormat::S24_P32: + case SampleFormat::S32: + case SampleFormat::FLOAT: + break; + + default: + error.Format(pcm_domain, + "PCM channel conversion for %s is not implemented", + sample_format_to_string(format)); + return false; + } + + format = _format; + src_channels = _src_channels; + dest_channels = _dest_channels; + return true; +} + +void +PcmChannelsConverter::Close() +{ +#ifndef NDEBUG + format = SampleFormat::UNDEFINED; +#endif +} + +ConstBuffer<void> +PcmChannelsConverter::Convert(ConstBuffer<void> src, gcc_unused Error &error) +{ + switch (format) { + case SampleFormat::UNDEFINED: + case SampleFormat::S8: + case SampleFormat::DSD: + assert(false); + gcc_unreachable(); + + case SampleFormat::S16: + return pcm_convert_channels_16(buffer, dest_channels, + src_channels, + ConstBuffer<int16_t>::FromVoid(src)).ToVoid(); + + case SampleFormat::S24_P32: + return pcm_convert_channels_24(buffer, dest_channels, + src_channels, + ConstBuffer<int32_t>::FromVoid(src)).ToVoid(); + + case SampleFormat::S32: + return pcm_convert_channels_32(buffer, dest_channels, + src_channels, + ConstBuffer<int32_t>::FromVoid(src)).ToVoid(); + + case SampleFormat::FLOAT: + return pcm_convert_channels_float(buffer, dest_channels, + src_channels, + ConstBuffer<float>::FromVoid(src)).ToVoid(); + } + + assert(false); + gcc_unreachable(); +} diff --git a/src/pcm/ChannelsConverter.hxx b/src/pcm/ChannelsConverter.hxx new file mode 100644 index 000000000..1374f9f5d --- /dev/null +++ b/src/pcm/ChannelsConverter.hxx @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_CONVERTER_HXX +#define MPD_PCM_CHANNELS_CONVERTER_HXX + +#include "check.h" +#include "AudioFormat.hxx" +#include "PcmBuffer.hxx" + +#ifndef NDEBUG +#include <assert.h> +#endif + +class Error; +template<typename T> struct ConstBuffer; + +/** + * A class that converts samples from one format to another. + */ +class PcmChannelsConverter { + SampleFormat format; + unsigned src_channels, dest_channels; + + PcmBuffer buffer; + +public: +#ifndef NDEBUG + PcmChannelsConverter() + :format(SampleFormat::UNDEFINED) {} + + ~PcmChannelsConverter() { + assert(format == SampleFormat::UNDEFINED); + } +#endif + + /** + * Opens the object, prepare for Convert(). + * + * @param format the sample format + * @param src_channels the number of source channels + * @param dest_channels the number of destination channels + * @param error location to store the error + * @return true on success + */ + bool Open(SampleFormat format, + unsigned src_channels, unsigned dest_channels, + Error &error); + + /** + * Closes the object. After that, you may call Open() again. + */ + void Close(); + + /** + * Convert a block of PCM data. + * + * @param src the input buffer + * @param error location to store the error + * @return the destination buffer on success, + * ConstBuffer::Null() on error + */ + gcc_pure + ConstBuffer<void> Convert(ConstBuffer<void> src, Error &error); +}; + +#endif diff --git a/src/pcm/ConfiguredResampler.cxx b/src/pcm/ConfiguredResampler.cxx new file mode 100644 index 000000000..f6aec3f95 --- /dev/null +++ b/src/pcm/ConfiguredResampler.cxx @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ConfiguredResampler.hxx" +#include "FallbackResampler.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" +#include "config/ConfigError.hxx" +#include "util/Error.hxx" + +#ifdef HAVE_LIBSAMPLERATE +#include "LibsamplerateResampler.hxx" +#endif + +#ifdef HAVE_SOXR +#include "SoxrResampler.hxx" +#endif + +#include <string.h> + +enum class SelectedResampler { + FALLBACK, + +#ifdef HAVE_LIBSAMPLERATE + LIBSAMPLERATE, +#endif + +#ifdef HAVE_SOXR + SOXR, +#endif +}; + +static SelectedResampler selected_resampler = SelectedResampler::FALLBACK; + +bool +pcm_resampler_global_init(Error &error) +{ + const char *converter = + config_get_string(CONF_SAMPLERATE_CONVERTER, ""); + + if (strcmp(converter, "internal") == 0) + return true; + +#ifdef HAVE_SOXR + if (memcmp(converter, "soxr", 4) == 0) { + selected_resampler = SelectedResampler::SOXR; + return pcm_resample_soxr_global_init(converter, error); + } +#endif + +#ifdef HAVE_LIBSAMPLERATE + selected_resampler = SelectedResampler::LIBSAMPLERATE; + return pcm_resample_lsr_global_init(converter, error); +#endif + + if (*converter == 0) + return true; + + error.Format(config_domain, + "The samplerate_converter '%s' is not available", + converter); + return false; +} + +PcmResampler * +pcm_resampler_create() +{ + switch (selected_resampler) { + case SelectedResampler::FALLBACK: + return new FallbackPcmResampler(); + +#ifdef HAVE_LIBSAMPLERATE + case SelectedResampler::LIBSAMPLERATE: + return new LibsampleratePcmResampler(); +#endif + +#ifdef HAVE_SOXR + case SelectedResampler::SOXR: + return new SoxrPcmResampler(); +#endif + } + + gcc_unreachable(); +} diff --git a/src/pcm/ConfiguredResampler.hxx b/src/pcm/ConfiguredResampler.hxx new file mode 100644 index 000000000..2b14b381e --- /dev/null +++ b/src/pcm/ConfiguredResampler.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CONFIGURED_RESAMPLER_HXX +#define MPD_CONFIGURED_RESAMPLER_HXX + +#include "check.h" + +class Error; +class PcmResampler; + +bool +pcm_resampler_global_init(Error &error); + +/** + * Create a #PcmResampler instance from the implementation class + * configured in mpd.conf. + */ +PcmResampler * +pcm_resampler_create(); + +#endif diff --git a/src/pcm/Domain.cxx b/src/pcm/Domain.cxx new file mode 100644 index 000000000..ecd5c22a4 --- /dev/null +++ b/src/pcm/Domain.cxx @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "Domain.hxx" +#include "util/Domain.hxx" + +const Domain pcm_domain("pcm"); diff --git a/src/pcm/Domain.hxx b/src/pcm/Domain.hxx new file mode 100644 index 000000000..781d5c71b --- /dev/null +++ b/src/pcm/Domain.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License 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_DOMAIN_HXX +#define PCM_DOMAIN_HXX + +class Domain; + +extern const Domain pcm_domain; + +#endif diff --git a/src/pcm/FallbackResampler.cxx b/src/pcm/FallbackResampler.cxx new file mode 100644 index 000000000..120b3fbdf --- /dev/null +++ b/src/pcm/FallbackResampler.cxx @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FallbackResampler.hxx" + +#include <assert.h> + +AudioFormat +FallbackPcmResampler::Open(AudioFormat &af, unsigned new_sample_rate, + gcc_unused Error &error) +{ + assert(af.IsValid()); + assert(audio_valid_sample_rate(new_sample_rate)); + + switch (af.format) { + case SampleFormat::UNDEFINED: + assert(false); + gcc_unreachable(); + + case SampleFormat::S8: + af.format = SampleFormat::S16; + break; + + case SampleFormat::S16: + case SampleFormat::FLOAT: + case SampleFormat::S24_P32: + case SampleFormat::S32: + break; + + case SampleFormat::DSD: + af.format = SampleFormat::FLOAT; + break; + } + + format = af; + out_rate = new_sample_rate; + + AudioFormat result = af; + result.sample_rate = new_sample_rate; + return result; +} + +void +FallbackPcmResampler::Close() +{ +} + +template<typename T> +static ConstBuffer<T> +pcm_resample_fallback(PcmBuffer &buffer, + unsigned channels, + unsigned src_rate, + ConstBuffer<T> src, + unsigned dest_rate) +{ + unsigned dest_pos = 0; + unsigned src_frames = src.size / channels; + 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.data); + T *dest_buffer = (T *)buffer.Get(dest_size); + + assert((src.size % 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.data[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.data[src_pos]; + dest_buffer[dest_pos++] = src.data[src_pos + 1]; + } + break; + } + + return { dest_buffer, dest_samples }; +} + +template<typename T> +static ConstBuffer<void> +pcm_resample_fallback_void(PcmBuffer &buffer, + unsigned channels, + unsigned src_rate, + ConstBuffer<void> src, + unsigned dest_rate) +{ + const auto typed_src = ConstBuffer<T>::FromVoid(src); + return pcm_resample_fallback(buffer, channels, src_rate, typed_src, + dest_rate); +} + +ConstBuffer<void> +FallbackPcmResampler::Resample(ConstBuffer<void> src, gcc_unused Error &error) +{ + switch (format.format) { + case SampleFormat::UNDEFINED: + case SampleFormat::S8: + case SampleFormat::DSD: + assert(false); + gcc_unreachable(); + + case SampleFormat::S16: + return pcm_resample_fallback_void<int16_t>(buffer, + format.channels, + format.sample_rate, + src, + out_rate); + + case SampleFormat::FLOAT: + case SampleFormat::S24_P32: + case SampleFormat::S32: + return pcm_resample_fallback_void<int32_t>(buffer, + format.channels, + format.sample_rate, + src, + out_rate); + } + + assert(false); + gcc_unreachable(); +} diff --git a/src/pcm/FallbackResampler.hxx b/src/pcm/FallbackResampler.hxx new file mode 100644 index 000000000..0b7f9d57b --- /dev/null +++ b/src/pcm/FallbackResampler.hxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_FALLBACK_RESAMPLER_HXX +#define MPD_PCM_FALLBACK_RESAMPLER_HXX + +#include "Resampler.hxx" +#include "PcmBuffer.hxx" +#include "AudioFormat.hxx" + +/** + * A naive resampler that is used when no external library was found + * (or when the user explicitly asks for bad quality). + */ +class FallbackPcmResampler final : public PcmResampler { + AudioFormat format; + unsigned out_rate; + + PcmBuffer buffer; + +public: + virtual AudioFormat Open(AudioFormat &af, unsigned new_sample_rate, + Error &error) override; + virtual void Close() override; + virtual ConstBuffer<void> Resample(ConstBuffer<void> src, + Error &error) override; +}; + +#endif diff --git a/src/pcm/FormatConverter.cxx b/src/pcm/FormatConverter.cxx new file mode 100644 index 000000000..64e2d8594 --- /dev/null +++ b/src/pcm/FormatConverter.cxx @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FormatConverter.hxx" +#include "PcmFormat.hxx" +#include "Domain.hxx" +#include "util/ConstBuffer.hxx" +#include "util/Error.hxx" + +#include <assert.h> + +bool +PcmFormatConverter::Open(SampleFormat _src_format, SampleFormat _dest_format, + gcc_unused Error &error) +{ + assert(_src_format != SampleFormat::UNDEFINED); + assert(_dest_format != SampleFormat::UNDEFINED); + + src_format = _src_format; + dest_format = _dest_format; + return true; +} + +void +PcmFormatConverter::Close() +{ +#ifndef NDEBUG + src_format = SampleFormat::UNDEFINED; + dest_format = SampleFormat::UNDEFINED; +#endif +} + +ConstBuffer<void> +PcmFormatConverter::Convert(ConstBuffer<void> src, Error &error) +{ + switch (dest_format) { + case SampleFormat::UNDEFINED: + assert(false); + gcc_unreachable(); + + case SampleFormat::S8: + case SampleFormat::DSD: + error.Format(pcm_domain, + "PCM conversion from %s to %s is not implemented", + sample_format_to_string(src_format), + sample_format_to_string(dest_format)); + return nullptr; + + case SampleFormat::S16: + return pcm_convert_to_16(buffer, dither, + src_format, + src).ToVoid(); + + case SampleFormat::S24_P32: + return pcm_convert_to_24(buffer, + src_format, + src).ToVoid(); + + case SampleFormat::S32: + return pcm_convert_to_32(buffer, + src_format, + src).ToVoid(); + + case SampleFormat::FLOAT: + return pcm_convert_to_float(buffer, + src_format, + src).ToVoid(); + } + + assert(false); + gcc_unreachable(); +} diff --git a/src/pcm/FormatConverter.hxx b/src/pcm/FormatConverter.hxx new file mode 100644 index 000000000..3d8b6fb75 --- /dev/null +++ b/src/pcm/FormatConverter.hxx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_CONVERTER_HXX +#define MPD_PCM_FORMAT_CONVERTER_HXX + +#include "check.h" +#include "AudioFormat.hxx" +#include "PcmBuffer.hxx" +#include "PcmDither.hxx" + +#ifndef NDEBUG +#include <assert.h> +#endif + +class Error; +template<typename T> struct ConstBuffer; + +/** + * A class that converts samples from one format to another. + */ +class PcmFormatConverter { + SampleFormat src_format, dest_format; + + PcmBuffer buffer; + PcmDither dither; + +public: +#ifndef NDEBUG + PcmFormatConverter() + :src_format(SampleFormat::UNDEFINED), + dest_format(SampleFormat::UNDEFINED) {} + + ~PcmFormatConverter() { + assert(src_format == SampleFormat::UNDEFINED); + assert(dest_format == SampleFormat::UNDEFINED); + } +#endif + + /** + * Opens the object, prepare for Convert(). + * + * @param src_format the sample format of incoming data + * @param dest_format the sample format of outgoing data + * @param error location to store the error + * @return true on success + */ + bool Open(SampleFormat src_format, SampleFormat dest_format, + Error &error); + + /** + * Closes the object. After that, you may call Open() again. + */ + void Close(); + + /** + * Convert a block of PCM data. + * + * @param src the input buffer + * @param error location to store the error + * @return the destination buffer on success, + * ConstBuffer::Null() on error + */ + gcc_pure + ConstBuffer<void> Convert(ConstBuffer<void> src, Error &error); +}; + +#endif diff --git a/src/pcm/GlueResampler.cxx b/src/pcm/GlueResampler.cxx new file mode 100644 index 000000000..0f5fe0271 --- /dev/null +++ b/src/pcm/GlueResampler.cxx @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "GlueResampler.hxx" +#include "ConfiguredResampler.hxx" +#include "Resampler.hxx" + +#include <assert.h> + +GluePcmResampler::GluePcmResampler() + :resampler(pcm_resampler_create()) {} + +GluePcmResampler::~GluePcmResampler() +{ + delete resampler; +} + +bool +GluePcmResampler::Open(AudioFormat src_format, unsigned new_sample_rate, + Error &error) +{ + assert(src_format.IsValid()); + assert(audio_valid_sample_rate(new_sample_rate)); + + AudioFormat requested_format = src_format; + AudioFormat dest_format = resampler->Open(requested_format, + new_sample_rate, + error); + if (!dest_format.IsValid()) + return false; + + assert(requested_format.channels == src_format.channels); + assert(dest_format.channels == src_format.channels); + assert(dest_format.sample_rate == new_sample_rate); + + if (requested_format.format != src_format.format && + !format_converter.Open(src_format.format, requested_format.format, + error)) + return false; + + src_sample_format = src_format.format; + requested_sample_format = requested_format.format; + output_sample_format = dest_format.format; + return true; +} + +void +GluePcmResampler::Close() +{ + if (requested_sample_format != src_sample_format) + format_converter.Close(); + + resampler->Close(); +} + +ConstBuffer<void> +GluePcmResampler::Resample(ConstBuffer<void> src, Error &error) +{ + assert(!src.IsNull()); + + if (requested_sample_format != src_sample_format) { + src = format_converter.Convert(src, error); + if (src.IsNull()) + return nullptr; + } + + return resampler->Resample(src, error); +} diff --git a/src/pcm/GlueResampler.hxx b/src/pcm/GlueResampler.hxx new file mode 100644 index 000000000..aff07823e --- /dev/null +++ b/src/pcm/GlueResampler.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_GLUE_RESAMPLER_HXX +#define MPD_GLUE_RESAMPLER_HXX + +#include "check.h" +#include "AudioFormat.hxx" +#include "FormatConverter.hxx" + +class Error; +class PcmResampler; +template<typename T> struct ConstBuffer; + +/** + * A glue class that integrates a #PcmResampler and automatically + * converts source data to the sample format required by the + * #PcmResampler instance. + */ +class GluePcmResampler { + PcmResampler *const resampler; + + SampleFormat src_sample_format, requested_sample_format; + SampleFormat output_sample_format; + + /** + * This object converts input data to the sample format + * requested by the #PcmResampler. + */ + PcmFormatConverter format_converter; + +public: + GluePcmResampler(); + ~GluePcmResampler(); + + bool Open(AudioFormat src_format, unsigned new_sample_rate, + Error &error); + void Close(); + + SampleFormat GetOutputSampleFormat() const { + return output_sample_format; + } + + ConstBuffer<void> Resample(ConstBuffer<void> src, Error &error); +}; + +#endif diff --git a/src/pcm/LibsamplerateResampler.cxx b/src/pcm/LibsamplerateResampler.cxx new file mode 100644 index 000000000..8b22f1e32 --- /dev/null +++ b/src/pcm/LibsamplerateResampler.cxx @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "LibsamplerateResampler.hxx" +#include "util/ASCII.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +static constexpr Domain libsamplerate_domain("libsamplerate"); + +static int lsr_converter = SRC_SINC_FASTEST; + +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 (StringEqualsCaseASCII(s, name, length)) { + 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; +} + +AudioFormat +LibsampleratePcmResampler::Open(AudioFormat &af, unsigned new_sample_rate, + Error &error) +{ + assert(af.IsValid()); + assert(audio_valid_sample_rate(new_sample_rate)); + + src_rate = af.sample_rate; + dest_rate = new_sample_rate; + channels = af.channels; + + /* libsamplerate works with floating point samples */ + af.format = SampleFormat::FLOAT; + + int src_error; + state = src_new(lsr_converter, channels, &src_error); + if (!state) { + error.Format(libsamplerate_domain, src_error, + "libsamplerate initialization has failed: %s", + src_strerror(src_error)); + return AudioFormat::Undefined(); + } + + memset(&data, 0, sizeof(data)); + + data.src_ratio = double(new_sample_rate) / double(af.sample_rate); + FormatDebug(libsamplerate_domain, + "setting samplerate conversion ratio to %.2lf", + data.src_ratio); + src_set_ratio(state, data.src_ratio); + + AudioFormat result = af; + result.sample_rate = new_sample_rate; + return result; +} + +void +LibsampleratePcmResampler::Close() +{ + state = src_delete(state); +} + +static bool +src_process(SRC_STATE *state, SRC_DATA *data, Error &error) +{ + int result = src_process(state, data); + if (result != 0) { + error.Format(libsamplerate_domain, result, + "libsamplerate has failed: %s", + src_strerror(result)); + return false; + } + + return true; +} + +inline ConstBuffer<float> +LibsampleratePcmResampler::Resample2(ConstBuffer<float> src, Error &error) +{ + assert(src.size % channels == 0); + + const unsigned src_frames = src.size / channels; + const unsigned dest_frames = + (src_frames * dest_rate + src_rate - 1) / src_rate; + size_t data_out_size = dest_frames * sizeof(float) * channels; + + data.data_in = const_cast<float *>(src.data); + data.data_out = (float *)buffer.Get(data_out_size); + data.input_frames = src_frames; + data.output_frames = dest_frames; + + if (!src_process(state, &data, error)) + return nullptr; + + return ConstBuffer<float>(data.data_out, + data.output_frames_gen * channels); +} + +ConstBuffer<void> +LibsampleratePcmResampler::Resample(ConstBuffer<void> src, Error &error) +{ + return Resample2(ConstBuffer<float>::FromVoid(src), error).ToVoid(); +} diff --git a/src/pcm/LibsamplerateResampler.hxx b/src/pcm/LibsamplerateResampler.hxx new file mode 100644 index 000000000..86d74d95a --- /dev/null +++ b/src/pcm/LibsamplerateResampler.hxx @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_LIBSAMPLERATE_RESAMPLER_HXX +#define MPD_PCM_LIBSAMPLERATE_RESAMPLER_HXX + +#include "Resampler.hxx" +#include "PcmBuffer.hxx" +#include "AudioFormat.hxx" + +#include <samplerate.h> + +/** + * A resampler using libsamplerate. + */ +class LibsampleratePcmResampler final : public PcmResampler { + unsigned src_rate, dest_rate; + unsigned channels; + + SRC_STATE *state; + SRC_DATA data; + + PcmBuffer buffer; + +public: + virtual AudioFormat Open(AudioFormat &af, unsigned new_sample_rate, + Error &error) override; + virtual void Close() override; + virtual ConstBuffer<void> Resample(ConstBuffer<void> src, + Error &error) override; + +private: + ConstBuffer<float> Resample2(ConstBuffer<float> src, Error &error); +}; + +bool +pcm_resample_lsr_global_init(const char *converter, Error &error); + +#endif diff --git a/src/pcm/PcmBuffer.cxx b/src/pcm/PcmBuffer.cxx index 578c579be..7bba2de47 100644 --- a/src/pcm/PcmBuffer.cxx +++ b/src/pcm/PcmBuffer.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,7 +19,6 @@ #include "config.h" #include "PcmBuffer.hxx" -#include "poison.h" void * PcmBuffer::Get(size_t new_size) diff --git a/src/pcm/PcmBuffer.hxx b/src/pcm/PcmBuffer.hxx index 717e24938..f56a85985 100644 --- a/src/pcm/PcmBuffer.hxx +++ b/src/pcm/PcmBuffer.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -49,6 +49,12 @@ public: */ gcc_malloc void *Get(size_t size); + + template<typename T> + gcc_malloc + T *GetT(size_t n) { + return (T *)Get(n * sizeof(T)); + } }; #endif diff --git a/src/pcm/PcmChannels.cxx b/src/pcm/PcmChannels.cxx index eb69985c1..276f31045 100644 --- a/src/pcm/PcmChannels.cxx +++ b/src/pcm/PcmChannels.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,9 @@ #include "config.h" #include "PcmChannels.hxx" #include "PcmBuffer.hxx" -#include "PcmUtils.hxx" +#include "Traits.hxx" +#include "AudioFormat.hxx" +#include "util/ConstBuffer.hxx" #include <assert.h> @@ -37,254 +39,143 @@ MonoToStereo(D dest, S src, S end) } -static void -pcm_convert_channels_16_2_to_1(int16_t *gcc_restrict dest, - const int16_t *gcc_restrict src, - const int16_t *gcc_restrict src_end) +template<SampleFormat F, class Traits=SampleTraits<F>> +static typename Traits::value_type +StereoToMono(typename Traits::value_type _a, + typename Traits::value_type _b) { - while (src < src_end) { - int32_t a = *src++, b = *src++; + typename Traits::sum_type a(_a); + typename Traits::sum_type b(_b); - *dest++ = (a + b) / 2; - } + return typename Traits::value_type((a + b) / 2); } -static void -pcm_convert_channels_16_n_to_2(int16_t *gcc_restrict dest, - unsigned src_channels, - const int16_t *gcc_restrict src, - const int16_t *gcc_restrict src_end) +template<SampleFormat F, class Traits=SampleTraits<F>> +static typename Traits::pointer_type +StereoToMono(typename Traits::pointer_type dest, + typename Traits::const_pointer_type src, + typename Traits::const_pointer_type 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; + while (src != end) { + const auto a = *src++; + const auto b = *src++; - /* XXX this is actually only mono ... */ - *dest++ = value; - *dest++ = value; + *dest++ = StereoToMono<F, Traits>(a, b); } -} - -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 nullptr; return dest; } -static void -pcm_convert_channels_24_2_to_1(int32_t *gcc_restrict dest, - const int32_t *gcc_restrict src, - const int32_t *gcc_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 *gcc_restrict dest, - unsigned src_channels, - const int32_t *gcc_restrict src, - const int32_t *gcc_restrict src_end) +template<SampleFormat F, class Traits=SampleTraits<F>> +static typename Traits::pointer_type +NToStereo(typename Traits::pointer_type dest, + unsigned src_channels, + typename Traits::const_pointer_type src, + typename Traits::const_pointer_type end) { - unsigned c; + assert((end - src) % src_channels == 0); - assert(src_channels > 0); - - while (src < src_end) { - int32_t sum = 0; - int32_t value; - - for (c = 0; c < src_channels; ++c) + while (src != end) { + typename Traits::sum_type sum = *src++; + for (unsigned c = 1; c < src_channels; ++c) sum += *src++; - value = sum / (int)src_channels; - /* XXX this is actually only mono ... */ + typename Traits::value_type value(sum / int(src_channels)); + + /* TODO: 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 nullptr; return dest; } -static void -pcm_convert_channels_32_2_to_1(int32_t *gcc_restrict dest, - const int32_t *gcc_restrict src, - const int32_t *gcc_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) +template<SampleFormat F, class Traits=SampleTraits<F>> +static typename Traits::pointer_type +NToM(typename Traits::pointer_type dest, + unsigned dest_channels, + unsigned src_channels, + typename Traits::const_pointer_type src, + typename Traits::const_pointer_type end) { - unsigned c; + assert((end - src) % src_channels == 0); - assert(src_channels > 0); - - while (src < src_end) { - int64_t sum = 0; - int32_t value; - - for (c = 0; c < src_channels; ++c) + while (src != end) { + typename Traits::sum_type sum = *src++; + for (unsigned c = 1; c < src_channels; ++c) sum += *src++; - value = sum / (int64_t)src_channels; - /* XXX this is actually only mono ... */ - *dest++ = value; - *dest++ = value; + typename Traits::value_type value(sum / int(src_channels)); + + /* TODO: this is actually only mono ... */ + for (unsigned c = 0; c < dest_channels; ++c) + *dest++ = value; } + + return dest; } -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) +template<SampleFormat F, class Traits=SampleTraits<F>> +static ConstBuffer<typename Traits::value_type> +ConvertChannels(PcmBuffer &buffer, + unsigned dest_channels, + unsigned src_channels, + ConstBuffer<typename Traits::value_type> src) { - assert(src_size % (sizeof(*src) * src_channels) == 0); - - size_t dest_size = src_size / src_channels * dest_channels; - *dest_size_r = dest_size; + assert(src.size % src_channels == 0); - int32_t *dest = (int32_t *)buffer.Get(dest_size); - const int32_t *src_end = (const int32_t *) - pcm_end_pointer(src, src_size); + const size_t dest_size = src.size / src_channels * dest_channels; + auto dest = buffer.GetT<typename Traits::value_type>(dest_size); if (src_channels == 1 && dest_channels == 2) - MonoToStereo(dest, src, src_end); + MonoToStereo(dest, src.begin(), src.end()); else if (src_channels == 2 && dest_channels == 1) - pcm_convert_channels_32_2_to_1(dest, src, src_end); + StereoToMono<F>(dest, src.begin(), src.end()); else if (dest_channels == 2) - pcm_convert_channels_32_n_to_2(dest, src_channels, src, - src_end); + NToStereo<F>(dest, src_channels, src.begin(), src.end()); else - return nullptr; + NToM<F>(dest, dest_channels, + src_channels, src.begin(), src.end()); - return dest; + return { dest, dest_size }; } -static void -pcm_convert_channels_float_2_to_1(float *gcc_restrict dest, - const float *gcc_restrict src, - const float *gcc_restrict src_end) +ConstBuffer<int16_t> +pcm_convert_channels_16(PcmBuffer &buffer, + unsigned dest_channels, + unsigned src_channels, + ConstBuffer<int16_t> src) { - while (src < src_end) { - double a = *src++, b = *src++; - - *dest++ = (a + b) / 2; - } + return ConvertChannels<SampleFormat::S16>(buffer, dest_channels, + src_channels, src); } -static void -pcm_convert_channels_float_n_to_2(float *dest, - unsigned src_channels, const float *src, - const float *src_end) +ConstBuffer<int32_t> +pcm_convert_channels_24(PcmBuffer &buffer, + unsigned dest_channels, + unsigned src_channels, + ConstBuffer<int32_t> src) { - 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; + return ConvertChannels<SampleFormat::S24_P32>(buffer, dest_channels, + src_channels, src); +} - /* XXX this is actually only mono ... */ - *dest++ = value; - *dest++ = value; - } +ConstBuffer<int32_t> +pcm_convert_channels_32(PcmBuffer &buffer, + unsigned dest_channels, + unsigned src_channels, + ConstBuffer<int32_t> src) +{ + return ConvertChannels<SampleFormat::S32>(buffer, dest_channels, + src_channels, src); } -const float * +ConstBuffer<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) + unsigned src_channels, + ConstBuffer<float> src) { - 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 nullptr; - - return dest; + return ConvertChannels<SampleFormat::FLOAT>(buffer, dest_channels, + src_channels, src); } diff --git a/src/pcm/PcmChannels.hxx b/src/pcm/PcmChannels.hxx index c67822825..6ad093c3b 100644 --- a/src/pcm/PcmChannels.hxx +++ b/src/pcm/PcmChannels.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,6 +24,7 @@ #include <stddef.h> class PcmBuffer; +template<typename T> struct ConstBuffer; /** * Changes the number of channels in 16 bit PCM data. @@ -32,15 +33,13 @@ class PcmBuffer; * @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 * +ConstBuffer<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); + unsigned src_channels, + ConstBuffer<int16_t> src); /** * Changes the number of channels in 24 bit PCM data (aligned at 32 @@ -50,15 +49,13 @@ pcm_convert_channels_16(PcmBuffer &buffer, * @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 * +ConstBuffer<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); + unsigned src_channels, + ConstBuffer<int32_t> src); /** * Changes the number of channels in 32 bit PCM data. @@ -67,15 +64,13 @@ pcm_convert_channels_24(PcmBuffer &buffer, * @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 * +ConstBuffer<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); + unsigned src_channels, + ConstBuffer<int32_t> src); /** * Changes the number of channels in 32 bit float PCM data. @@ -84,14 +79,12 @@ pcm_convert_channels_32(PcmBuffer &buffer, * @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 * +ConstBuffer<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); + unsigned src_channels, + ConstBuffer<float> src); #endif diff --git a/src/pcm/PcmConvert.cxx b/src/pcm/PcmConvert.cxx index 8eafe527c..ad79e60fc 100644 --- a/src/pcm/PcmConvert.cxx +++ b/src/pcm/PcmConvert.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,289 +19,151 @@ #include "config.h" #include "PcmConvert.hxx" -#include "PcmChannels.hxx" -#include "PcmFormat.hxx" +#include "Domain.hxx" +#include "ConfiguredResampler.hxx" #include "AudioFormat.hxx" +#include "util/ConstBuffer.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" +#include "util/ConstBuffer.hxx" #include <assert.h> #include <math.h> -const Domain pcm_convert_domain("pcm_convert"); +bool +pcm_convert_global_init(Error &error) +{ + return pcm_resampler_global_init(error); +} PcmConvert::PcmConvert() { +#ifndef NDEBUG + src_format.Clear(); + dest_format.Clear(); +#endif } PcmConvert::~PcmConvert() { + assert(!src_format.IsValid()); + assert(!dest_format.IsValid()); } -void -PcmConvert::Reset() +bool +PcmConvert::Open(AudioFormat _src_format, AudioFormat _dest_format, + Error &error) { - dsd.Reset(); - resampler.Reset(); -} + assert(!src_format.IsValid()); + assert(!dest_format.IsValid()); + assert(_src_format.IsValid()); + assert(_dest_format.IsValid()); -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; + src_format = _src_format; + dest_format = _dest_format; + + AudioFormat format = src_format; + if (format.format == SampleFormat::DSD) + format.format = SampleFormat::FLOAT; - assert(dest_format.format == SampleFormat::S16); + enable_resampler = format.sample_rate != dest_format.sample_rate; + if (enable_resampler) { + if (!resampler.Open(format, dest_format.sample_rate, error)) + return false; - buf = pcm_convert_to_16(format_buffer, dither, - src_format.format, - src_buffer, src_size, - &len); - if (buf == nullptr) { - error.Format(pcm_convert_domain, - "Conversion from %s to 16 bit is not implemented", - sample_format_to_string(src_format.format)); - return nullptr; + format.format = resampler.GetOutputSampleFormat(); + format.sample_rate = dest_format.sample_rate; } - 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 == nullptr) { - error.Format(pcm_convert_domain, - "Conversion from %u to %u channels " - "is not implemented", - src_format.channels, - dest_format.channels); - return nullptr; - } + enable_format = format.format != dest_format.format; + if (enable_format && + !format_converter.Open(format.format, dest_format.format, error)) { + if (enable_resampler) + resampler.Close(); + return false; } - 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 == nullptr) - return nullptr; + format.format = dest_format.format; + + enable_channels = format.channels != dest_format.channels; + if (enable_channels && + !channels_converter.Open(format.format, format.channels, + dest_format.channels, error)) { + if (enable_format) + format_converter.Close(); + if (enable_resampler) + resampler.Close(); + return false; } - *dest_size_r = len; - return buf; + return true; } -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) +void +PcmConvert::Close() { - 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 == nullptr) { - error.Format(pcm_convert_domain, - "Conversion from %s to 24 bit is not implemented", - sample_format_to_string(src_format.format)); - return nullptr; - } + if (enable_channels) + channels_converter.Close(); + if (enable_format) + format_converter.Close(); + if (enable_resampler) + resampler.Close(); - 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 == nullptr) { - error.Format(pcm_convert_domain, - "Conversion from %u to %u channels " - "is not implemented", - src_format.channels, - dest_format.channels); - return nullptr; - } - } - - 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 == nullptr) - return nullptr; - } + dsd.Reset(); - *dest_size_r = len; - return buf; +#ifndef NDEBUG + src_format.Clear(); + dest_format.Clear(); +#endif } -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 void * +PcmConvert::Convert(const void *src, size_t src_size, + 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 == nullptr) { - error.Format(pcm_convert_domain, - "Conversion from %s to 32 bit is not implemented", - sample_format_to_string(src_format.format)); - return nullptr; - } - - 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 == nullptr) { - error.Format(pcm_convert_domain, - "Conversion from %u to %u channels " - "is not implemented", - src_format.channels, - dest_format.channels); + ConstBuffer<void> buffer(src, src_size); + AudioFormat format = src_format; + + if (format.format == SampleFormat::DSD) { + auto s = ConstBuffer<uint8_t>::FromVoid(buffer); + auto d = dsd.ToFloat(format.channels, + false, s); + if (d.IsNull()) { + error.Set(pcm_domain, + "DSD to PCM conversion failed"); return nullptr; } - } - 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 == nullptr) - return buf; + buffer = d.ToVoid(); + format.format = SampleFormat::FLOAT; } - *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 */ + if (enable_resampler) { + buffer = resampler.Resample(buffer, error); + if (buffer.IsNull()) + return nullptr; - buffer = pcm_convert_to_float(format_buffer, - src_format.format, - buffer, size, &size); - if (buffer == nullptr) { - error.Format(pcm_convert_domain, - "Conversion from %s to float is not implemented", - sample_format_to_string(src_format.format)); - return nullptr; + format.format = resampler.GetOutputSampleFormat(); + format.sample_rate = dest_format.sample_rate; } - /* 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 == nullptr) { - error.Format(pcm_convert_domain, - "Conversion from %u to %u channels " - "is not implemented", - src_format.channels, - dest_format.channels); + if (enable_format) { + buffer = format_converter.Convert(buffer, error); + if (buffer.IsNull()) return nullptr; - } - } - /* 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 == nullptr) - return nullptr; + format.format = dest_format.format; } - *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 == nullptr) { - error.Set(pcm_convert_domain, - "DSD to PCM conversion failed"); + if (enable_channels) { + buffer = channels_converter.Convert(buffer, error); + if (buffer.IsNull()) return nullptr; - } - float_format = src_format; - float_format.format = SampleFormat::FLOAT; - - src_format = float_format; - src = f; - src_size = f_size; + format.channels = dest_format.channels; } - 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 nullptr; - } + *dest_size_r = buffer.size; + return buffer.data; } diff --git a/src/pcm/PcmConvert.hxx b/src/pcm/PcmConvert.hxx index 40f785179..7d05b3bb2 100644 --- a/src/pcm/PcmConvert.hxx +++ b/src/pcm/PcmConvert.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,15 +20,18 @@ #ifndef PCM_CONVERT_HXX #define PCM_CONVERT_HXX -#include "PcmDither.hxx" #include "PcmDsd.hxx" -#include "PcmResample.hxx" #include "PcmBuffer.hxx" +#include "FormatConverter.hxx" +#include "ChannelsConverter.hxx" +#include "GlueResampler.hxx" +#include "AudioFormat.hxx" #include <stddef.h> -struct AudioFormat; +template<typename T> struct ConstBuffer; class Error; +class Domain; /** * This object is statically allocated (within another struct), and @@ -38,27 +41,29 @@ class Error; class PcmConvert { PcmDsd dsd; - PcmResampler resampler; + GluePcmResampler resampler; + PcmFormatConverter format_converter; + PcmChannelsConverter channels_converter; - PcmDither dither; + AudioFormat src_format, dest_format; - /** the buffer for converting the sample format */ - PcmBuffer format_buffer; - - /** the buffer for converting the channel count */ - PcmBuffer channels_buffer; + bool enable_resampler, enable_format, enable_channels; public: PcmConvert(); ~PcmConvert(); + /** + * Prepare the object. Call Close() when done. + */ + bool Open(AudioFormat _src_format, AudioFormat _dest_format, + Error &error); /** - * Reset the pcm_convert_state object. Use this at the - * boundary between two distinct songs and each time the - * format changes. + * Close the object after it was prepared with Open(). After + * that, it may be reused by calling Open() again. */ - void Reset(); + void Close(); /** * Converts PCM data between two audio formats. @@ -72,38 +77,12 @@ public: * 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, + const void *Convert(const void *src, size_t src_size, 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; +bool +pcm_convert_global_init(Error &error); #endif diff --git a/src/pcm/PcmDither.cxx b/src/pcm/PcmDither.cxx index 98d0d443e..7b2a9e900 100644 --- a/src/pcm/PcmDither.cxx +++ b/src/pcm/PcmDither.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,18 +20,14 @@ #include "config.h" #include "PcmDither.hxx" #include "PcmPrng.hxx" +#include "Traits.hxx" -inline int16_t -PcmDither::Dither24To16(int_fast32_t sample) +template<typename T, T MIN, T MAX, unsigned scale_bits> +inline T +PcmDither::Dither(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; + constexpr T round = 1 << (scale_bits - 1); + constexpr T mask = (1 << scale_bits) - 1; sample += error[0] - error[1] + error[2]; @@ -39,9 +35,9 @@ PcmDither::Dither24To16(int_fast32_t sample) error[1] = error[0] / 2; /* round */ - int_fast32_t output = sample + round; + T output = sample + round; - int_fast32_t rnd = pcm_prng(random); + const T rnd = pcm_prng(random); output += (rnd & mask) - (random & mask); random = rnd; @@ -63,27 +59,59 @@ PcmDither::Dither24To16(int_fast32_t sample) error[0] = sample - output; - return (int16_t)(output >> scale_bits); + return output >> scale_bits; } -void -PcmDither::Dither24To16(int16_t *dest, const int32_t *src, - const int32_t *src_end) +template<typename ST, unsigned SBITS, unsigned DBITS> +inline ST +PcmDither::DitherShift(ST sample) +{ + static_assert(sizeof(ST) * 8 > SBITS, "Source type too small"); + static_assert(SBITS > DBITS, "Non-positive scale_bits"); + + static constexpr ST MIN = -(ST(1) << (SBITS - 1)); + static constexpr ST MAX = (ST(1) << (SBITS - 1)) - 1; + + return Dither<ST, MIN, MAX, SBITS - DBITS>(sample); +} + +template<typename ST, typename DT> +inline typename DT::value_type +PcmDither::DitherConvert(typename ST::value_type sample) +{ + static_assert(ST::BITS > DT::BITS, + "Sample formats cannot be dithered"); + + constexpr unsigned scale_bits = ST::BITS - DT::BITS; + + return Dither<typename ST::sum_type, ST::MIN, ST::MAX, + scale_bits>(sample); +} + +template<typename ST, typename DT> +inline void +PcmDither::DitherConvert(typename DT::pointer_type dest, + typename ST::const_pointer_type src, + typename ST::const_pointer_type src_end) { while (src < src_end) - *dest++ = Dither24To16(*src++); + *dest++ = DitherConvert<ST, DT>(*src++); } -inline int16_t -PcmDither::Dither32To16(int_fast32_t sample) +inline void +PcmDither::Dither24To16(int16_t *dest, const int32_t *src, + const int32_t *src_end) { - return Dither24To16(sample >> 8); + typedef SampleTraits<SampleFormat::S24_P32> ST; + typedef SampleTraits<SampleFormat::S16> DT; + DitherConvert<ST, DT>(dest, src, src_end); } -void +inline void PcmDither::Dither32To16(int16_t *dest, const int32_t *src, const int32_t *src_end) { - while (src < src_end) - *dest++ = Dither32To16(*src++); + typedef SampleTraits<SampleFormat::S32> ST; + typedef SampleTraits<SampleFormat::S16> DT; + DitherConvert<ST, DT>(dest, src, src_end); } diff --git a/src/pcm/PcmDither.hxx b/src/pcm/PcmDither.hxx index 106382307..54b0f7315 100644 --- a/src/pcm/PcmDither.hxx +++ b/src/pcm/PcmDither.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,6 +22,8 @@ #include <stdint.h> +enum class SampleFormat : uint8_t; + class PcmDither { int32_t error[3]; int32_t random; @@ -30,6 +32,18 @@ public: constexpr PcmDither() :error{0, 0, 0}, random(0) {} + /** + * Shift the given sample by #SBITS-#DBITS to the right, and + * apply dithering. + * + * @param ST the input sample type + * @param SBITS the input bit width + * @param DBITS the output bit width + * @param sample the input sample value + */ + template<typename ST, unsigned SBITS, unsigned DBITS> + ST DitherShift(ST sample); + void Dither24To16(int16_t *dest, const int32_t *src, const int32_t *src_end); @@ -37,8 +51,34 @@ public: const int32_t *src_end); private: - int16_t Dither24To16(int_fast32_t sample); - int16_t Dither32To16(int_fast32_t sample); + /** + * Shift the given sample by #scale_bits to the right, and + * apply dithering. + * + * @param T the input sample type + * @param MIN the minimum input sample value + * @param MAX the maximum input sample value + * @param scale_bits the number of bits to be discarded + * @param sample the input sample value + */ + template<typename T, T MIN, T MAX, unsigned scale_bits> + T Dither(T sample); + + /** + * Convert the given sample from one sample format to another, + * discarding bits. + * + * @param ST the input #SampleTraits class + * @param ST the output #SampleTraits class + * @param sample the input sample value + */ + template<typename ST, typename DT> + typename DT::value_type DitherConvert(typename ST::value_type sample); + + template<typename ST, typename DT> + void DitherConvert(typename DT::pointer_type dest, + typename ST::const_pointer_type src, + typename ST::const_pointer_type src_end); }; #endif diff --git a/src/pcm/PcmDsd.cxx b/src/pcm/PcmDsd.cxx index 4db274635..ee549658d 100644 --- a/src/pcm/PcmDsd.cxx +++ b/src/pcm/PcmDsd.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,11 +21,11 @@ #include "PcmDsd.hxx" #include "dsd2pcm/dsd2pcm.h" #include "util/Macros.hxx" +#include "util/ConstBuffer.hxx" #include <algorithm> #include <assert.h> -#include <string.h> PcmDsd::PcmDsd() { @@ -47,22 +47,20 @@ PcmDsd::Reset() dsd2pcm_reset(dsd2pcm[i]); } -const float * +ConstBuffer<float> PcmDsd::ToFloat(unsigned channels, bool lsbfirst, - const uint8_t *src, size_t src_size, - size_t *dest_size_r) + ConstBuffer<uint8_t> src) { - assert(src != nullptr); - assert(src_size > 0); - assert(src_size % channels == 0); + assert(!src.IsNull()); + assert(!src.IsEmpty()); + assert(src.size % channels == 0); assert(channels <= ARRAY_SIZE(dsd2pcm)); - const unsigned num_samples = src_size; - const unsigned num_frames = src_size / channels; + 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) { @@ -73,9 +71,9 @@ PcmDsd::ToFloat(unsigned channels, bool lsbfirst, } dsd2pcm_translate(dsd2pcm[c], num_frames, - src + c, channels, + src.data + c, channels, lsbfirst, dest + c, channels); } - return dest; + return { dest, num_samples }; } diff --git a/src/pcm/PcmDsd.hxx b/src/pcm/PcmDsd.hxx index 26ee11b13..cb3ef1fd6 100644 --- a/src/pcm/PcmDsd.hxx +++ b/src/pcm/PcmDsd.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,22 +25,24 @@ #include <stdint.h> +template<typename T> struct ConstBuffer; + /** * Wrapper for the dsd2pcm library. */ -struct PcmDsd { +class PcmDsd { PcmBuffer buffer; struct dsd2pcm_ctx_s *dsd2pcm[32]; +public: PcmDsd(); ~PcmDsd(); void Reset(); - const float *ToFloat(unsigned channels, bool lsbfirst, - const uint8_t *src, size_t src_size, - size_t *dest_size_r); + ConstBuffer<float> ToFloat(unsigned channels, bool lsbfirst, + ConstBuffer<uint8_t> src); }; #endif diff --git a/src/pcm/PcmDsdUsb.cxx b/src/pcm/PcmDsdUsb.cxx index 2d0f33a15..1bfb4206f 100644 --- a/src/pcm/PcmDsdUsb.cxx +++ b/src/pcm/PcmDsdUsb.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,6 +22,8 @@ #include "PcmBuffer.hxx" #include "AudioFormat.hxx" +#include <assert.h> + constexpr static inline uint32_t pcm_two_dsd_to_usb_marker1(uint8_t a, uint8_t b) diff --git a/src/pcm/PcmDsdUsb.hxx b/src/pcm/PcmDsdUsb.hxx index 3b7121465..bb31d2d45 100644 --- a/src/pcm/PcmDsdUsb.hxx +++ b/src/pcm/PcmDsdUsb.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm/PcmExport.cxx b/src/pcm/PcmExport.cxx index f6ce1e661..fc3ef57ca 100644 --- a/src/pcm/PcmExport.cxx +++ b/src/pcm/PcmExport.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm/PcmExport.hxx b/src/pcm/PcmExport.hxx index bd18c0534..61ac14c6f 100644 --- a/src/pcm/PcmExport.hxx +++ b/src/pcm/PcmExport.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm/PcmFormat.cxx b/src/pcm/PcmFormat.cxx index 4565c71c6..248443c81 100644 --- a/src/pcm/PcmFormat.cxx +++ b/src/pcm/PcmFormat.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,61 +19,26 @@ #include "config.h" #include "PcmFormat.hxx" -#include "PcmDither.hxx" #include "PcmBuffer.hxx" #include "PcmUtils.hxx" +#include "Traits.hxx" +#include "util/ConstBuffer.hxx" +#include "util/WritableBuffer.hxx" -#include <type_traits> +#include "PcmDither.cxx" // including the .cxx file to get inlined templates -template<SampleFormat F> -struct SampleTraits {}; - -template<> -struct SampleTraits<SampleFormat::S8> { - typedef int8_t value_type; - typedef value_type *pointer_type; - typedef const value_type *const_pointer_type; - - static constexpr size_t SAMPLE_SIZE = sizeof(value_type); - static constexpr unsigned BITS = sizeof(value_type) * 8; -}; - -template<> -struct SampleTraits<SampleFormat::S16> { - typedef int16_t value_type; - typedef value_type *pointer_type; - typedef const value_type *const_pointer_type; - - static constexpr size_t SAMPLE_SIZE = sizeof(value_type); - static constexpr unsigned BITS = sizeof(value_type) * 8; -}; - -template<> -struct SampleTraits<SampleFormat::S32> { - typedef int32_t value_type; - typedef value_type *pointer_type; - typedef const value_type *const_pointer_type; - - static constexpr size_t SAMPLE_SIZE = sizeof(value_type); - static constexpr unsigned BITS = sizeof(value_type) * 8; -}; - -template<> -struct SampleTraits<SampleFormat::S24_P32> { - typedef int32_t value_type; - typedef value_type *pointer_type; - typedef const value_type *const_pointer_type; - - static constexpr size_t SAMPLE_SIZE = sizeof(value_type); - static constexpr unsigned BITS = 24; -}; +template<typename T> +static inline ConstBuffer<T> +ToConst(WritableBuffer<T> b) +{ + return { b.data, b.size }; +} static void -pcm_convert_8_to_16(int16_t *out, const int8_t *in, const int8_t *in_end) +pcm_convert_8_to_16(int16_t *out, const int8_t *in, size_t n) { - while (in < in_end) { - *out++ = *in++ << 8; - } + for (size_t i = 0; i != n; ++i) + out[i] = in[i] << 8; } static void @@ -93,28 +58,19 @@ pcm_convert_32_to_16(PcmDither &dither, template<SampleFormat F, class Traits=SampleTraits<F>> static void ConvertFromFloat(typename Traits::pointer_type dest, - const float *src, const float *end) + const float *src, size_t n) { constexpr auto bits = Traits::BITS; const float factor = 1 << (bits - 1); - while (src != end) { - int sample(*src++ * factor); - *dest++ = PcmClamp<typename Traits::value_type, int, bits>(sample); + for (size_t i = 0; i != n; ++i) { + typename Traits::long_type sample(src[i] * factor); + dest[i] = PcmClamp<F, Traits>(sample); } } template<SampleFormat F, class Traits=SampleTraits<F>> -static void -ConvertFromFloat(typename Traits::pointer_type dest, - const float *src, size_t size) -{ - ConvertFromFloat<F, Traits>(dest, src, - pcm_end_pointer(src, size)); -} - -template<SampleFormat F, class Traits=SampleTraits<F>> static typename Traits::pointer_type AllocateFromFloat(PcmBuffer &buffer, const float *src, size_t src_size, size_t *dest_size_r) @@ -125,65 +81,55 @@ AllocateFromFloat(PcmBuffer &buffer, const float *src, size_t src_size, const size_t num_samples = src_size / src_sample_size; *dest_size_r = num_samples * sizeof(typename Traits::value_type); auto dest = (typename Traits::pointer_type)buffer.Get(*dest_size_r); - ConvertFromFloat<F, Traits>(dest, src, src_size); + ConvertFromFloat<F, Traits>(dest, src, src_size / sizeof(*src)); 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) +template<SampleFormat F, class Traits=SampleTraits<F>> +static WritableBuffer<typename Traits::value_type> +AllocateFromFloat(PcmBuffer &buffer, ConstBuffer<float> src) { - 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; + auto dest = buffer.GetT<typename Traits::value_type>(src.size); + ConvertFromFloat<F, Traits>(dest, src.data, src.size); + return { dest, src.size }; } -static int16_t * +static ConstBuffer<int16_t> +pcm_allocate_8_to_16(PcmBuffer &buffer, ConstBuffer<int8_t> src) +{ + auto dest = buffer.GetT<int16_t>(src.size); + pcm_convert_8_to_16(dest, src.data, src.size); + return { dest, src.size }; +} + +static ConstBuffer<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; + ConstBuffer<int32_t> src) +{ + auto dest = buffer.GetT<int16_t>(src.size); + pcm_convert_24_to_16(dither, dest, src.data, src.end()); + return { dest, src.size }; } -static int16_t * +static ConstBuffer<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; + ConstBuffer<int32_t> src) +{ + auto dest = buffer.GetT<int16_t>(src.size); + pcm_convert_32_to_16(dither, dest, src.data, src.end()); + return { dest, src.size }; } -static int16_t * -pcm_allocate_float_to_16(PcmBuffer &buffer, - const float *src, size_t src_size, - size_t *dest_size_r) +static ConstBuffer<int16_t> +pcm_allocate_float_to_16(PcmBuffer &buffer, ConstBuffer<float> src) { - return AllocateFromFloat<SampleFormat::S16>(buffer, src, src_size, - dest_size_r); + return ToConst(AllocateFromFloat<SampleFormat::S16>(buffer, src)); } -const int16_t * +ConstBuffer<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) + SampleFormat src_format, ConstBuffer<void> src) { - assert(src_size % sample_format_size(src_format) == 0); - switch (src_format) { case SampleFormat::UNDEFINED: case SampleFormat::DSD: @@ -191,104 +137,84 @@ pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither, case SampleFormat::S8: return pcm_allocate_8_to_16(buffer, - (const int8_t *)src, src_size, - dest_size_r); + ConstBuffer<int8_t>::FromVoid(src)); case SampleFormat::S16: - *dest_size_r = src_size; - return (const int16_t *)src; + return ConstBuffer<int16_t>::FromVoid(src); case SampleFormat::S24_P32: return pcm_allocate_24p32_to_16(buffer, dither, - (const int32_t *)src, src_size, - dest_size_r); + ConstBuffer<int32_t>::FromVoid(src)); case SampleFormat::S32: return pcm_allocate_32_to_16(buffer, dither, - (const int32_t *)src, src_size, - dest_size_r); + ConstBuffer<int32_t>::FromVoid(src)); case SampleFormat::FLOAT: return pcm_allocate_float_to_16(buffer, - (const float *)src, src_size, - dest_size_r); + ConstBuffer<float>::FromVoid(src)); } return nullptr; } static void -pcm_convert_8_to_24(int32_t *out, const int8_t *in, const int8_t *in_end) +pcm_convert_8_to_24(int32_t *out, const int8_t *in, size_t n) { - while (in < in_end) - *out++ = *in++ << 16; + for (size_t i = 0; i != n; ++i) + out[i] = in[i] << 16; } static void -pcm_convert_16_to_24(int32_t *out, const int16_t *in, const int16_t *in_end) +pcm_convert_16_to_24(int32_t *out, const int16_t *in, size_t n) { - while (in < in_end) - *out++ = *in++ << 8; + for (size_t i = 0; i != n; ++i) + out[i] = in[i] << 8; } static void pcm_convert_32_to_24(int32_t *gcc_restrict out, const int32_t *gcc_restrict in, - const int32_t *gcc_restrict in_end) + size_t n) { - while (in < in_end) - *out++ = *in++ >> 8; + for (size_t i = 0; i != n; ++i) + out[i] = in[i] >> 8; } -static int32_t * -pcm_allocate_8_to_24(PcmBuffer &buffer, - const int8_t *src, size_t src_size, size_t *dest_size_r) +static ConstBuffer<int32_t> +pcm_allocate_8_to_24(PcmBuffer &buffer, ConstBuffer<int8_t> src) { - 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; + auto dest = buffer.GetT<int32_t>(src.size); + pcm_convert_8_to_24(dest, src.data, src.size); + return { dest, src.size }; } -static int32_t * -pcm_allocate_16_to_24(PcmBuffer &buffer, - const int16_t *src, size_t src_size, size_t *dest_size_r) +static ConstBuffer<int32_t> +pcm_allocate_16_to_24(PcmBuffer &buffer, ConstBuffer<int16_t> src) { - 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; + auto dest = buffer.GetT<int32_t>(src.size); + pcm_convert_16_to_24(dest, src.data, src.size); + return { dest, src.size }; } -static int32_t * -pcm_allocate_32_to_24(PcmBuffer &buffer, - const int32_t *src, size_t src_size, size_t *dest_size_r) +static ConstBuffer<int32_t> +pcm_allocate_32_to_24(PcmBuffer &buffer, ConstBuffer<int32_t> src) { - *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; + auto dest = buffer.GetT<int32_t>(src.size); + pcm_convert_32_to_24(dest, src.data, src.size); + return { dest, src.size }; } -static int32_t * -pcm_allocate_float_to_24(PcmBuffer &buffer, - const float *src, size_t src_size, - size_t *dest_size_r) +static WritableBuffer<int32_t> +pcm_allocate_float_to_24(PcmBuffer &buffer, ConstBuffer<float> src) { - return AllocateFromFloat<SampleFormat::S24_P32>(buffer, src, src_size, - dest_size_r); + return AllocateFromFloat<SampleFormat::S24_P32>(buffer, src); } -const int32_t * +ConstBuffer<int32_t> pcm_convert_to_24(PcmBuffer &buffer, - SampleFormat src_format, const void *src, - size_t src_size, size_t *dest_size_r) + SampleFormat src_format, ConstBuffer<void> src) { - assert(src_size % sample_format_size(src_format) == 0); - switch (src_format) { case SampleFormat::UNDEFINED: case SampleFormat::DSD: @@ -296,110 +222,89 @@ pcm_convert_to_24(PcmBuffer &buffer, case SampleFormat::S8: return pcm_allocate_8_to_24(buffer, - (const int8_t *)src, src_size, - dest_size_r); + ConstBuffer<int8_t>::FromVoid(src)); case SampleFormat::S16: return pcm_allocate_16_to_24(buffer, - (const int16_t *)src, src_size, - dest_size_r); + ConstBuffer<int16_t>::FromVoid(src)); case SampleFormat::S24_P32: - *dest_size_r = src_size; - return (const int32_t *)src; + return ConstBuffer<int32_t>::FromVoid(src); case SampleFormat::S32: return pcm_allocate_32_to_24(buffer, - (const int32_t *)src, src_size, - dest_size_r); + ConstBuffer<int32_t>::FromVoid(src)); case SampleFormat::FLOAT: - return pcm_allocate_float_to_24(buffer, - (const float *)src, src_size, - dest_size_r); + return ToConst(pcm_allocate_float_to_24(buffer, + ConstBuffer<float>::FromVoid(src))); } return nullptr; } static void -pcm_convert_8_to_32(int32_t *out, const int8_t *in, const int8_t *in_end) +pcm_convert_8_to_32(int32_t *out, const int8_t *in, size_t n) { - while (in < in_end) - *out++ = *in++ << 24; + for (size_t i = 0; i != n; ++i) + out[i] = in[i] << 24; } static void -pcm_convert_16_to_32(int32_t *out, const int16_t *in, const int16_t *in_end) +pcm_convert_16_to_32(int32_t *out, const int16_t *in, size_t n) { - while (in < in_end) - *out++ = *in++ << 16; + for (size_t i = 0; i != n; ++i) + out[i] = in[i] << 16; } static void pcm_convert_24_to_32(int32_t *gcc_restrict out, const int32_t *gcc_restrict in, - const int32_t *gcc_restrict in_end) + size_t n) { - while (in < in_end) - *out++ = *in++ << 8; + for (size_t i = 0; i != n; ++i) + out[i] = in[i] << 8; } -static int32_t * -pcm_allocate_8_to_32(PcmBuffer &buffer, - const int8_t *src, size_t src_size, size_t *dest_size_r) +static ConstBuffer<int32_t> +pcm_allocate_8_to_32(PcmBuffer &buffer, ConstBuffer<int8_t> src) { - 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; + auto dest = buffer.GetT<int32_t>(src.size); + pcm_convert_8_to_32(dest, src.data, src.size); + return { dest, src.size }; } -static int32_t * -pcm_allocate_16_to_32(PcmBuffer &buffer, - const int16_t *src, size_t src_size, size_t *dest_size_r) +static ConstBuffer<int32_t> +pcm_allocate_16_to_32(PcmBuffer &buffer, ConstBuffer<int16_t> src) { - 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; + auto dest = buffer.GetT<int32_t>(src.size); + pcm_convert_16_to_32(dest, src.data, src.size); + return { dest, src.size }; } -static int32_t * -pcm_allocate_24p32_to_32(PcmBuffer &buffer, - const int32_t *src, size_t src_size, - size_t *dest_size_r) +static ConstBuffer<int32_t> +pcm_allocate_24p32_to_32(PcmBuffer &buffer, ConstBuffer<int32_t> src) { - *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; + auto dest = buffer.GetT<int32_t>(src.size); + pcm_convert_24_to_32(dest, src.data, src.size); + return { dest, src.size }; } -static int32_t * -pcm_allocate_float_to_32(PcmBuffer &buffer, - const float *src, size_t src_size, - size_t *dest_size_r) +static ConstBuffer<int32_t> +pcm_allocate_float_to_32(PcmBuffer &buffer, ConstBuffer<float> src) { /* convert to S24_P32 first */ - int32_t *dest = pcm_allocate_float_to_24(buffer, src, src_size, - dest_size_r); + auto dest = pcm_allocate_float_to_24(buffer, src); /* convert to 32 bit in-place */ - pcm_convert_24_to_32(dest, dest, pcm_end_pointer(dest, *dest_size_r)); - return dest; + pcm_convert_24_to_32(dest.data, dest.data, src.size); + return ToConst(dest); } -const int32_t * +ConstBuffer<int32_t> pcm_convert_to_32(PcmBuffer &buffer, - SampleFormat src_format, const void *src, - size_t src_size, size_t *dest_size_r) + SampleFormat src_format, ConstBuffer<void> src) { - assert(src_size % sample_format_size(src_format) == 0); - switch (src_format) { case SampleFormat::UNDEFINED: case SampleFormat::DSD: @@ -407,27 +312,22 @@ pcm_convert_to_32(PcmBuffer &buffer, case SampleFormat::S8: return pcm_allocate_8_to_32(buffer, - (const int8_t *)src, src_size, - dest_size_r); + ConstBuffer<int8_t>::FromVoid(src)); case SampleFormat::S16: return pcm_allocate_16_to_32(buffer, - (const int16_t *)src, src_size, - dest_size_r); + ConstBuffer<int16_t>::FromVoid(src)); case SampleFormat::S24_P32: return pcm_allocate_24p32_to_32(buffer, - (const int32_t *)src, src_size, - dest_size_r); + ConstBuffer<int32_t>::FromVoid(src)); case SampleFormat::S32: - *dest_size_r = src_size; - return (const int32_t *)src; + return ConstBuffer<int32_t>::FromVoid(src); case SampleFormat::FLOAT: return pcm_allocate_float_to_32(buffer, - (const float *)src, src_size, - dest_size_r); + ConstBuffer<float>::FromVoid(src)); } return nullptr; @@ -437,78 +337,50 @@ template<SampleFormat F, class Traits=SampleTraits<F>> static void ConvertToFloat(float *dest, typename Traits::const_pointer_type src, - typename Traits::const_pointer_type end) + size_t n) { constexpr float factor = 0.5 / (1 << (Traits::BITS - 2)); - while (src != end) - *dest++ = float(*src++) * factor; - -} - -template<SampleFormat F, class Traits=SampleTraits<F>> -static void -ConvertToFloat(float *dest, - typename Traits::const_pointer_type src, size_t size) -{ - ConvertToFloat<F, Traits>(dest, src, pcm_end_pointer(src, size)); + for (size_t i = 0; i != n; ++i) + dest[i] = float(src[i]) * factor; } template<SampleFormat F, class Traits=SampleTraits<F>> -static float * +static ConstBuffer<float> AllocateToFloat(PcmBuffer &buffer, - typename Traits::const_pointer_type src, size_t src_size, - size_t *dest_size_r) + ConstBuffer<typename Traits::value_type> src) { - constexpr size_t src_sample_size = Traits::SAMPLE_SIZE; - 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<F, Traits>(dest, src, src_size); - return dest; + float *dest = buffer.GetT<float>(src.size); + ConvertToFloat<F, Traits>(dest, src.data, src.size); + return { dest, src.size }; } -static float * -pcm_allocate_8_to_float(PcmBuffer &buffer, - const int8_t *src, size_t src_size, - size_t *dest_size_r) +static ConstBuffer<float> +pcm_allocate_8_to_float(PcmBuffer &buffer, ConstBuffer<int8_t> src) { - return AllocateToFloat<SampleFormat::S8>(buffer, src, src_size, - dest_size_r); + return AllocateToFloat<SampleFormat::S8>(buffer, src); } -static float * -pcm_allocate_16_to_float(PcmBuffer &buffer, - const int16_t *src, size_t src_size, - size_t *dest_size_r) +static ConstBuffer<float> +pcm_allocate_16_to_float(PcmBuffer &buffer, ConstBuffer<int16_t> src) { - return AllocateToFloat<SampleFormat::S16>(buffer, src, src_size, - dest_size_r); + return AllocateToFloat<SampleFormat::S16>(buffer, src); } -static float * -pcm_allocate_24p32_to_float(PcmBuffer &buffer, - const int32_t *src, size_t src_size, - size_t *dest_size_r) +static ConstBuffer<float> +pcm_allocate_24p32_to_float(PcmBuffer &buffer, ConstBuffer<int32_t> src) { - return AllocateToFloat<SampleFormat::S24_P32>(buffer, src, src_size, - dest_size_r); + return AllocateToFloat<SampleFormat::S24_P32>(buffer, src); } -static float * -pcm_allocate_32_to_float(PcmBuffer &buffer, - const int32_t *src, size_t src_size, - size_t *dest_size_r) +static ConstBuffer<float> +pcm_allocate_32_to_float(PcmBuffer &buffer, ConstBuffer<int32_t> src) { - return AllocateToFloat<SampleFormat::S32>(buffer, src, src_size, - dest_size_r); + return AllocateToFloat<SampleFormat::S32>(buffer, src); } -const float * +ConstBuffer<float> pcm_convert_to_float(PcmBuffer &buffer, - SampleFormat src_format, const void *src, - size_t src_size, size_t *dest_size_r) + SampleFormat src_format, ConstBuffer<void> src) { switch (src_format) { case SampleFormat::UNDEFINED: @@ -517,27 +389,22 @@ pcm_convert_to_float(PcmBuffer &buffer, case SampleFormat::S8: return pcm_allocate_8_to_float(buffer, - (const int8_t *)src, src_size, - dest_size_r); + ConstBuffer<int8_t>::FromVoid(src)); 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); + ConstBuffer<int16_t>::FromVoid(src)); case SampleFormat::S32: return pcm_allocate_32_to_float(buffer, - (const int32_t *)src, src_size, - dest_size_r); + ConstBuffer<int32_t>::FromVoid(src)); + + case SampleFormat::S24_P32: + return pcm_allocate_24p32_to_float(buffer, + ConstBuffer<int32_t>::FromVoid(src)); case SampleFormat::FLOAT: - *dest_size_r = src_size; - return (const float *)src; + return ConstBuffer<float>::FromVoid(src); } return nullptr; diff --git a/src/pcm/PcmFormat.hxx b/src/pcm/PcmFormat.hxx index cc44d6dd5..da182e771 100644 --- a/src/pcm/PcmFormat.hxx +++ b/src/pcm/PcmFormat.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,6 +25,7 @@ #include <stdint.h> #include <stddef.h> +template<typename T> struct ConstBuffer; class PcmBuffer; class PcmDither; @@ -36,14 +37,12 @@ class PcmDither; * @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 * +gcc_pure +ConstBuffer<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); + SampleFormat src_format, ConstBuffer<void> src); /** * Converts PCM samples to 24 bit (32 bit alignment). @@ -51,14 +50,12 @@ pcm_convert_to_16(PcmBuffer &buffer, PcmDither &dither, * @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 * +gcc_pure +ConstBuffer<int32_t> pcm_convert_to_24(PcmBuffer &buffer, - SampleFormat src_format, const void *src, - size_t src_size, size_t *dest_size_r); + SampleFormat src_format, ConstBuffer<void> src); /** * Converts PCM samples to 32 bit. @@ -66,14 +63,12 @@ pcm_convert_to_24(PcmBuffer &buffer, * @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 * +gcc_pure +ConstBuffer<int32_t> pcm_convert_to_32(PcmBuffer &buffer, - SampleFormat src_format, const void *src, - size_t src_size, size_t *dest_size_r); + SampleFormat src_format, ConstBuffer<void> src); /** * Converts PCM samples to 32 bit floating point. @@ -85,9 +80,9 @@ pcm_convert_to_32(PcmBuffer &buffer, * @param dest_size_r returns the number of bytes of the destination buffer * @return the destination buffer */ -const float * +gcc_pure +ConstBuffer<float> pcm_convert_to_float(PcmBuffer &buffer, - SampleFormat src_format, const void *src, - size_t src_size, size_t *dest_size_r); + SampleFormat src_format, ConstBuffer<void> src); #endif diff --git a/src/pcm/PcmMix.cxx b/src/pcm/PcmMix.cxx index 001794061..d21b5f04b 100644 --- a/src/pcm/PcmMix.cxx +++ b/src/pcm/PcmMix.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,42 +19,56 @@ #include "config.h" #include "PcmMix.hxx" -#include "PcmVolume.hxx" +#include "Volume.hxx" #include "PcmUtils.hxx" #include "AudioFormat.hxx" +#include "Traits.hxx" +#include "util/Clamp.hxx" +#include "PcmDither.cxx" // including the .cxx file to get inlined templates + +#include <assert.h> #include <math.h> -template<typename T, typename U, unsigned bits> -static T -PcmAddVolume(T _a, T _b, int volume1, int volume2) +template<SampleFormat F, class Traits=SampleTraits<F>> +static typename Traits::value_type +PcmAddVolume(PcmDither &dither, + typename Traits::value_type _a, typename Traits::value_type _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; + typename Traits::long_type a(_a), b(_b); + typename Traits::long_type c(a * volume1 + b * volume2); - return PcmClamp<T, U, bits>(c); + return dither.DitherShift<typename Traits::long_type, + Traits::BITS + PCM_VOLUME_BITS, + Traits::BITS>(c); } -template<typename T, typename U, unsigned bits> +template<SampleFormat F, class Traits=SampleTraits<F>> static void -PcmAddVolume(T *a, const T *b, unsigned n, int volume1, int volume2) +PcmAddVolume(PcmDither &dither, + typename Traits::pointer_type a, + typename Traits::const_pointer_type b, + size_t 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); + a[i] = PcmAddVolume<F, Traits>(dither, a[i], b[i], + volume1, volume2); } -template<typename T, typename U, unsigned bits> +template<SampleFormat F, class Traits=SampleTraits<F>> static void -PcmAddVolumeVoid(void *a, const void *b, size_t size, int volume1, int volume2) +PcmAddVolumeVoid(PcmDither &dither, + void *a, const void *b, size_t size, int volume1, int volume2) { - constexpr size_t sample_size = sizeof(T); + constexpr size_t sample_size = Traits::SAMPLE_SIZE; assert(size % sample_size == 0); - PcmAddVolume<T, U, bits>((T *)a, (const T *)b, size / sample_size, - volume1, volume2); + PcmAddVolume<F, Traits>(dither, + typename Traits::pointer_type(a), + typename Traits::const_pointer_type(b), + size / sample_size, + volume1, volume2); } static void @@ -72,7 +86,7 @@ pcm_add_vol_float(float *buffer1, const float *buffer2, } static bool -pcm_add_vol(void *buffer1, const void *buffer2, size_t size, +pcm_add_vol(PcmDither &dither, void *buffer1, const void *buffer2, size_t size, int vol1, int vol2, SampleFormat format) { @@ -83,23 +97,27 @@ pcm_add_vol(void *buffer1, const void *buffer2, size_t size, return false; case SampleFormat::S8: - PcmAddVolumeVoid<int8_t, int32_t, 8>(buffer1, buffer2, size, - vol1, vol2); + PcmAddVolumeVoid<SampleFormat::S8>(dither, + buffer1, buffer2, size, + vol1, vol2); return true; case SampleFormat::S16: - PcmAddVolumeVoid<int16_t, int32_t, 16>(buffer1, buffer2, size, - vol1, vol2); + PcmAddVolumeVoid<SampleFormat::S16>(dither, + buffer1, buffer2, size, + vol1, vol2); return true; case SampleFormat::S24_P32: - PcmAddVolumeVoid<int32_t, int64_t, 24>(buffer1, buffer2, size, - vol1, vol2); + PcmAddVolumeVoid<SampleFormat::S24_P32>(dither, + buffer1, buffer2, size, + vol1, vol2); return true; case SampleFormat::S32: - PcmAddVolumeVoid<int32_t, int64_t, 32>(buffer1, buffer2, size, - vol1, vol2); + PcmAddVolumeVoid<SampleFormat::S32>(dither, + buffer1, buffer2, size, + vol1, vol2); return true; case SampleFormat::FLOAT: @@ -114,30 +132,35 @@ pcm_add_vol(void *buffer1, const void *buffer2, size_t size, gcc_unreachable(); } -template<typename T, typename U, unsigned bits> -static T -PcmAdd(T _a, T _b) +template<SampleFormat F, class Traits=SampleTraits<F>> +static typename Traits::value_type +PcmAdd(typename Traits::value_type _a, typename Traits::value_type _b) { - U a(_a), b(_b); - return PcmClamp<T, U, bits>(a + b); + typename Traits::sum_type a(_a), b(_b); + + return PcmClamp<F, Traits>(a + b); } -template<typename T, typename U, unsigned bits> +template<SampleFormat F, class Traits=SampleTraits<F>> static void -PcmAdd(T *a, const T *b, unsigned n) +PcmAdd(typename Traits::pointer_type a, + typename Traits::const_pointer_type b, + size_t n) { for (size_t i = 0; i != n; ++i) - a[i] = PcmAdd<T, U, bits>(a[i], b[i]); + a[i] = PcmAdd<F, Traits>(a[i], b[i]); } -template<typename T, typename U, unsigned bits> +template<SampleFormat F, class Traits=SampleTraits<F>> static void PcmAddVoid(void *a, const void *b, size_t size) { - constexpr size_t sample_size = sizeof(T); + constexpr size_t sample_size = Traits::SAMPLE_SIZE; assert(size % sample_size == 0); - PcmAdd<T, U, bits>((T *)a, (const T *)b, size / sample_size); + PcmAdd<F, Traits>(typename Traits::pointer_type(a), + typename Traits::const_pointer_type(b), + size / sample_size); } static void @@ -162,19 +185,19 @@ pcm_add(void *buffer1, const void *buffer2, size_t size, return false; case SampleFormat::S8: - PcmAddVoid<int8_t, int32_t, 8>(buffer1, buffer2, size); + PcmAddVoid<SampleFormat::S8>(buffer1, buffer2, size); return true; case SampleFormat::S16: - PcmAddVoid<int16_t, int32_t, 16>(buffer1, buffer2, size); + PcmAddVoid<SampleFormat::S16>(buffer1, buffer2, size); return true; case SampleFormat::S24_P32: - PcmAddVoid<int32_t, int64_t, 24>(buffer1, buffer2, size); + PcmAddVoid<SampleFormat::S24_P32>(buffer1, buffer2, size); return true; case SampleFormat::S32: - PcmAddVoid<int32_t, int64_t, 32>(buffer1, buffer2, size); + PcmAddVoid<SampleFormat::S32>(buffer1, buffer2, size); return true; case SampleFormat::FLOAT: @@ -188,10 +211,9 @@ pcm_add(void *buffer1, const void *buffer2, size_t size, } bool -pcm_mix(void *buffer1, const void *buffer2, size_t size, +pcm_mix(PcmDither &dither, 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 -1 @@ -202,8 +224,9 @@ pcm_mix(void *buffer1, const void *buffer2, size_t size, 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); + int vol1 = s * PCM_VOLUME_1S + 0.5; + vol1 = Clamp<int>(vol1, 0, PCM_VOLUME_1S); - return pcm_add_vol(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format); + return pcm_add_vol(dither, buffer1, buffer2, size, + vol1, PCM_VOLUME_1S - vol1, format); } diff --git a/src/pcm/PcmMix.hxx b/src/pcm/PcmMix.hxx index 637c88f8a..4e22a33f1 100644 --- a/src/pcm/PcmMix.hxx +++ b/src/pcm/PcmMix.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,6 +25,8 @@ #include <stddef.h> +class PcmDither; + /* * Linearly mixes two PCM buffers. Both must have the same length and * the same audio format. The formula is: @@ -44,7 +46,7 @@ */ gcc_warn_unused_result bool -pcm_mix(void *buffer1, const void *buffer2, size_t size, +pcm_mix(PcmDither &dither, void *buffer1, const void *buffer2, size_t size, SampleFormat format, float portion1); #endif diff --git a/src/pcm/PcmPack.cxx b/src/pcm/PcmPack.cxx index 8920eb288..7a3379ad0 100644 --- a/src/pcm/PcmPack.cxx +++ b/src/pcm/PcmPack.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm/PcmPack.hxx b/src/pcm/PcmPack.hxx index aed011767..271a3cd25 100644 --- a/src/pcm/PcmPack.hxx +++ b/src/pcm/PcmPack.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm/PcmPrng.hxx b/src/pcm/PcmPrng.hxx index 0c823250d..5233caba6 100644 --- a/src/pcm/PcmPrng.hxx +++ b/src/pcm/PcmPrng.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,7 +24,7 @@ * A very simple linear congruential PRNG. It's good enough for PCM * dithering. */ -static unsigned long +constexpr static inline unsigned long pcm_prng(unsigned long state) { return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL; diff --git a/src/pcm/PcmResample.cxx b/src/pcm/PcmResample.cxx deleted file mode 100644 index 01f269ea9..000000000 --- a/src/pcm/PcmResample.cxx +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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); -} - -const int32_t * -PcmResampler::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) -{ -#ifdef HAVE_LIBSAMPLERATE - if (pcm_resample_lsr_enabled()) - return pcm_resample_lsr_24(this, channels, - src_rate, src_buffer, src_size, - dest_rate, dest_size_r, - error_r); -#else - (void)error_r; -#endif - - /* reuse the 32 bit code - the resampler code doesn't care if - the upper 8 bits are actually used */ - 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 deleted file mode 100644 index e839d6ecd..000000000 --- a/src/pcm/PcmResample.hxx +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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); -}; - -bool -pcm_resample_global_init(Error &error); - -#endif diff --git a/src/pcm/PcmResampleFallback.cxx b/src/pcm/PcmResampleFallback.cxx deleted file mode 100644 index a62cd64f7..000000000 --- a/src/pcm/PcmResampleFallback.cxx +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 deleted file mode 100644 index 5090c13d1..000000000 --- a/src/pcm/PcmResampleInternal.hxx +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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); - -const int32_t * -pcm_resample_lsr_24(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 deleted file mode 100644 index 9eac2d545..000000000 --- a/src/pcm/PcmResampleLibsamplerate.cxx +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "PcmUtils.hxx" -#include "util/ASCII.hxx" -#include "util/Error.hxx" -#include "util/Domain.hxx" -#include "Log.hxx" - -#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 (StringEqualsCaseASCII(s, name, length)) { - 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; -} - -const int32_t * -pcm_resample_lsr_24(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) -{ - const auto result = pcm_resample_lsr_32(state, channels, - src_rate, src_buffer, src_size, - dest_rate, dest_size_r, - error); - if (result != nullptr) - /* src_float_to_int_array() clamps for 32 bit - integers; now make sure everything's fine for 24 - bit */ - /* TODO: eliminate the 32 bit clamp to reduce overhead */ - PcmClampN<int32_t, int32_t, 24>(const_cast<int32_t *>(result), - result, - *dest_size_r / sizeof(*result)); - - return result; -} diff --git a/src/pcm/PcmUtils.hxx b/src/pcm/PcmUtils.hxx index febe12d7b..23870a729 100644 --- a/src/pcm/PcmUtils.hxx +++ b/src/pcm/PcmUtils.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,53 +26,31 @@ #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); -} +enum class SampleFormat : uint8_t; +template<SampleFormat F> struct SampleTraits; /** * 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> +template<SampleFormat F, class Traits=SampleTraits<F>> gcc_const -static inline T -PcmClamp(U x) +static inline typename Traits::value_type +PcmClamp(typename Traits::long_type x) { - constexpr U MIN_VALUE = -(U(1) << (bits - 1)); - constexpr U MAX_VALUE = (U(1) << (bits - 1)) - 1; + typedef typename Traits::value_type T; typedef std::numeric_limits<T> limits; - static_assert(MIN_VALUE >= limits::min(), "out of range"); - static_assert(MAX_VALUE <= limits::max(), "out of range"); + static_assert(Traits::MIN >= limits::min(), "out of range"); + static_assert(Traits::MAX <= limits::max(), "out of range"); - if (gcc_unlikely(x < MIN_VALUE)) - return T(MIN_VALUE); + if (gcc_unlikely(x < Traits::MIN)) + return T(Traits::MIN); - if (gcc_unlikely(x > MAX_VALUE)) - return T(MAX_VALUE); + if (gcc_unlikely(x > Traits::MAX)) + return T(Traits::MAX); return T(x); } -/** - * Check if the values in this buffer are within the range of the - * provided bit size, and clamps them whenever necessary. - */ -template<typename T, typename U, unsigned bits> -static inline void -PcmClampN(T *dest, const U *src, unsigned n) -{ - while (n-- > 0) - *dest++ = PcmClamp<T, U, bits>(*src++); -} - #endif diff --git a/src/pcm/PcmVolume.cxx b/src/pcm/PcmVolume.cxx deleted file mode 100644 index 564880633..000000000 --- a/src/pcm/PcmVolume.cxx +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 <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 deleted file mode 100644 index 8cd82acf7..000000000 --- a/src/pcm/PcmVolume.hxx +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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/pcm/Resampler.hxx b/src/pcm/Resampler.hxx new file mode 100644 index 000000000..9b6ccbbc7 --- /dev/null +++ b/src/pcm/Resampler.hxx @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_RESAMPLER_HXX +#define MPD_PCM_RESAMPLER_HXX + +#include "util/ConstBuffer.hxx" +#include "Compiler.h" + +struct AudioFormat; +class Error; + +/** + * This is an interface for plugins that convert PCM data to a + * specific sample rate. + */ +class PcmResampler { +public: + virtual ~PcmResampler() {} + + /** + * Opens the resampler, preparing it for Resample(). + * + * @param af the audio format of incoming data; the plugin may + * modify the object to enforce another input format (however, + * it may not request a different input sample rate) + * @param new_sample_rate the requested output sample rate + * @param error location to store the error + * @return the format of outgoing data or + * AudioFormat::Undefined() on error + */ + virtual AudioFormat Open(AudioFormat &af, unsigned new_sample_rate, + Error &error) = 0; + + /** + * Closes the resampler. After that, you may call Open() + * again. + */ + virtual void Close() = 0; + + /** + * Resamples a block of PCM data. + * + * @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 nullptr + * to ignore errors. + * @return the destination buffer on success (will be + * invalidated by filter_close() or filter_filter()), nullptr on + * error + */ + gcc_pure + virtual ConstBuffer<void> Resample(ConstBuffer<void> src, + Error &error) = 0; +}; + +#endif diff --git a/src/pcm/SoxrResampler.cxx b/src/pcm/SoxrResampler.cxx new file mode 100644 index 000000000..2508e816e --- /dev/null +++ b/src/pcm/SoxrResampler.cxx @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "SoxrResampler.hxx" +#include "AudioFormat.hxx" +#include "util/ASCII.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <soxr.h> + +#include <assert.h> + +static constexpr Domain soxr_domain("soxr"); + +static unsigned long soxr_quality_recipe = SOXR_HQ; + +static const char * +soxr_quality_name(unsigned long recipe) +{ + switch (recipe) { + case SOXR_VHQ: + return "Very High Quality"; + case SOXR_HQ: + return "High Quality"; + case SOXR_MQ: + return "Medium Quality"; + case SOXR_LQ: + return "Low Quality"; + case SOXR_QQ: + return "Quick"; + } + + gcc_unreachable(); +} + +static bool +soxr_parse_converter(const char *converter) +{ + assert(converter != nullptr); + + assert(memcmp(converter, "soxr", 4) == 0); + if (converter[4] == '\0') + return true; + if (converter[4] != ' ') + return false; + + // converter example is "soxr very high", we want the "very high" part + const char *quality = converter + 5; + if (strcmp(quality, "very high") == 0) + soxr_quality_recipe = SOXR_VHQ; + else if (strcmp(quality, "high") == 0) + soxr_quality_recipe = SOXR_HQ; + else if (strcmp(quality, "medium") == 0) + soxr_quality_recipe = SOXR_MQ; + else if (strcmp(quality, "low") == 0) + soxr_quality_recipe = SOXR_LQ; + else if (strcmp(quality, "quick") == 0) + soxr_quality_recipe = SOXR_QQ; + else + return false; + + return true; +} + +bool +pcm_resample_soxr_global_init(const char *converter, Error &error) +{ + if (!soxr_parse_converter(converter)) { + error.Format(soxr_domain, + "unknown samplerate converter '%s'", converter); + return false; + } + + FormatDebug(soxr_domain, + "soxr converter '%s'", + soxr_quality_name(soxr_quality_recipe)); + + return true; +} + +AudioFormat +SoxrPcmResampler::Open(AudioFormat &af, unsigned new_sample_rate, + Error &error) +{ + assert(af.IsValid()); + assert(audio_valid_sample_rate(new_sample_rate)); + + soxr_error_t e; + soxr_quality_spec_t quality = soxr_quality_spec(soxr_quality_recipe, 0); + soxr = soxr_create(af.sample_rate, new_sample_rate, + af.channels, &e, + nullptr, &quality, nullptr); + if (soxr == nullptr) { + error.Format(soxr_domain, + "soxr initialization has failed: %s", e); + return AudioFormat::Undefined(); + } + + FormatDebug(soxr_domain, "soxr engine '%s'", soxr_engine(soxr)); + + channels = af.channels; + + ratio = float(new_sample_rate) / float(af.sample_rate); + FormatDebug(soxr_domain, + "samplerate conversion ratio to %.2lf", + ratio); + + /* libsoxr works with floating point samples */ + af.format = SampleFormat::FLOAT; + + AudioFormat result = af; + result.sample_rate = new_sample_rate; + return result; +} + +void +SoxrPcmResampler::Close() +{ + soxr_delete(soxr); +} + +ConstBuffer<void> +SoxrPcmResampler::Resample(ConstBuffer<void> src, Error &error) +{ + const size_t frame_size = channels * sizeof(float); + assert(src.size % frame_size == 0); + + const size_t n_frames = src.size / frame_size; + + const size_t o_frames = size_t(n_frames * ratio + 0.5); + + float *output_buffer = (float *)buffer.Get(o_frames * frame_size); + + size_t i_done, o_done; + soxr_error_t e = soxr_process(soxr, src.data, n_frames, &i_done, + output_buffer, o_frames, &o_done); + if (e != nullptr) { + error.Format(soxr_domain, "soxr error: %s", e); + return nullptr; + } + + return { output_buffer, o_done * frame_size }; +} diff --git a/src/pcm/SoxrResampler.hxx b/src/pcm/SoxrResampler.hxx new file mode 100644 index 000000000..efe9bd5cb --- /dev/null +++ b/src/pcm/SoxrResampler.hxx @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_SOXR_RESAMPLER_HXX +#define MPD_PCM_SOXR_RESAMPLER_HXX + +#include "Resampler.hxx" +#include "PcmBuffer.hxx" + +struct AudioFormat; + +/** + * A resampler using soxr. + */ +class SoxrPcmResampler final : public PcmResampler { + struct soxr *soxr; + + unsigned channels; + float ratio; + + PcmBuffer buffer; + +public: + virtual AudioFormat Open(AudioFormat &af, unsigned new_sample_rate, + Error &error) override; + virtual void Close() override; + virtual ConstBuffer<void> Resample(ConstBuffer<void> src, + Error &error) override; +}; + +bool +pcm_resample_soxr_global_init(const char *converter, Error &error); + +#endif diff --git a/src/pcm/Traits.hxx b/src/pcm/Traits.hxx new file mode 100644 index 000000000..97259ac73 --- /dev/null +++ b/src/pcm/Traits.hxx @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_TRAITS_HXX +#define MPD_PCM_TRAITS_HXX + +#include "check.h" +#include "AudioFormat.hxx" + +#include <stdint.h> +#include <stddef.h> + +/** + * This template describes the specified #SampleFormat. This is an + * empty prototype; the specializations contain the real definitions. + * See SampleTraits<uint8_t> for more documentation. + */ +template<SampleFormat F> +struct SampleTraits {}; + +template<> +struct SampleTraits<SampleFormat::S8> { + /** + * The type used for one sample value. + */ + typedef int8_t value_type; + + /** + * A writable pointer. + */ + typedef value_type *pointer_type; + + /** + * A read-only pointer. + */ + typedef const value_type *const_pointer_type; + + /** + * A "long" type that is large and accurate enough for adding + * two values without risking an (integer) overflow or + * (floating point) precision loss. + */ + typedef int sum_type; + + /** + * A "long" type that is large and accurate enough for + * arithmetic without risking an (integer) overflow or + * (floating point) precision loss. + */ + typedef int_least32_t long_type; + + /** + * The size of one sample in bytes. + */ + static constexpr size_t SAMPLE_SIZE = sizeof(value_type); + + /** + * The integer bit depth of one sample. This attribute may + * not exist if this is not an integer sample format. + */ + static constexpr unsigned BITS = sizeof(value_type) * 8; + + /** + * The minimum sample value. + */ + static constexpr value_type MIN = -(sum_type(1) << (BITS - 1)); + + /** + * The maximum sample value. + */ + static constexpr value_type MAX = (sum_type(1) << (BITS - 1)) - 1; +}; + +template<> +struct SampleTraits<SampleFormat::S16> { + typedef int16_t value_type; + typedef value_type *pointer_type; + typedef const value_type *const_pointer_type; + + typedef int_least32_t sum_type; + typedef int_least32_t long_type; + + static constexpr size_t SAMPLE_SIZE = sizeof(value_type); + static constexpr unsigned BITS = sizeof(value_type) * 8; + + static constexpr value_type MIN = -(sum_type(1) << (BITS - 1)); + static constexpr value_type MAX = (sum_type(1) << (BITS - 1)) - 1; +}; + +template<> +struct SampleTraits<SampleFormat::S32> { + typedef int32_t value_type; + typedef value_type *pointer_type; + typedef const value_type *const_pointer_type; + + typedef int_least64_t sum_type; + typedef int_least64_t long_type; + + static constexpr size_t SAMPLE_SIZE = sizeof(value_type); + static constexpr unsigned BITS = sizeof(value_type) * 8; + + static constexpr value_type MIN = -(sum_type(1) << (BITS - 1)); + static constexpr value_type MAX = (sum_type(1) << (BITS - 1)) - 1; +}; + +template<> +struct SampleTraits<SampleFormat::S24_P32> { + typedef int32_t value_type; + typedef value_type *pointer_type; + typedef const value_type *const_pointer_type; + + typedef int_least32_t sum_type; + typedef int_least64_t long_type; + + static constexpr size_t SAMPLE_SIZE = sizeof(value_type); + static constexpr unsigned BITS = 24; + + static constexpr value_type MIN = -(sum_type(1) << (BITS - 1)); + static constexpr value_type MAX = (sum_type(1) << (BITS - 1)) - 1; +}; + +template<> +struct SampleTraits<SampleFormat::FLOAT> { + typedef float value_type; + typedef value_type *pointer_type; + typedef const value_type *const_pointer_type; + + typedef float sum_type; + typedef float long_type; + + static constexpr size_t SAMPLE_SIZE = sizeof(value_type); + + static constexpr value_type MIN = -1; + static constexpr value_type MAX = 1; +}; + +#endif diff --git a/src/pcm/Volume.cxx b/src/pcm/Volume.cxx new file mode 100644 index 000000000..b12d8fd41 --- /dev/null +++ b/src/pcm/Volume.cxx @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "Domain.hxx" +#include "PcmUtils.hxx" +#include "Traits.hxx" +#include "util/ConstBuffer.hxx" +#include "util/Error.hxx" + +#include "PcmDither.cxx" // including the .cxx file to get inlined templates + +#include <stdint.h> +#include <string.h> + +template<SampleFormat F, class Traits=SampleTraits<F>> +static inline typename Traits::value_type +pcm_volume_sample(PcmDither &dither, + typename Traits::value_type _sample, + int volume) +{ + typename Traits::long_type sample(_sample); + + return dither.DitherShift<typename Traits::long_type, + Traits::BITS + PCM_VOLUME_BITS, + Traits::BITS>(sample * volume); +} + +template<SampleFormat F, class Traits=SampleTraits<F>> +static void +pcm_volume_change(PcmDither &dither, + typename Traits::pointer_type dest, + typename Traits::const_pointer_type src, + size_t n, + int volume) +{ + for (size_t i = 0; i != n; ++i) + dest[i] = pcm_volume_sample<F, Traits>(dither, src[i], volume); +} + +static void +pcm_volume_change_8(PcmDither &dither, + int8_t *dest, const int8_t *src, size_t n, + int volume) +{ + pcm_volume_change<SampleFormat::S8>(dither, dest, src, n, volume); +} + +static void +pcm_volume_change_16(PcmDither &dither, + int16_t *dest, const int16_t *src, size_t n, + int volume) +{ + pcm_volume_change<SampleFormat::S16>(dither, dest, src, n, volume); +} + +static void +pcm_volume_change_24(PcmDither &dither, + int32_t *dest, const int32_t *src, size_t n, + int volume) +{ + pcm_volume_change<SampleFormat::S24_P32>(dither, dest, src, n, + volume); +} + +static void +pcm_volume_change_32(PcmDither &dither, + int32_t *dest, const int32_t *src, size_t n, + int volume) +{ + pcm_volume_change<SampleFormat::S32>(dither, dest, src, n, volume); +} + +static void +pcm_volume_change_float(float *dest, const float *src, size_t n, + float volume) +{ + for (size_t i = 0; i != n; ++i) + dest[i] = src[i] * volume; +} + +bool +PcmVolume::Open(SampleFormat _format, Error &error) +{ + assert(format == SampleFormat::UNDEFINED); + + switch (_format) { + case SampleFormat::UNDEFINED: + error.Format(pcm_domain, + "Software volume for %s is not implemented", + sample_format_to_string(_format)); + return false; + + case SampleFormat::S8: + case SampleFormat::S16: + case SampleFormat::S24_P32: + case SampleFormat::S32: + case SampleFormat::FLOAT: + break; + + case SampleFormat::DSD: + // TODO: implement this; currently, it's a no-op + break; + } + + format = _format; + return true; +} + +ConstBuffer<void> +PcmVolume::Apply(ConstBuffer<void> src) +{ + if (volume == PCM_VOLUME_1) + return src; + + void *data = buffer.Get(src.size); + + if (volume == 0) { + /* optimized special case: 0% volume = memset(0) */ + /* TODO: is this valid for all sample formats? What + about floating point? */ + memset(data, 0, src.size); + return { data, src.size }; + } + + switch (format) { + case SampleFormat::UNDEFINED: + assert(false); + gcc_unreachable(); + + case SampleFormat::S8: + pcm_volume_change_8(dither, (int8_t *)data, + (const int8_t *)src.data, + src.size / sizeof(int8_t), + volume); + break; + + case SampleFormat::S16: + pcm_volume_change_16(dither, (int16_t *)data, + (const int16_t *)src.data, + src.size / sizeof(int16_t), + volume); + break; + + case SampleFormat::S24_P32: + pcm_volume_change_24(dither, (int32_t *)data, + (const int32_t *)src.data, + src.size / sizeof(int32_t), + volume); + break; + + case SampleFormat::S32: + pcm_volume_change_32(dither, (int32_t *)data, + (const int32_t *)src.data, + src.size / sizeof(int32_t), + volume); + break; + + case SampleFormat::FLOAT: + pcm_volume_change_float((float *)data, + (const float *)src.data, + src.size / sizeof(float), + pcm_volume_to_float(volume)); + break; + + case SampleFormat::DSD: + // TODO: implement this; currently, it's a no-op + return src; + } + + return { data, src.size }; +} diff --git a/src/pcm/Volume.hxx b/src/pcm/Volume.hxx new file mode 100644 index 000000000..a156fc72e --- /dev/null +++ b/src/pcm/Volume.hxx @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "AudioFormat.hxx" +#include "PcmBuffer.hxx" +#include "PcmDither.hxx" + +#include <stdint.h> +#include <stddef.h> + +#ifndef NDEBUG +#include <assert.h> +#endif + +class Error; +template<typename T> struct ConstBuffer; + +/** + * Number of fractional bits for a fixed-point volume value. + */ +static constexpr unsigned PCM_VOLUME_BITS = 10; + +/** + * This value means "100% volume". + */ +static constexpr unsigned PCM_VOLUME_1 = 1024; +static constexpr int PCM_VOLUME_1S = PCM_VOLUME_1; + +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; +} + +/** + * A class that converts samples from one format to another. + */ +class PcmVolume { + SampleFormat format; + + unsigned volume; + + PcmBuffer buffer; + PcmDither dither; + +public: + PcmVolume() + :volume(PCM_VOLUME_1) { +#ifndef NDEBUG + format = SampleFormat::UNDEFINED; +#endif + } + +#ifndef NDEBUG + ~PcmVolume() { + assert(format == SampleFormat::UNDEFINED); + } +#endif + + unsigned GetVolume() const { + return volume; + } + + /** + * @param _volume the volume level in the range + * [0..#PCM_VOLUME_1]; may be bigger than #PCM_VOLUME_1, but + * then it will most likely clip a lot + */ + void SetVolume(unsigned _volume) { + volume = _volume; + } + + /** + * Opens the object, prepare for Apply(). + * + * @param format the sample format + * @param error location to store the error + * @return true on success + */ + bool Open(SampleFormat format, Error &error); + + /** + * Closes the object. After that, you may call Open() again. + */ + void Close() { +#ifndef NDEBUG + assert(format != SampleFormat::UNDEFINED); + format = SampleFormat::UNDEFINED; +#endif + } + + /** + * Apply the volume level. + */ + gcc_pure + ConstBuffer<void> Apply(ConstBuffer<void> src); +}; + +#endif diff --git a/src/playlist/AsxPlaylistPlugin.cxx b/src/playlist/AsxPlaylistPlugin.cxx deleted file mode 100644 index 94198b8c3..000000000 --- a/src/playlist/AsxPlaylistPlugin.cxx +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/ASCII.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. - */ - TagType 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] != nullptr; ++i) - if (StringEqualsCaseASCII(attribute_names[i], name)) - return attribute_values[i]; - - return nullptr; -} - -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 (StringEqualsCaseASCII(element_name, "entry")) { - parser->state = AsxParser::ENTRY; - parser->song = Song::NewRemote("asx:"); - parser->tag = TAG_NUM_OF_ITEM_TYPES; - } - - break; - - case AsxParser::ENTRY: - if (StringEqualsCaseASCII(element_name, "ref")) { - const gchar *href = get_attribute(attribute_names, - attribute_values, - "href"); - if (href != nullptr) { - /* 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 != nullptr) { - song->tag = parser->song->tag; - parser->song->tag = nullptr; - parser->song->Free(); - } - - parser->song = song; - } - } else if (StringEqualsCaseASCII(element_name, "author")) - /* is that correct? or should it be COMPOSER - or PERFORMER? */ - parser->tag = TAG_ARTIST; - else if (StringEqualsCaseASCII(element_name, "title")) - 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 (StringEqualsCaseASCII(element_name, "entry")) { - 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 == nullptr) - 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(InputStream &is) -{ - AsxParser parser; - GMarkupParseContext *context; - char buffer[1024]; - size_t nbytes; - bool success; - Error error2; - GError *error = nullptr; - - /* 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 nullptr; - } - - 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 nullptr; - } - } - - 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 nullptr; - } - - 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", - nullptr -}; - -static const char *const asx_mime_types[] = { - "video/x-ms-asf", - nullptr -}; - -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 deleted file mode 100644 index 240c1824a..000000000 --- a/src/playlist/AsxPlaylistPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 42a43bbad..000000000 --- a/src/playlist/CuePlaylistPlugin.cxx +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 { - InputStream &is; - TextInputStream tis; - CueParser parser; - - public: - CuePlaylist(InputStream &_is) - :is(_is), tis(is) { - } - - virtual Song *NextSong() override; -}; - -static SongEnumerator * -cue_playlist_open_stream(InputStream &is) -{ - return new CuePlaylist(is); -} - -Song * -CuePlaylist::NextSong() -{ - Song *song = parser.Get(); - if (song != nullptr) - return song; - - std::string line; - while (tis.ReadLine(line)) { - parser.Feed(line.c_str()); - song = parser.Get(); - if (song != nullptr) - return song; - } - - parser.Finish(); - return parser.Get(); -} - -static const char *const cue_playlist_suffixes[] = { - "cue", - nullptr -}; - -static const char *const cue_playlist_mime_types[] = { - "application/x-cue", - nullptr -}; - -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 deleted file mode 100644 index cf5e3a8f0..000000000 --- a/src/playlist/CuePlaylistPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index a1a865c08..000000000 --- a/src/playlist/DespotifyPlaylistPlugin.cxx +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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 deleted file mode 100644 index c1e5b7f39..000000000 --- a/src/playlist/DespotifyPlaylistPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 deleted file mode 100644 index d758650eb..000000000 --- a/src/playlist/EmbeddedCuePlaylistPlugin.cxx +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public 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 "fs/Traits.hxx" -#include "fs/AllocatedPath.hxx" -#include "util/ASCII.hxx" - -#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. - */ - std::string filename; - - /** - * The value of the file's "CUESHEET" tag. - */ - std::string cuesheet; - - /** - * The offset of the next line within "cuesheet". - */ - char *next; - - CueParser *parser; - -public: - EmbeddedCuePlaylist() - :parser(nullptr) { - } - - virtual ~EmbeddedCuePlaylist() { - delete parser; - } - - virtual Song *NextSong() override; -}; - -static void -embcue_tag_pair(const char *name, const char *value, void *ctx) -{ - EmbeddedCuePlaylist *playlist = (EmbeddedCuePlaylist *)ctx; - - if (playlist->cuesheet.empty() && - StringEqualsCaseASCII(name, "cuesheet")) - playlist->cuesheet = 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 (!PathTraits::IsAbsoluteUTF8(uri)) - /* only local files supported */ - return nullptr; - - const auto path_fs = AllocatedPath::FromUTF8(uri); - if (path_fs.IsNull()) - return nullptr; - - const auto playlist = new EmbeddedCuePlaylist(); - - tag_file_scan(path_fs, &embcue_tag_handler, playlist); - if (playlist->cuesheet.empty()) { - tag_ape_scan2(path_fs, &embcue_tag_handler, playlist); - if (playlist->cuesheet.empty()) - tag_id3_scan(path_fs, &embcue_tag_handler, playlist); - } - - if (playlist->cuesheet.empty()) { - /* no "CUESHEET" tag found */ - delete playlist; - return nullptr; - } - - playlist->filename = PathTraits::GetBaseUTF8(uri); - - playlist->next = &playlist->cuesheet[0]; - playlist->parser = new CueParser(); - - return playlist; -} - -Song * -EmbeddedCuePlaylist::NextSong() -{ - Song *song = parser->Get(); - if (song != nullptr) - return song; - - while (*next != 0) { - const char *line = next; - char *eol = strpbrk(next, "\r\n"); - if (eol != nullptr) { - /* 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 != nullptr) - return song->ReplaceURI(filename.c_str()); - } - - parser->Finish(); - song = parser->Get(); - if (song != nullptr) - song = song->ReplaceURI(filename.c_str()); - 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", - nullptr -}; - -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 deleted file mode 100644 index e306730f4..000000000 --- a/src/playlist/EmbeddedCuePlaylistPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 8d260fec7..000000000 --- a/src/playlist/ExtM3uPlaylistPlugin.cxx +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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(InputStream &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(InputStream &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(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; - } - - line_s = strchug_fast(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 deleted file mode 100644 index 844fba15c..000000000 --- a/src/playlist/ExtM3uPlaylistPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index 3f99bdfdf..000000000 --- a/src/playlist/M3uPlaylistPlugin.cxx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 "util/StringUtil.hxx" -#include "TextInputStream.hxx" - -class M3uPlaylist final : public SongEnumerator { - TextInputStream tis; - -public: - M3uPlaylist(InputStream &is) - :tis(is) { - } - - virtual Song *NextSong() override; -}; - -static SongEnumerator * -m3u_open_stream(InputStream &is) -{ - return new M3uPlaylist(is); -} - -Song * -M3uPlaylist::NextSong() -{ - std::string line; - const char *line_s; - - do { - if (!tis.ReadLine(line)) - return nullptr; - - line_s = line.c_str(); - line_s = strchug_fast(line_s); - } while (line_s[0] == '#' || *line_s == 0); - - return Song::NewRemote(line_s); -} - -static const char *const m3u_suffixes[] = { - "m3u", - nullptr -}; - -static const char *const m3u_mime_types[] = { - "audio/x-mpegurl", - nullptr -}; - -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 deleted file mode 100644 index a2058bb29..000000000 --- a/src/playlist/M3uPlaylistPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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/MemorySongEnumerator.cxx b/src/playlist/MemorySongEnumerator.cxx new file mode 100644 index 000000000..c3127c2bf --- /dev/null +++ b/src/playlist/MemorySongEnumerator.cxx @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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" + +DetachedSong * +MemorySongEnumerator::NextSong() +{ + if (songs.empty()) + return nullptr; + + auto result = new DetachedSong(std::move(songs.front())); + songs.pop_front(); + return result; +} diff --git a/src/playlist/MemorySongEnumerator.hxx b/src/playlist/MemorySongEnumerator.hxx new file mode 100644 index 000000000..e87a4f6dd --- /dev/null +++ b/src/playlist/MemorySongEnumerator.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "DetachedSong.hxx" + +#include <forward_list> + +class MemorySongEnumerator final : public SongEnumerator { + std::forward_list<DetachedSong> songs; + +public: + MemorySongEnumerator(std::forward_list<DetachedSong> &&_songs) + :songs(std::move(_songs)) {} + + virtual DetachedSong *NextSong() override; +}; + +#endif diff --git a/src/playlist/PlaylistAny.cxx b/src/playlist/PlaylistAny.cxx new file mode 100644 index 000000000..04efbb569 --- /dev/null +++ b/src/playlist/PlaylistAny.cxx @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "input/InputStream.hxx" +#include "Log.hxx" + +#include <assert.h> + +static SongEnumerator * +playlist_open_remote(const char *uri, Mutex &mutex, Cond &cond, + InputStream **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; + InputStream *is = InputStream::OpenReady(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, + InputStream **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/PlaylistAny.hxx b/src/playlist/PlaylistAny.hxx new file mode 100644 index 000000000..c472afb31 --- /dev/null +++ b/src/playlist/PlaylistAny.hxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 + +class Mutex; +class Cond; +class SongEnumerator; +struct InputStream; + +/** + * 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, + InputStream **is_r); + +#endif diff --git a/src/playlist/PlaylistMapper.cxx b/src/playlist/PlaylistMapper.cxx new file mode 100644 index 000000000..0df0bc61f --- /dev/null +++ b/src/playlist/PlaylistMapper.cxx @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/AllocatedPath.hxx" +#include "util/UriUtil.hxx" + +#include <assert.h> + +static SongEnumerator * +playlist_open_path(const char *path_fs, Mutex &mutex, Cond &cond, + InputStream **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, + InputStream **is_r) +{ + assert(spl_valid_name(uri)); + + const auto &playlist_directory_fs = map_spl_path(); + if (playlist_directory_fs.IsNull()) + return nullptr; + + const auto uri_fs = AllocatedPath::FromUTF8(uri); + if (uri_fs.IsNull()) + return nullptr; + + const auto path_fs = + AllocatedPath::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, + InputStream **is_r) +{ + assert(uri_safe_local(uri)); + + const auto 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, + InputStream **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/playlist/PlaylistMapper.hxx b/src/playlist/PlaylistMapper.hxx new file mode 100644 index 000000000..a460cb124 --- /dev/null +++ b/src/playlist/PlaylistMapper.hxx @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 + +class Mutex; +class Cond; +class SongEnumerator; +struct InputStream; + +/** + * 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, + InputStream **is_r); + +#endif diff --git a/src/playlist/PlaylistPlugin.hxx b/src/playlist/PlaylistPlugin.hxx new file mode 100644 index 000000000..d3c44f1f4 --- /dev/null +++ b/src/playlist/PlaylistPlugin.hxx @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 + +struct config_param; +struct InputStream; +struct Tag; +class Mutex; +class Cond; +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)(InputStream &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, + InputStream &is) +{ + return plugin->open_stream(is); +} + +#endif diff --git a/src/playlist/PlaylistQueue.cxx b/src/playlist/PlaylistQueue.cxx new file mode 100644 index 000000000..564c94d0a --- /dev/null +++ b/src/playlist/PlaylistQueue.cxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "PlaylistAny.hxx" +#include "PlaylistSong.hxx" +#include "Playlist.hxx" +#include "input/InputStream.hxx" +#include "SongEnumerator.hxx" +#include "DetachedSong.hxx" +#include "thread/Cond.hxx" +#include "fs/Traits.hxx" + +PlaylistResult +playlist_load_into_queue(const char *uri, SongEnumerator &e, + unsigned start_index, unsigned end_index, + playlist &dest, PlayerControl &pc, + bool secure) +{ + const std::string base_uri = uri != nullptr + ? PathTraitsUTF8::GetParent(uri) + : std::string("."); + + DetachedSong *song; + for (unsigned i = 0; + i < end_index && (song = e.NextSong()) != nullptr; + ++i) { + if (i < start_index) { + /* skip songs before the start index */ + delete song; + continue; + } + + if (!playlist_check_translate_song(*song, base_uri.c_str(), + secure)) { + delete song; + continue; + } + + PlaylistResult result = dest.AppendSong(pc, std::move(*song)); + delete song; + if (result != PlaylistResult::SUCCESS) + return result; + } + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist_open_into_queue(const char *uri, + unsigned start_index, unsigned end_index, + playlist &dest, PlayerControl &pc, + bool secure) +{ + Mutex mutex; + Cond cond; + + InputStream *is; + auto playlist = playlist_open_any(uri, mutex, cond, &is); + if (playlist == nullptr) + return PlaylistResult::NO_SUCH_LIST; + + PlaylistResult 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/playlist/PlaylistQueue.hxx b/src/playlist/PlaylistQueue.hxx new file mode 100644 index 000000000..075191ced --- /dev/null +++ b/src/playlist/PlaylistQueue.hxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 PlayerControl; + +/** + * 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) + */ +PlaylistResult +playlist_load_into_queue(const char *uri, SongEnumerator &e, + unsigned start_index, unsigned end_index, + playlist &dest, PlayerControl &pc, + bool secure); + +/** + * Opens a playlist with a playlist plugin and append to the specified + * play queue. + */ +PlaylistResult +playlist_open_into_queue(const char *uri, + unsigned start_index, unsigned end_index, + playlist &dest, PlayerControl &pc, + bool secure); + +#endif + diff --git a/src/playlist/PlaylistRegistry.cxx b/src/playlist/PlaylistRegistry.cxx new file mode 100644 index 000000000..55064849b --- /dev/null +++ b/src/playlist/PlaylistRegistry.cxx @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "plugins/ExtM3uPlaylistPlugin.hxx" +#include "plugins/M3uPlaylistPlugin.hxx" +#include "plugins/XspfPlaylistPlugin.hxx" +#include "plugins/DespotifyPlaylistPlugin.hxx" +#include "plugins/SoundCloudPlaylistPlugin.hxx" +#include "plugins/PlsPlaylistPlugin.hxx" +#include "plugins/AsxPlaylistPlugin.hxx" +#include "plugins/RssPlaylistPlugin.hxx" +#include "plugins/CuePlaylistPlugin.hxx" +#include "plugins/EmbeddedCuePlaylistPlugin.hxx" +#include "input/InputStream.hxx" +#include "util/UriUtil.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "util/Macros.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigData.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> + +const struct playlist_plugin *const playlist_plugins[] = { + &extm3u_playlist_plugin, + &m3u_playlist_plugin, + &pls_playlist_plugin, +#ifdef HAVE_EXPAT + &xspf_playlist_plugin, + &asx_playlist_plugin, + &rss_playlist_plugin, +#endif +#ifdef ENABLE_DESPOTIFY + &despotify_playlist_plugin, +#endif +#ifdef ENABLE_SOUNDCLOUD + &soundcloud_playlist_plugin, +#endif + &cue_playlist_plugin, + &embcue_playlist_plugin, + nullptr +}; + +static constexpr unsigned n_playlist_plugins = + ARRAY_SIZE(playlist_plugins) - 1; + +/** which plugins have been initialized successfully? */ +static bool playlist_plugins_enabled[n_playlist_plugins]; + +#define playlist_plugins_for_each_enabled(plugin) \ + playlist_plugins_for_each(plugin) \ + if (playlist_plugins_enabled[playlist_plugin_iterator - playlist_plugins]) + +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 = + config_find_block(CONF_PLAYLIST_PLUGIN, "name", + 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) +{ + SongEnumerator *playlist = nullptr; + + assert(uri != nullptr); + + const auto scheme = uri_get_scheme(uri); + if (scheme.empty()) + 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.c_str())) { + playlist = playlist_plugin_open_uri(plugin, uri, + mutex, cond); + if (playlist != nullptr) + break; + + tried[i] = true; + } + } + + 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[n_playlist_plugins]; + + 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(InputStream &is, const char *mime) +{ + 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.Rewind(IgnoreError()); + + auto playlist = playlist_plugin_open_stream(plugin, + is); + if (playlist != nullptr) + return playlist; + } + } + + return nullptr; +} + +static SongEnumerator * +playlist_list_open_stream_mime(InputStream &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*/ + const std::string mime(full_mime, semicolon); + return playlist_list_open_stream_mime2(is, mime.c_str()); +} + +static SongEnumerator * +playlist_list_open_stream_suffix(InputStream &is, const char *suffix) +{ + 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.Rewind(IgnoreError()); + + auto playlist = playlist_plugin_open_stream(plugin, is); + if (playlist != nullptr) + return playlist; + } + } + + return nullptr; +} + +SongEnumerator * +playlist_list_open_stream(InputStream &is, const char *uri) +{ + assert(is.ready); + + const char *const mime = is.GetMimeType(); + if (mime != nullptr) { + auto playlist = playlist_list_open_stream_mime(is, mime); + if (playlist != nullptr) + return playlist; + } + + const char *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, + InputStream **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; + InputStream *is = InputStream::OpenReady(path_fs, mutex, cond, error); + if (is == nullptr) { + if (error.IsDefined()) + LogError(error); + + return nullptr; + } + + auto playlist = playlist_list_open_stream_suffix(*is, suffix); + if (playlist != nullptr) + *is_r = is; + else + is->Close(); + + return playlist; +} diff --git a/src/playlist/PlaylistRegistry.hxx b/src/playlist/PlaylistRegistry.hxx new file mode 100644 index 000000000..0079fa68e --- /dev/null +++ b/src/playlist/PlaylistRegistry.hxx @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 + +class Mutex; +class Cond; +class SongEnumerator; +struct InputStream; + +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(InputStream &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, + InputStream **is_r); + +#endif diff --git a/src/playlist/PlaylistSong.cxx b/src/playlist/PlaylistSong.cxx new file mode 100644 index 000000000..69f8762ab --- /dev/null +++ b/src/playlist/PlaylistSong.cxx @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "db/DatabaseSong.hxx" +#include "ls.hxx" +#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" +#include "fs/AllocatedPath.hxx" +#include "fs/Traits.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "DetachedSong.hxx" + +#include <assert.h> +#include <string.h> + +static void +merge_song_metadata(DetachedSong &add, const DetachedSong &base) +{ + { + TagBuilder builder(add.GetTag()); + builder.Complement(base.GetTag()); + add.SetTag(builder.Commit()); + } + + add.SetLastModified(base.GetLastModified()); +} + +static void +apply_song_metadata(DetachedSong &dest, const DetachedSong &src) +{ + if (!src.GetTag().IsDefined() && + src.GetStartMS() == 0 && src.GetEndMS() == 0) + return; + + merge_song_metadata(dest, src); + + if (dest.GetTag().IsDefined() && dest.GetTag().time > 0 && + src.GetStartMS() > 0 && src.GetEndMS() == 0 && + src.GetStartMS() / 1000 < (unsigned)dest.GetTag().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 */ + dest.WritableTag().time = + dest.GetTag().time - src.GetStartMS() / 1000; +} + +static bool +playlist_check_load_song(DetachedSong &song) +{ + const char *const uri = song.GetURI(); + + if (uri_has_scheme(uri)) { + return true; + } else if (PathTraitsUTF8::IsAbsolute(uri)) { + DetachedSong tmp(uri); + if (!tmp.Update()) + return false; + + apply_song_metadata(song, tmp); + return true; + } else { + DetachedSong *tmp = DatabaseDetachSong(uri, IgnoreError()); + if (tmp == nullptr) + return false; + + apply_song_metadata(song, *tmp); + delete tmp; + return true; + } +} + +bool +playlist_check_translate_song(DetachedSong &song, const char *base_uri, + bool secure) +{ + const char *const uri = song.GetURI(); + + if (uri_has_scheme(uri)) + /* valid remote song? */ + return uri_supported_scheme(uri); + + if (base_uri != nullptr && strcmp(base_uri, ".") == 0) + /* PathTraitsUTF8::GetParent() 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 (PathTraitsUTF8::IsAbsolute(uri)) { + /* XXX fs_charset vs utf8? */ + const char *suffix = map_to_relative_path(uri); + assert(suffix != nullptr); + + if (suffix != uri) + song.SetURI(std::string(suffix)); + else if (!secure) + /* local files must be relative to the music + directory when "secure" is enabled */ + return false; + + base_uri = nullptr; + } + + if (base_uri != nullptr) { + song.SetURI(PathTraitsUTF8::Build(base_uri, uri)); + /* repeat the above checks */ + return playlist_check_translate_song(song, nullptr, secure); + } + + return playlist_check_load_song(song); +} diff --git a/src/playlist/PlaylistSong.hxx b/src/playlist/PlaylistSong.hxx new file mode 100644 index 000000000..2a47b28db --- /dev/null +++ b/src/playlist/PlaylistSong.hxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 + +class DetachedSong; + +/** + * Verifies the song, returns false if it is unsafe. Translate the + * song to a song within the database, if it is a local file. + * + * @param secure if true, then local files are only allowed if they + * are relative to base_uri + * @return true on success, false if the song should not be used + */ +bool +playlist_check_translate_song(DetachedSong &song, const char *base_uri, + bool secure); + +#endif diff --git a/src/playlist/PlsPlaylistPlugin.cxx b/src/playlist/PlsPlaylistPlugin.cxx deleted file mode 100644 index 99be3ad35..000000000 --- a/src/playlist/PlsPlaylistPlugin.cxx +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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 *value; - int length; - GError *error = nullptr; - 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 = nullptr; - - /* 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 = nullptr; - } - } - - while (num_entries > 0) { - Song *song; - - char key[64]; - sprintf(key, "File%u", 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); - return; - } - - song = Song::NewRemote(value); - g_free(value); - - sprintf(key, "Title%u", num_entries); - value = g_key_file_get_string(keyfile, "playlist", key, - &error); - if(error == nullptr && value){ - if (song->tag == nullptr) - song->tag = new Tag(); - song->tag->AddItem(TAG_TITLE, value); - } - /* Ignore errors? Most likely value not present */ - if(error) g_error_free(error); - error = nullptr; - g_free(value); - - sprintf(key, "Length%u", num_entries); - length = g_key_file_get_integer(keyfile, "playlist", key, - &error); - if(error == nullptr && length > 0){ - if (song->tag == nullptr) - song->tag = new Tag(); - song->tag->time = length; - } - /* Ignore errors? Most likely value not present */ - if(error) g_error_free(error); - error = nullptr; - - songs.emplace_front(song); - num_entries--; - } - -} - -static SongEnumerator * -pls_open_stream(InputStream &is) -{ - GError *error = nullptr; - 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 nullptr; - } - - 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 nullptr; - } - - 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 nullptr; - } - - std::forward_list<SongPointer> songs; - pls_parser(keyfile, songs); - g_key_file_free(keyfile); - - return new MemorySongEnumerator(std::move(songs)); -} - -static const char *const pls_suffixes[] = { - "pls", - nullptr -}; - -static const char *const pls_mime_types[] = { - "audio/x-scpls", - nullptr -}; - -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 deleted file mode 100644 index 3fafd36d0..000000000 --- a/src/playlist/PlsPlaylistPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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/Print.cxx b/src/playlist/Print.cxx new file mode 100644 index 000000000..dc5aa252c --- /dev/null +++ b/src/playlist/Print.cxx @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Print.hxx" +#include "PlaylistAny.hxx" +#include "PlaylistSong.hxx" +#include "SongEnumerator.hxx" +#include "SongPrint.hxx" +#include "input/InputStream.hxx" +#include "DetachedSong.hxx" +#include "fs/Traits.hxx" +#include "thread/Cond.hxx" + +static void +playlist_provider_print(Client &client, const char *uri, + SongEnumerator &e, bool detail) +{ + const std::string base_uri = uri != nullptr + ? PathTraitsUTF8::GetParent(uri) + : std::string("."); + + DetachedSong *song; + while ((song = e.NextSong()) != nullptr) { + if (playlist_check_translate_song(*song, base_uri.c_str(), + false)) { + if (detail) + song_print_info(client, *song); + else + song_print_uri(client, *song); + } + + delete song; + } +} + +bool +playlist_file_print(Client &client, const char *uri, bool detail) +{ + Mutex mutex; + Cond cond; + + InputStream *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/playlist/Print.hxx b/src/playlist/Print.hxx new file mode 100644 index 000000000..c2fff5475 --- /dev/null +++ b/src/playlist/Print.hxx @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 + +class Client; + +/** + * 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/playlist/RssPlaylistPlugin.cxx b/src/playlist/RssPlaylistPlugin.cxx deleted file mode 100644 index e2a44bfd3..000000000 --- a/src/playlist/RssPlaylistPlugin.cxx +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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/ASCII.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. - */ - TagType 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] != nullptr; ++i) - if (StringEqualsCaseASCII(attribute_names[i], name)) - return attribute_values[i]; - - return nullptr; -} - -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 (StringEqualsCaseASCII(element_name, "item")) { - parser->state = RssParser::ITEM; - parser->song = Song::NewRemote("rss:"); - parser->tag = TAG_NUM_OF_ITEM_TYPES; - } - - break; - - case RssParser::ITEM: - if (StringEqualsCaseASCII(element_name, "enclosure")) { - const gchar *href = get_attribute(attribute_names, - attribute_values, - "url"); - if (href != nullptr) { - /* 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 != nullptr) { - song->tag = parser->song->tag; - parser->song->tag = nullptr; - parser->song->Free(); - } - - parser->song = song; - } - } else if (StringEqualsCaseASCII(element_name, "title")) - parser->tag = TAG_TITLE; - else if (StringEqualsCaseASCII(element_name, "itunes:author")) - 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 (StringEqualsCaseASCII(element_name, "item")) { - 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 == nullptr) - 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(InputStream &is) -{ - RssParser parser; - GMarkupParseContext *context; - char buffer[1024]; - size_t nbytes; - bool success; - Error error2; - GError *error = nullptr; - - /* 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 nullptr; - } - - 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 nullptr; - } - } - - 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 nullptr; - } - - 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", - nullptr -}; - -static const char *const rss_mime_types[] = { - "application/rss+xml", - "text/xml", - nullptr -}; - -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 deleted file mode 100644 index f49f7e9cf..000000000 --- a/src/playlist/RssPlaylistPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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/SongEnumerator.hxx b/src/playlist/SongEnumerator.hxx new file mode 100644 index 000000000..75295add1 --- /dev/null +++ b/src/playlist/SongEnumerator.hxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 + +class DetachedSong; + +/** + * 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 DetachedSong *NextSong() = 0; +}; + +#endif diff --git a/src/playlist/SoundCloudPlaylistPlugin.cxx b/src/playlist/SoundCloudPlaylistPlugin.cxx deleted file mode 100644 index f6797b14d..000000000 --- a/src/playlist/SoundCloudPlaylistPlugin.cxx +++ /dev/null @@ -1,421 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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> - -#include <string.h> - -static struct { - std::string apikey; -} soundcloud_config; - -static constexpr Domain soundcloud_domain("soundcloud"); - -static bool -soundcloud_init(const config_param ¶m) -{ - soundcloud_config.apikey = param.GetBlockValue("apikey", ""); - if (soundcloud_config.apikey.empty()) { - LogDebug(soundcloud_domain, - "disabling the soundcloud playlist plugin " - "because API key is not set"); - return false; - } - - return true; -} - -/** - * 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, nullptr); - } else { - /* assume it's just a path on soundcloud.com */ - u = g_strconcat("http://soundcloud.com/", uri, nullptr); - } - - ru = g_strconcat("http://api.soundcloud.com/resolve.json?url=", - u, "&client_id=", - soundcloud_config.apikey.c_str(), nullptr); - 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", - nullptr, -}; - -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 != nullptr) - g_free(data->title); - data->title = g_strndup(s, stringlen); - break; - case Stream_URL: - if (data->stream_url != nullptr) - 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 (memcmp((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.c_str(), nullptr); - s = Song::NewRemote(u); - g_free(u); - - Tag *t = new Tag(); - t->time = data->duration / 1000; - if (data->title != nullptr) - t->AddItem(TAG_NAME, data->title); - s->tag = t; - - data->songs.emplace_front(s); - - return 1; -} - -static yajl_callbacks parse_callbacks = { - nullptr, - nullptr, - handle_integer, - nullptr, - nullptr, - handle_string, - handle_start_map, - handle_mapkey, - handle_end_map, - nullptr, - nullptr, -}; - -/** - * 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; - InputStream *input_stream = InputStream::Open(url, mutex, cond, - error); - if (input_stream == nullptr) { - 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 nullptr; - } - - char *u = nullptr; - if (strcmp(arg, "track") == 0) { - u = g_strconcat("http://api.soundcloud.com/tracks/", - rest, ".json?client_id=", - soundcloud_config.apikey.c_str(), nullptr); - } else if (strcmp(arg, "playlist") == 0) { - u = g_strconcat("http://api.soundcloud.com/playlists/", - rest, ".json?client_id=", - soundcloud_config.apikey.c_str(), nullptr); - } 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 == nullptr) { - LogWarning(soundcloud_domain, "unknown soundcloud URI"); - return nullptr; - } - - yajl_handle hand; - struct parse_data data; - - data.got_url = 0; - data.title = nullptr; - data.stream_url = nullptr; -#ifdef HAVE_YAJL1 - hand = yajl_alloc(&parse_callbacks, nullptr, nullptr, (void *) &data); -#else - hand = yajl_alloc(&parse_callbacks, nullptr, (void *) &data); -#endif - - int ret = soundcloud_parse_json(u, hand, mutex, cond); - - g_free(u); - yajl_free(hand); - if (data.title != nullptr) - g_free(data.title); - if (data.stream_url != nullptr) - g_free(data.stream_url); - - if (ret == -1) - return nullptr; - - data.songs.reverse(); - return new MemorySongEnumerator(std::move(data.songs)); -} - -static const char *const soundcloud_schemes[] = { - "soundcloud", - nullptr -}; - -const struct playlist_plugin soundcloud_playlist_plugin = { - "soundcloud", - - soundcloud_init, - nullptr, - soundcloud_open_uri, - nullptr, - - soundcloud_schemes, - nullptr, - nullptr, -}; - - diff --git a/src/playlist/SoundCloudPlaylistPlugin.hxx b/src/playlist/SoundCloudPlaylistPlugin.hxx deleted file mode 100644 index 7c121328c..000000000 --- a/src/playlist/SoundCloudPlaylistPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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 deleted file mode 100644 index dcfab5a80..000000000 --- a/src/playlist/XspfPlaylistPlugin.cxx +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this 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. - */ - TagType 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 = nullptr; - 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 != nullptr) - 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 != nullptr && - parser->tag != TAG_NUM_OF_ITEM_TYPES) { - if (parser->song->tag == nullptr) - parser->song->tag = new Tag(); - parser->song->tag->AddItem(parser->tag, text, text_len); - } - - break; - - case XspfParser::LOCATION: - if (parser->song == nullptr) { - 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 != nullptr) - parser->song->Free(); -} - -/* - * The playlist object - * - */ - -static SongEnumerator * -xspf_open_stream(InputStream &is) -{ - XspfParser parser; - GMarkupParseContext *context; - char buffer[1024]; - size_t nbytes; - bool success; - Error error2; - GError *error = nullptr; - - /* 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 nullptr; - } - - 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 nullptr; - } - } - - 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 nullptr; - } - - 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", - nullptr -}; - -static const char *const xspf_mime_types[] = { - "application/xspf+xml", - nullptr -}; - -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 deleted file mode 100644 index fc9bbd2c6..000000000 --- a/src/playlist/XspfPlaylistPlugin.hxx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * 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/plugins/AsxPlaylistPlugin.cxx b/src/playlist/plugins/AsxPlaylistPlugin.cxx new file mode 100644 index 000000000..3185a8144 --- /dev/null +++ b/src/playlist/plugins/AsxPlaylistPlugin.cxx @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "tag/TagBuilder.hxx" +#include "util/ASCII.hxx" +#include "util/Error.hxx" +#include "lib/expat/ExpatParser.hxx" +#include "Log.hxx" + +/** + * 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<DetachedSong> 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. + */ + TagType tag_type; + + /** + * The current song URI. It is set by the "ref" element. + */ + std::string location; + + TagBuilder tag_builder; + + AsxParser() + :state(ROOT) {} + +}; + +static void XMLCALL +asx_start_element(void *user_data, const XML_Char *element_name, + const XML_Char **atts) +{ + AsxParser *parser = (AsxParser *)user_data; + + switch (parser->state) { + case AsxParser::ROOT: + if (StringEqualsCaseASCII(element_name, "entry")) { + parser->state = AsxParser::ENTRY; + parser->location.clear(); + parser->tag_type = TAG_NUM_OF_ITEM_TYPES; + } + + break; + + case AsxParser::ENTRY: + if (StringEqualsCaseASCII(element_name, "ref")) { + const char *href = + ExpatParser::GetAttributeCase(atts, "href"); + if (href != nullptr) + parser->location = href; + } else if (StringEqualsCaseASCII(element_name, "author")) + /* is that correct? or should it be COMPOSER + or PERFORMER? */ + parser->tag_type = TAG_ARTIST; + else if (StringEqualsCaseASCII(element_name, "title")) + parser->tag_type = TAG_TITLE; + + break; + } +} + +static void XMLCALL +asx_end_element(void *user_data, const XML_Char *element_name) +{ + AsxParser *parser = (AsxParser *)user_data; + + switch (parser->state) { + case AsxParser::ROOT: + break; + + case AsxParser::ENTRY: + if (StringEqualsCaseASCII(element_name, "entry")) { + if (!parser->location.empty()) + parser->songs.emplace_front(std::move(parser->location), + parser->tag_builder.Commit()); + + parser->state = AsxParser::ROOT; + } else + parser->tag_type = TAG_NUM_OF_ITEM_TYPES; + + break; + } +} + +static void XMLCALL +asx_char_data(void *user_data, const XML_Char *s, int len) +{ + AsxParser *parser = (AsxParser *)user_data; + + switch (parser->state) { + case AsxParser::ROOT: + break; + + case AsxParser::ENTRY: + if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES) + parser->tag_builder.AddItem(parser->tag_type, s, len); + + break; + } +} + +/* + * The playlist object + * + */ + +static SongEnumerator * +asx_open_stream(InputStream &is) +{ + AsxParser parser; + + { + ExpatParser expat(&parser); + expat.SetElementHandler(asx_start_element, asx_end_element); + expat.SetCharacterDataHandler(asx_char_data); + + Error error; + if (!expat.Parse(is, error)) { + LogError(error); + return nullptr; + } + } + + parser.songs.reverse(); + return new MemorySongEnumerator(std::move(parser.songs)); +} + +static const char *const asx_suffixes[] = { + "asx", + nullptr +}; + +static const char *const asx_mime_types[] = { + "video/x-ms-asf", + nullptr +}; + +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/plugins/AsxPlaylistPlugin.hxx b/src/playlist/plugins/AsxPlaylistPlugin.hxx new file mode 100644 index 000000000..63371be0f --- /dev/null +++ b/src/playlist/plugins/AsxPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/CuePlaylistPlugin.cxx b/src/playlist/plugins/CuePlaylistPlugin.cxx new file mode 100644 index 000000000..d6be4eb83 --- /dev/null +++ b/src/playlist/plugins/CuePlaylistPlugin.cxx @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "cue/CueParser.hxx" +#include "input/TextInputStream.hxx" + +#include <string> + +class CuePlaylist final : public SongEnumerator { + InputStream &is; + TextInputStream tis; + CueParser parser; + + public: + CuePlaylist(InputStream &_is) + :is(_is), tis(is) { + } + + virtual DetachedSong *NextSong() override; +}; + +static SongEnumerator * +cue_playlist_open_stream(InputStream &is) +{ + return new CuePlaylist(is); +} + +DetachedSong * +CuePlaylist::NextSong() +{ + DetachedSong *song = parser.Get(); + if (song != nullptr) + return song; + + std::string line; + while (tis.ReadLine(line)) { + parser.Feed(line.c_str()); + song = parser.Get(); + if (song != nullptr) + return song; + } + + parser.Finish(); + return parser.Get(); +} + +static const char *const cue_playlist_suffixes[] = { + "cue", + nullptr +}; + +static const char *const cue_playlist_mime_types[] = { + "application/x-cue", + nullptr +}; + +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/plugins/CuePlaylistPlugin.hxx b/src/playlist/plugins/CuePlaylistPlugin.hxx new file mode 100644 index 000000000..4d833bfc2 --- /dev/null +++ b/src/playlist/plugins/CuePlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/DespotifyPlaylistPlugin.cxx b/src/playlist/plugins/DespotifyPlaylistPlugin.cxx new file mode 100644 index 000000000..0d18e8b25 --- /dev/null +++ b/src/playlist/plugins/DespotifyPlaylistPlugin.cxx @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "lib/despotify/DespotifyUtils.hxx" +#include "../PlaylistPlugin.hxx" +#include "../MemorySongEnumerator.hxx" +#include "tag/Tag.hxx" +#include "DetachedSong.hxx" +#include "Log.hxx" + +extern "C" { +#include <despotify.h> +} + +#include <string.h> +#include <stdlib.h> + +static void +add_song(std::forward_list<DetachedSong> &songs, ds_track &track) +{ + const char *dsp_scheme = despotify_playlist_plugin.schemes[0]; + 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; + } + + songs.emplace_front(uri, mpd_despotify_tag_from_track(track)); +} + +static bool +parse_track(struct despotify_session *session, + std::forward_list<DetachedSong> &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<DetachedSong> &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<DetachedSong> 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/plugins/DespotifyPlaylistPlugin.hxx b/src/playlist/plugins/DespotifyPlaylistPlugin.hxx new file mode 100644 index 000000000..6acfd40f4 --- /dev/null +++ b/src/playlist/plugins/DespotifyPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/EmbeddedCuePlaylistPlugin.cxx b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx new file mode 100644 index 000000000..53f3feda0 --- /dev/null +++ b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.cxx @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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/TagHandler.hxx" +#include "tag/TagId3.hxx" +#include "tag/ApeTag.hxx" +#include "DetachedSong.hxx" +#include "TagFile.hxx" +#include "cue/CueParser.hxx" +#include "fs/Traits.hxx" +#include "fs/AllocatedPath.hxx" +#include "util/ASCII.hxx" + +#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. + */ + std::string filename; + + /** + * The value of the file's "CUESHEET" tag. + */ + std::string cuesheet; + + /** + * The offset of the next line within "cuesheet". + */ + char *next; + + CueParser *parser; + +public: + EmbeddedCuePlaylist() + :parser(nullptr) { + } + + virtual ~EmbeddedCuePlaylist() { + delete parser; + } + + virtual DetachedSong *NextSong() override; +}; + +static void +embcue_tag_pair(const char *name, const char *value, void *ctx) +{ + EmbeddedCuePlaylist *playlist = (EmbeddedCuePlaylist *)ctx; + + if (playlist->cuesheet.empty() && + StringEqualsCaseASCII(name, "cuesheet")) + playlist->cuesheet = 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 (!PathTraitsUTF8::IsAbsolute(uri)) + /* only local files supported */ + return nullptr; + + const auto path_fs = AllocatedPath::FromUTF8(uri); + if (path_fs.IsNull()) + return nullptr; + + const auto playlist = new EmbeddedCuePlaylist(); + + tag_file_scan(path_fs, embcue_tag_handler, playlist); + if (playlist->cuesheet.empty()) { + tag_ape_scan2(path_fs, &embcue_tag_handler, playlist); + if (playlist->cuesheet.empty()) + tag_id3_scan(path_fs, &embcue_tag_handler, playlist); + } + + if (playlist->cuesheet.empty()) { + /* no "CUESHEET" tag found */ + delete playlist; + return nullptr; + } + + playlist->filename = PathTraitsUTF8::GetBase(uri); + + playlist->next = &playlist->cuesheet[0]; + playlist->parser = new CueParser(); + + return playlist; +} + +DetachedSong * +EmbeddedCuePlaylist::NextSong() +{ + DetachedSong *song = parser->Get(); + if (song != nullptr) + return song; + + while (*next != 0) { + const char *line = next; + char *eol = strpbrk(next, "\r\n"); + if (eol != nullptr) { + /* 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 != nullptr) { + song->SetURI(filename); + return song; + } + } + + parser->Finish(); + song = parser->Get(); + if (song != nullptr) + song->SetURI(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", + nullptr +}; + +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/plugins/EmbeddedCuePlaylistPlugin.hxx b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx new file mode 100644 index 000000000..5eedf3f13 --- /dev/null +++ b/src/playlist/plugins/EmbeddedCuePlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/ExtM3uPlaylistPlugin.cxx b/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx new file mode 100644 index 000000000..a337bce4c --- /dev/null +++ b/src/playlist/plugins/ExtM3uPlaylistPlugin.cxx @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "DetachedSong.hxx" +#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" +#include "util/StringUtil.hxx" +#include "input/TextInputStream.hxx" + +#include <string.h> +#include <stdlib.h> + +class ExtM3uPlaylist final : public SongEnumerator { + TextInputStream tis; + +public: + ExtM3uPlaylist(InputStream &is) + :tis(is) { + } + + bool CheckFirstLine() { + std::string line; + return tis.ReadLine(line) && + strcmp(line.c_str(), "#EXTM3U") == 0; + } + + virtual DetachedSong *NextSong() override; +}; + +static SongEnumerator * +extm3u_open_stream(InputStream &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; + + duration = strtol(line, &endptr, 10); + if (endptr[0] != ',') + /* malformed line */ + return Tag(); + + if (duration < 0) + /* 0 means unknown duration */ + duration = 0; + + name = strchug_fast(endptr + 1); + if (*name == 0 && duration == 0) + /* no information available; don't allocate a tag + object */ + return Tag(); + + TagBuilder tag; + tag.SetTime(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.Commit(); +} + +DetachedSong * +ExtM3uPlaylist::NextSong() +{ + Tag tag; + std::string line; + const char *line_s; + + do { + if (!tis.ReadLine(line)) + return NULL; + + line_s = line.c_str(); + + if (StringStartsWith(line_s, "#EXTINF:")) { + tag = extm3u_parse_tag(line_s + 8); + continue; + } + + line_s = strchug_fast(line_s); + } while (line_s[0] == '#' || *line_s == 0); + + return new DetachedSong(line_s, std::move(tag)); +} + +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/plugins/ExtM3uPlaylistPlugin.hxx b/src/playlist/plugins/ExtM3uPlaylistPlugin.hxx new file mode 100644 index 000000000..5743ded43 --- /dev/null +++ b/src/playlist/plugins/ExtM3uPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/M3uPlaylistPlugin.cxx b/src/playlist/plugins/M3uPlaylistPlugin.cxx new file mode 100644 index 000000000..f892f2010 --- /dev/null +++ b/src/playlist/plugins/M3uPlaylistPlugin.cxx @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "DetachedSong.hxx" +#include "util/StringUtil.hxx" +#include "input/TextInputStream.hxx" + +class M3uPlaylist final : public SongEnumerator { + TextInputStream tis; + +public: + M3uPlaylist(InputStream &is) + :tis(is) { + } + + virtual DetachedSong *NextSong() override; +}; + +static SongEnumerator * +m3u_open_stream(InputStream &is) +{ + return new M3uPlaylist(is); +} + +DetachedSong * +M3uPlaylist::NextSong() +{ + std::string line; + const char *line_s; + + do { + if (!tis.ReadLine(line)) + return nullptr; + + line_s = line.c_str(); + line_s = strchug_fast(line_s); + } while (line_s[0] == '#' || *line_s == 0); + + return new DetachedSong(line_s); +} + +static const char *const m3u_suffixes[] = { + "m3u", + nullptr +}; + +static const char *const m3u_mime_types[] = { + "audio/x-mpegurl", + nullptr +}; + +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/plugins/M3uPlaylistPlugin.hxx b/src/playlist/plugins/M3uPlaylistPlugin.hxx new file mode 100644 index 000000000..f1ad14069 --- /dev/null +++ b/src/playlist/plugins/M3uPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/PlsPlaylistPlugin.cxx b/src/playlist/plugins/PlsPlaylistPlugin.cxx new file mode 100644 index 000000000..979f59a96 --- /dev/null +++ b/src/playlist/plugins/PlsPlaylistPlugin.cxx @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "input/InputStream.hxx" +#include "DetachedSong.hxx" +#include "tag/TagBuilder.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> + +#include <string> + +#include <stdio.h> + +static constexpr Domain pls_domain("pls"); + +static void +pls_parser(GKeyFile *keyfile, std::forward_list<DetachedSong> &songs) +{ + gchar *value; + GError *error = nullptr; + 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 = nullptr; + + /* 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 = nullptr; + } + } + + for (; num_entries > 0; --num_entries) { + char key[64]; + sprintf(key, "File%u", num_entries); + char *uri = 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); + return; + } + + TagBuilder tag; + + sprintf(key, "Title%u", num_entries); + value = g_key_file_get_string(keyfile, "playlist", key, + nullptr); + if (value != nullptr) + tag.AddItem(TAG_TITLE, value); + + g_free(value); + + sprintf(key, "Length%u", num_entries); + int length = g_key_file_get_integer(keyfile, "playlist", key, + nullptr); + if (length > 0) + tag.SetTime(length); + + songs.emplace_front(uri, tag.Commit()); + g_free(uri); + } + +} + +static SongEnumerator * +pls_open_stream(InputStream &is) +{ + GError *error = nullptr; + Error error2; + + std::string kf_data; + + do { + char buffer[1024]; + size_t nbytes = is.LockRead(buffer, sizeof(buffer), error2); + if (nbytes == 0) { + if (error2.IsDefined()) { + LogError(error2); + return nullptr; + } + + 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 nullptr; + } + + GKeyFile *keyfile = g_key_file_new(); + if (!g_key_file_load_from_data(keyfile, + kf_data.data(), kf_data.length(), + G_KEY_FILE_NONE, &error)) { + FormatError(pls_domain, + "KeyFile parser failed: %s", error->message); + g_error_free(error); + g_key_file_free(keyfile); + return nullptr; + } + + std::forward_list<DetachedSong> songs; + pls_parser(keyfile, songs); + g_key_file_free(keyfile); + + return new MemorySongEnumerator(std::move(songs)); +} + +static const char *const pls_suffixes[] = { + "pls", + nullptr +}; + +static const char *const pls_mime_types[] = { + "audio/x-scpls", + nullptr +}; + +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/plugins/PlsPlaylistPlugin.hxx b/src/playlist/plugins/PlsPlaylistPlugin.hxx new file mode 100644 index 000000000..1a3f33873 --- /dev/null +++ b/src/playlist/plugins/PlsPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/RssPlaylistPlugin.cxx b/src/playlist/plugins/RssPlaylistPlugin.cxx new file mode 100644 index 000000000..6f9aad54b --- /dev/null +++ b/src/playlist/plugins/RssPlaylistPlugin.cxx @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "tag/TagBuilder.hxx" +#include "util/ASCII.hxx" +#include "util/Error.hxx" +#include "lib/expat/ExpatParser.hxx" +#include "Log.hxx" + +/** + * 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<DetachedSong> 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. + */ + TagType tag_type; + + /** + * The current song URI. It is set by the "enclosure" + * element. + */ + std::string location; + + TagBuilder tag_builder; + + RssParser() + :state(ROOT) {} +}; + +static void XMLCALL +rss_start_element(void *user_data, const XML_Char *element_name, + const XML_Char **atts) +{ + RssParser *parser = (RssParser *)user_data; + + switch (parser->state) { + case RssParser::ROOT: + if (StringEqualsCaseASCII(element_name, "item")) { + parser->state = RssParser::ITEM; + parser->location.clear(); + parser->tag_type = TAG_NUM_OF_ITEM_TYPES; + } + + break; + + case RssParser::ITEM: + if (StringEqualsCaseASCII(element_name, "enclosure")) { + const char *href = + ExpatParser::GetAttributeCase(atts, "url"); + if (href != nullptr) + parser->location = href; + } else if (StringEqualsCaseASCII(element_name, "title")) + parser->tag_type = TAG_TITLE; + else if (StringEqualsCaseASCII(element_name, "itunes:author")) + parser->tag_type = TAG_ARTIST; + + break; + } +} + +static void XMLCALL +rss_end_element(void *user_data, const XML_Char *element_name) +{ + RssParser *parser = (RssParser *)user_data; + + switch (parser->state) { + case RssParser::ROOT: + break; + + case RssParser::ITEM: + if (StringEqualsCaseASCII(element_name, "item")) { + if (!parser->location.empty()) + parser->songs.emplace_front(std::move(parser->location), + parser->tag_builder.Commit()); + + parser->state = RssParser::ROOT; + } else + parser->tag_type = TAG_NUM_OF_ITEM_TYPES; + + break; + } +} + +static void XMLCALL +rss_char_data(void *user_data, const XML_Char *s, int len) +{ + RssParser *parser = (RssParser *)user_data; + + switch (parser->state) { + case RssParser::ROOT: + break; + + case RssParser::ITEM: + if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES) + parser->tag_builder.AddItem(parser->tag_type, s, len); + + break; + } +} + +/* + * The playlist object + * + */ + +static SongEnumerator * +rss_open_stream(InputStream &is) +{ + RssParser parser; + + { + ExpatParser expat(&parser); + expat.SetElementHandler(rss_start_element, rss_end_element); + expat.SetCharacterDataHandler(rss_char_data); + + Error error; + if (!expat.Parse(is, error)) { + LogError(error); + return nullptr; + } + } + + parser.songs.reverse(); + return new MemorySongEnumerator(std::move(parser.songs)); +} + +static const char *const rss_suffixes[] = { + "rss", + nullptr +}; + +static const char *const rss_mime_types[] = { + "application/rss+xml", + "text/xml", + nullptr +}; + +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/plugins/RssPlaylistPlugin.hxx b/src/playlist/plugins/RssPlaylistPlugin.hxx new file mode 100644 index 000000000..a00a5a898 --- /dev/null +++ b/src/playlist/plugins/RssPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/SoundCloudPlaylistPlugin.cxx b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx new file mode 100644 index 000000000..8f378ac9d --- /dev/null +++ b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "config/ConfigData.hxx" +#include "input/InputStream.hxx" +#include "tag/TagBuilder.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#include <glib.h> +#include <yajl/yajl_parse.h> + +#include <string> + +#include <string.h> + +static struct { + std::string apikey; +} soundcloud_config; + +static constexpr Domain soundcloud_domain("soundcloud"); + +static bool +soundcloud_init(const config_param ¶m) +{ + // APIKEY for MPD application, registered under DarkFox' account. + soundcloud_config.apikey = param.GetBlockValue("apikey", "a25e51780f7f86af0afa91f241d091f8"); + if (soundcloud_config.apikey.empty()) { + LogDebug(soundcloud_domain, + "disabling the soundcloud playlist plugin " + "because API key is not set"); + return false; + } + + return true; +} + +/** + * 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 (StringStartsWith(uri, "https://")) { + u = g_strdup(uri); + } else if (StringStartsWith(uri, "soundcloud.com")) { + u = g_strconcat("https://", uri, nullptr); + } else { + /* assume it's just a path on soundcloud.com */ + u = g_strconcat("https://soundcloud.com/", uri, nullptr); + } + + ru = g_strconcat("https://api.soundcloud.com/resolve.json?url=", + u, "&client_id=", + soundcloud_config.apikey.c_str(), nullptr); + 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", + nullptr, +}; + +struct parse_data { + int key; + char* stream_url; + long duration; + char* title; + int got_url; /* nesting level of last stream_url */ + + std::forward_list<DetachedSong> 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: + g_free(data->title); + data->title = g_strndup(s, stringlen); + break; + case Stream_URL: + 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 (memcmp((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; + + char *u = g_strconcat(data->stream_url, "?client_id=", + soundcloud_config.apikey.c_str(), nullptr); + + TagBuilder tag; + tag.SetTime(data->duration / 1000); + if (data->title != nullptr) + tag.AddItem(TAG_NAME, data->title); + + data->songs.emplace_front(u, tag.Commit()); + g_free(u); + + return 1; +} + +static yajl_callbacks parse_callbacks = { + nullptr, + nullptr, + handle_integer, + nullptr, + nullptr, + handle_string, + handle_start_map, + handle_mapkey, + handle_end_map, + nullptr, + nullptr, +}; + +/** + * 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) +{ + Error error; + InputStream *input_stream = InputStream::OpenReady(url, mutex, cond, + error); + if (input_stream == nullptr) { + if (error.IsDefined()) + LogError(error); + return -1; + } + + mutex.lock(); + + yajl_status stat; + int done = 0; + + while (!done) { + char buffer[4096]; + unsigned char *ubuffer = (unsigned char *)buffer; + 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) +{ + assert(memcmp(uri, "soundcloud://", 13) == 0); + uri += 13; + + char *u = nullptr; + if (memcmp(uri, "track/", 6) == 0) { + const char *rest = uri + 6; + u = g_strconcat("https://api.soundcloud.com/tracks/", + rest, ".json?client_id=", + soundcloud_config.apikey.c_str(), nullptr); + } else if (memcmp(uri, "playlist/", 9) == 0) { + const char *rest = uri + 9; + u = g_strconcat("https://api.soundcloud.com/playlists/", + rest, ".json?client_id=", + soundcloud_config.apikey.c_str(), nullptr); + } else if (memcmp(uri, "user/", 5) == 0) { + const char *rest = uri + 5; + u = g_strconcat("https://api.soundcloud.com/users/", + rest, "/tracks.json?client_id=", + soundcloud_config.apikey.c_str(), nullptr); + } else if (memcmp(uri, "search/", 7) == 0) { + const char *rest = uri + 7; + u = g_strconcat("https://api.soundcloud.com/tracks.json?q=", + rest, "&client_id=", + soundcloud_config.apikey.c_str(), nullptr); + } else if (memcmp(uri, "url/", 4) == 0) { + const char *rest = uri + 4; + /* Translate to soundcloud resolver call. libcurl will automatically + follow the redirect to the right resource. */ + u = soundcloud_resolve(rest); + } + + if (u == nullptr) { + LogWarning(soundcloud_domain, "unknown soundcloud URI"); + return nullptr; + } + + struct parse_data data; + data.got_url = 0; + data.title = nullptr; + data.stream_url = nullptr; +#ifdef HAVE_YAJL1 + yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, nullptr, + &data); +#else + yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, &data); +#endif + + int ret = soundcloud_parse_json(u, hand, mutex, cond); + + g_free(u); + yajl_free(hand); + g_free(data.title); + g_free(data.stream_url); + + if (ret == -1) + return nullptr; + + data.songs.reverse(); + return new MemorySongEnumerator(std::move(data.songs)); +} + +static const char *const soundcloud_schemes[] = { + "soundcloud", + nullptr +}; + +const struct playlist_plugin soundcloud_playlist_plugin = { + "soundcloud", + + soundcloud_init, + nullptr, + soundcloud_open_uri, + nullptr, + + soundcloud_schemes, + nullptr, + nullptr, +}; + + diff --git a/src/playlist/plugins/SoundCloudPlaylistPlugin.hxx b/src/playlist/plugins/SoundCloudPlaylistPlugin.hxx new file mode 100644 index 000000000..b355b477a --- /dev/null +++ b/src/playlist/plugins/SoundCloudPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/plugins/XspfPlaylistPlugin.cxx b/src/playlist/plugins/XspfPlaylistPlugin.cxx new file mode 100644 index 000000000..5b6010b53 --- /dev/null +++ b/src/playlist/plugins/XspfPlaylistPlugin.cxx @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "DetachedSong.hxx" +#include "input/InputStream.hxx" +#include "tag/TagBuilder.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "lib/expat/ExpatParser.hxx" +#include "Log.hxx" + +#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<DetachedSong> 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. + */ + TagType tag_type; + + /** + * The current song URI. It is set by the "location" element. + */ + std::string location; + + TagBuilder tag_builder; + + XspfParser() + :state(ROOT) {} +}; + +static void XMLCALL +xspf_start_element(void *user_data, const XML_Char *element_name, + gcc_unused const XML_Char **atts) +{ + 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->location.clear(); + parser->tag_type = 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_type = TAG_TITLE; + else if (strcmp(element_name, "creator") == 0) + /* TAG_COMPOSER would be more correct + according to the XSPF spec */ + parser->tag_type = TAG_ARTIST; + else if (strcmp(element_name, "annotation") == 0) + parser->tag_type = TAG_COMMENT; + else if (strcmp(element_name, "album") == 0) + parser->tag_type = TAG_ALBUM; + else if (strcmp(element_name, "trackNum") == 0) + parser->tag_type = TAG_TRACK; + + break; + + case XspfParser::LOCATION: + break; + } +} + +static void XMLCALL +xspf_end_element(void *user_data, const XML_Char *element_name) +{ + 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->location.empty()) + parser->songs.emplace_front(std::move(parser->location), + parser->tag_builder.Commit()); + + parser->state = XspfParser::TRACKLIST; + } else + parser->tag_type = TAG_NUM_OF_ITEM_TYPES; + + break; + + case XspfParser::LOCATION: + parser->state = XspfParser::TRACK; + break; + } +} + +static void XMLCALL +xspf_char_data(void *user_data, const XML_Char *s, int len) +{ + XspfParser *parser = (XspfParser *)user_data; + + switch (parser->state) { + case XspfParser::ROOT: + case XspfParser::PLAYLIST: + case XspfParser::TRACKLIST: + break; + + case XspfParser::TRACK: + if (!parser->location.empty() && + parser->tag_type != TAG_NUM_OF_ITEM_TYPES) + parser->tag_builder.AddItem(parser->tag_type, s, len); + + break; + + case XspfParser::LOCATION: + parser->location.assign(s, len); + + break; + } +} + +/* + * The playlist object + * + */ + +static SongEnumerator * +xspf_open_stream(InputStream &is) +{ + XspfParser parser; + + { + ExpatParser expat(&parser); + expat.SetElementHandler(xspf_start_element, xspf_end_element); + expat.SetCharacterDataHandler(xspf_char_data); + + Error error; + if (!expat.Parse(is, error)) { + LogError(error); + return nullptr; + } + } + + parser.songs.reverse(); + return new MemorySongEnumerator(std::move(parser.songs)); +} + +static const char *const xspf_suffixes[] = { + "xspf", + nullptr +}; + +static const char *const xspf_mime_types[] = { + "application/xspf+xml", + nullptr +}; + +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/plugins/XspfPlaylistPlugin.hxx b/src/playlist/plugins/XspfPlaylistPlugin.hxx new file mode 100644 index 000000000..6b08a6be6 --- /dev/null +++ b/src/playlist/plugins/XspfPlaylistPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/poison.h b/src/poison.h index c95b5d005..c112f6e19 100644 --- a/src/poison.h +++ b/src/poison.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/protocol/Ack.cxx b/src/protocol/Ack.cxx index 3b66764bf..56f0f0b5d 100644 --- a/src/protocol/Ack.cxx +++ b/src/protocol/Ack.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/protocol/Ack.hxx b/src/protocol/Ack.hxx index 7d3be2626..e2c4dd9d1 100644 --- a/src/protocol/Ack.hxx +++ b/src/protocol/Ack.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/protocol/ArgParser.cxx b/src/protocol/ArgParser.cxx index b13ea3f4e..d2c40bbd6 100644 --- a/src/protocol/ArgParser.cxx +++ b/src/protocol/ArgParser.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/protocol/ArgParser.hxx b/src/protocol/ArgParser.hxx index ea28de79e..f7f9531d0 100644 --- a/src/protocol/ArgParser.hxx +++ b/src/protocol/ArgParser.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/protocol/Result.cxx b/src/protocol/Result.cxx index 17b553c34..3cc5fc33e 100644 --- a/src/protocol/Result.cxx +++ b/src/protocol/Result.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,7 +19,7 @@ #include "config.h" #include "Result.hxx" -#include "Client.hxx" +#include "client/Client.hxx" #include <assert.h> diff --git a/src/protocol/Result.hxx b/src/protocol/Result.hxx index 0f7339c1c..0ac9d1e6b 100644 --- a/src/protocol/Result.hxx +++ b/src/protocol/Result.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/queue/IdTable.hxx b/src/queue/IdTable.hxx new file mode 100644 index 000000000..8e445243d --- /dev/null +++ b/src/queue/IdTable.hxx @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.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/queue/Queue.cxx b/src/queue/Queue.cxx new file mode 100644 index 000000000..99b545ab1 --- /dev/null +++ b/src/queue/Queue.cxx @@ -0,0 +1,482 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "DetachedSong.hxx" + +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]; + ModifyAtPosition(position); +} + +unsigned +Queue::Append(DetachedSong &&song, uint8_t priority) +{ + assert(!IsFull()); + + const unsigned position = length++; + const unsigned id = id_table.Insert(position); + + auto &item = items[position]; + item.song = new DetachedSong(std::move(song)); + 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); + + delete items[position].song; + + 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]; + + delete item->song; + + id_table.Erase(item->id); + } + + length = 0; +} + +static void +queue_sort_order_by_priority(Queue *queue, unsigned start, unsigned end) +{ + assert(queue != nullptr); + 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/Queue.hxx b/src/queue/Queue.hxx new file mode 100644 index 000000000..016619e65 --- /dev/null +++ b/src/queue/Queue.hxx @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 "Compiler.h" +#include "IdTable.hxx" +#include "util/LazyRandomEngine.hxx" + +#include <algorithm> + +#include <assert.h> +#include <stdint.h> + +class DetachedSong; + +/** + * 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 { + DetachedSong *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; + + explicit Queue(unsigned max_length); + + /** + * Deinitializes a queue object. It does not free the queue + * pointer itself. + */ + ~Queue(); + + Queue(const Queue &) = delete; + Queue &operator=(const Queue &) = 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. + */ + DetachedSong &Get(unsigned position) const { + assert(position < length); + + return *items[position].song; + } + + /** + * Returns the song at the specified order number. + */ + DetachedSong &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". Call + * IncrementVersion() after all modifications have been made. + * number. + */ + void ModifyAtPosition(unsigned position) { + assert(position < length); + + items[position].version = version; + } + + /** + * Marks the specified song as "modified". Call + * IncrementVersion() after all modifications have been made. + * number. + */ + void ModifyAtOrder(unsigned order); + + /** + * 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(DetachedSong &&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/queue/QueuePrint.cxx b/src/queue/QueuePrint.cxx new file mode 100644 index 000000000..831ecafb9 --- /dev/null +++ b/src/queue/QueuePrint.cxx @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "client/Client.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 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 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 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 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 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 Queue &queue, + const SongFilter &filter) +{ + for (unsigned i = 0; i < queue.GetLength(); i++) { + const DetachedSong &song = queue.Get(i); + + if (filter.Match(song)) + queue_print_song_info(client, queue, i); + } +} diff --git a/src/queue/QueuePrint.hxx b/src/queue/QueuePrint.hxx new file mode 100644 index 000000000..1aa876219 --- /dev/null +++ b/src/queue/QueuePrint.hxx @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 Queue &queue, + unsigned start, unsigned end); + +void +queue_print_uris(Client &client, const Queue &queue, + unsigned start, unsigned end); + +void +queue_print_changes_info(Client &client, const Queue &queue, + uint32_t version); + +void +queue_print_changes_position(Client &client, const Queue &queue, + uint32_t version); + +void +queue_find(Client &client, const Queue &queue, + const SongFilter &filter); + +#endif diff --git a/src/queue/QueueSave.cxx b/src/queue/QueueSave.cxx new file mode 100644 index 000000000..11c61aa9a --- /dev/null +++ b/src/queue/QueueSave.cxx @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "DetachedSong.hxx" +#include "SongSave.hxx" +#include "db/DatabaseSong.hxx" +#include "fs/TextFile.hxx" +#include "util/StringUtil.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "fs/Traits.hxx" +#include "Log.hxx" + +#include <stdlib.h> + +#define PRIO_LABEL "Prio: " + +static void +queue_save_database_song(FILE *fp, int idx, const DetachedSong &song) +{ + fprintf(fp, "%i:%s\n", idx, song.GetURI()); +} + +static void +queue_save_full_song(FILE *fp, const DetachedSong &song) +{ + song_save(fp, song); +} + +static void +queue_save_song(FILE *fp, int idx, const DetachedSong &song) +{ + if (song.IsInDatabase()) + queue_save_database_song(fp, idx, song); + else + queue_save_full_song(fp, song); +} + +void +queue_save(FILE *fp, const 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 (StringStartsWith(line, PRIO_LABEL)) { + priority = strtoul(line + sizeof(PRIO_LABEL) - 1, nullptr, 10); + + line = file.ReadLine(); + if (line == nullptr) + return; + } + + DetachedSong *song; + + if (StringStartsWith(line, SONG_BEGIN)) { + const char *uri = line + sizeof(SONG_BEGIN) - 1; + if (!uri_has_scheme(uri) && !PathTraitsUTF8::IsAbsolute(uri)) + return; + + Error error; + song = song_load(file, uri, error); + if (song == nullptr) { + 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 = new DetachedSong(uri); + } else { + song = DatabaseDetachSong(uri, IgnoreError()); + if (song == nullptr) + return; + } + } + + queue.Append(std::move(*song), priority); + delete song; +} diff --git a/src/queue/QueueSave.hxx b/src/queue/QueueSave.hxx new file mode 100644 index 000000000..c9a646369 --- /dev/null +++ b/src/queue/QueueSave.hxx @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 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/sticker/SongSticker.cxx b/src/sticker/SongSticker.cxx new file mode 100644 index 000000000..3431a1702 --- /dev/null +++ b/src/sticker/SongSticker.cxx @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "db/LightSong.hxx" +#include "db/Song.hxx" +#include "db/Directory.hxx" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +std::string +sticker_song_get_value(const LightSong &song, const char *name) +{ + const auto uri = song.GetURI(); + return sticker_load_value("song", uri.c_str(), name); +} + +bool +sticker_song_set_value(const LightSong &song, + const char *name, const char *value) +{ + const auto uri = song.GetURI(); + return sticker_store_value("song", uri.c_str(), name, value); +} + +bool +sticker_song_delete(const LightSong &song) +{ + const auto uri = song.GetURI(); + return sticker_delete("song", uri.c_str()); +} + +bool +sticker_song_delete_value(const LightSong &song, const char *name) +{ + const auto uri = song.GetURI(); + return sticker_delete_value("song", uri.c_str(), name); +} + +struct sticker * +sticker_song_get(const LightSong &song) +{ + const auto uri = song.GetURI(); + return sticker_load("song", uri.c_str()); +} + +struct sticker_song_find_data { + Directory *directory; + const char *base_uri; + size_t base_uri_length; + + void (*func)(const LightSong &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 != nullptr) + data->func(song->Export(), value, data->user_data); +} + +bool +sticker_song_find(Directory &directory, const char *name, + void (*func)(const LightSong &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, "/", nullptr); + else + /* searching in root directory - no trailing slash */ + allocated = nullptr; + + 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/sticker/SongSticker.hxx b/src/sticker/SongSticker.hxx new file mode 100644 index 000000000..2f977bd21 --- /dev/null +++ b/src/sticker/SongSticker.hxx @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 + +#include "Compiler.h" + +#include <string> + +struct LightSong; +struct Directory; +struct sticker; + +/** + * Returns one value from a song's sticker record. The caller must + * free the return value with g_free(). + */ +gcc_pure +std::string +sticker_song_get_value(const LightSong &song, const char *name); + +/** + * Sets a sticker value in the specified song. Overwrites existing + * values. + */ +bool +sticker_song_set_value(const LightSong &song, + const char *name, const char *value); + +/** + * Deletes a sticker from the database. All values are deleted. + */ +bool +sticker_song_delete(const LightSong &song); + +/** + * Deletes a sticker value. Does nothing if the sticker did not + * exist. + */ +bool +sticker_song_delete_value(const LightSong &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 + */ +sticker * +sticker_song_get(const LightSong &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)(const LightSong &song, const char *value, + void *user_data), + void *user_data); + +#endif diff --git a/src/sticker/StickerDatabase.cxx b/src/sticker/StickerDatabase.cxx new file mode 100644 index 000000000..93eaa900d --- /dev/null +++ b/src/sticker/StickerDatabase.cxx @@ -0,0 +1,604 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "util/Macros.hxx" +#include "Log.hxx" + +#include <string> +#include <map> + +#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[ARRAY_SIZE(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, nullptr); + if (ret != SQLITE_OK) { + error.Format(sticker_domain, ret, + "sqlite3_prepare_v2() failed: %s", + sqlite3_errmsg(sticker_db)); + return nullptr; + } + + return stmt; +} + +bool +sticker_global_init(Path path, Error &error) +{ + assert(!path.IsNull()); + + int ret; + + /* 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, + nullptr, nullptr, nullptr); + 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 < ARRAY_SIZE(sticker_sql); ++i) { + assert(sticker_sql[i] != nullptr); + + sticker_stmt[i] = sticker_prepare(sticker_sql[i], error); + if (sticker_stmt[i] == nullptr) + return false; + } + + return true; +} + +void +sticker_global_finish(void) +{ + if (sticker_db == nullptr) + /* not configured */ + return; + + for (unsigned i = 0; i < ARRAY_SIZE(sticker_stmt); ++i) { + assert(sticker_stmt[i] != nullptr); + + sqlite3_finalize(sticker_stmt[i]); + } + + sqlite3_close(sticker_db); +} + +bool +sticker_enabled(void) +{ + return sticker_db != nullptr; +} + +std::string +sticker_load_value(const char *type, const char *uri, const char *name) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_GET]; + int ret; + + assert(sticker_enabled()); + assert(type != nullptr); + assert(uri != nullptr); + assert(name != nullptr); + + if (*name == 0) + return std::string(); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return std::string(); + } + + ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return std::string(); + } + + ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return std::string(); + } + + do { + ret = sqlite3_step(stmt); + } while (ret == SQLITE_BUSY); + + std::string value; + if (ret == SQLITE_ROW) { + /* record found */ + value = (const char*)sqlite3_column_text(stmt, 0); + } else if (ret == SQLITE_DONE) { + /* no record found */ + } else { + /* error */ + LogError(sticker_db, "sqlite3_step() failed"); + } + + 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 != nullptr); + assert(uri != nullptr); + assert(sticker_enabled()); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); + 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 != nullptr); + assert(uri != nullptr); + assert(name != nullptr); + assert(*name != 0); + assert(value != nullptr); + + assert(sticker_enabled()); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, value, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 2, type, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 3, uri, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 4, name, -1, nullptr); + 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 != nullptr); + assert(uri != nullptr); + assert(name != nullptr); + assert(*name != 0); + assert(value != nullptr); + + assert(sticker_enabled()); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 4, value, -1, nullptr); + 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 != nullptr); + assert(uri != nullptr); + assert(name != nullptr); + assert(value != nullptr); + + 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 != nullptr); + assert(uri != nullptr); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); + 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 != nullptr); + assert(uri != nullptr); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr); + 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 sticker &sticker, + void (*func)(const char *name, const char *value, + void *user_data), + void *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 nullptr; + + if (s.table.empty()) + /* don't return empty sticker objects */ + return nullptr; + + 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, + void *user_data), + void *user_data) +{ + sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_FIND]; + int ret; + + assert(type != nullptr); + assert(name != nullptr); + assert(func != nullptr); + assert(sticker_enabled()); + + sqlite3_reset(stmt); + + ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + if (base_uri == nullptr) + base_uri = ""; + + ret = sqlite3_bind_text(stmt, 2, base_uri, -1, nullptr); + if (ret != SQLITE_OK) { + LogError(sticker_db, "sqlite3_bind_text() failed"); + return false; + } + + ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr); + 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/sticker/StickerDatabase.hxx b/src/sticker/StickerDatabase.hxx new file mode 100644 index 000000000..8993489c4 --- /dev/null +++ b/src/sticker/StickerDatabase.hxx @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 + +#include "Compiler.h" + +#include <string> + +class Error; +class Path; +struct sticker; + +/** + * Opens the sticker database. + * + * @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. + */ +gcc_const +bool +sticker_enabled(void); + +/** + * Returns one value from an object's sticker record. Returns an + * empty string if the value doesn't exist. + */ +std::string +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(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 nullptr if none was found + */ +gcc_pure +const char * +sticker_get_value(const 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 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 nullptr on error or if there is no sticker + */ +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 nullptr 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/sticker/StickerPrint.cxx b/src/sticker/StickerPrint.cxx new file mode 100644 index 000000000..a952ff203 --- /dev/null +++ b/src/sticker/StickerPrint.cxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/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 sticker &sticker) +{ + sticker_foreach(sticker, print_sticker_cb, &client); +} diff --git a/src/sticker/StickerPrint.hxx b/src/sticker/StickerPrint.hxx new file mode 100644 index 000000000..39f3dc09e --- /dev/null +++ b/src/sticker/StickerPrint.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 sticker &sticker); + +#endif diff --git a/src/system/Clock.cxx b/src/system/Clock.cxx index 347997a44..9baa0c0ca 100644 --- a/src/system/Clock.cxx +++ b/src/system/Clock.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -31,6 +31,28 @@ #endif unsigned +MonotonicClockS(void) +{ +#ifdef WIN32 + return GetTickCount() / 1000; +#elif defined(__APPLE__) /* OS X does not define CLOCK_MONOTONIC */ + static mach_timebase_info_data_t base; + if (base.denom == 0) + (void)mach_timebase_info(&base); + + return (unsigned)((mach_absolute_time() * base.numer / 1000) + / (1000000 * base.denom)); +#elif defined(CLOCK_MONOTONIC) + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec; +#else + /* we have no monotonic clock, fall back to time() */ + return time(nullptr); +#endif +} + +unsigned MonotonicClockMS(void) { #ifdef WIN32 @@ -96,3 +118,29 @@ MonotonicClockUS(void) #endif } +#ifdef WIN32 + +gcc_const +static unsigned +DeltaFileTimeS(FILETIME a, FILETIME b) +{ + ULARGE_INTEGER a2, b2; + b2.LowPart = b.dwLowDateTime; + b2.HighPart = b.dwHighDateTime; + a2.LowPart = a.dwLowDateTime; + a2.HighPart = a.dwHighDateTime; + return (a2.QuadPart - b2.QuadPart) / 10000000; +} + +unsigned +GetProcessUptimeS() +{ + FILETIME creation_time, exit_time, kernel_time, user_time, now; + GetProcessTimes(GetCurrentProcess(), &creation_time, + &exit_time, &kernel_time, &user_time); + GetSystemTimeAsFileTime(&now); + + return DeltaFileTimeS(now, creation_time); +} + +#endif diff --git a/src/system/Clock.hxx b/src/system/Clock.hxx index 7be1127bf..333a41000 100644 --- a/src/system/Clock.hxx +++ b/src/system/Clock.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,6 +25,13 @@ #include <stdint.h> /** + * Returns the value of a monotonic clock in seconds. + */ +gcc_pure +unsigned +MonotonicClockS(); + +/** * Returns the value of a monotonic clock in milliseconds. */ gcc_pure @@ -38,4 +45,15 @@ gcc_pure uint64_t MonotonicClockUS(); +#ifdef WIN32 + +/** + * Returns the uptime of the current process in seconds. + */ +gcc_pure +unsigned +GetProcessUptimeS(); + +#endif + #endif diff --git a/src/system/EPollFD.cxx b/src/system/EPollFD.cxx index 5721c0194..16787a50b 100644 --- a/src/system/EPollFD.cxx +++ b/src/system/EPollFD.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/system/EPollFD.hxx b/src/system/EPollFD.hxx index 41f7ec377..8b9d7d2ba 100644 --- a/src/system/EPollFD.hxx +++ b/src/system/EPollFD.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,6 +23,7 @@ #include <assert.h> #include <sys/epoll.h> #include <unistd.h> +#include <stdint.h> #include "check.h" diff --git a/src/system/EventFD.cxx b/src/system/EventFD.cxx index 3560bf8c3..9ac4c1d94 100644 --- a/src/system/EventFD.cxx +++ b/src/system/EventFD.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/system/EventFD.hxx b/src/system/EventFD.hxx index 67a0258ab..2a70461d9 100644 --- a/src/system/EventFD.hxx +++ b/src/system/EventFD.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/system/EventPipe.cxx b/src/system/EventPipe.cxx index b49d1d0f0..b8fc85aed 100644 --- a/src/system/EventPipe.cxx +++ b/src/system/EventPipe.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/system/EventPipe.hxx b/src/system/EventPipe.hxx index 86a10b0bb..42b3bb93d 100644 --- a/src/system/EventPipe.hxx +++ b/src/system/EventPipe.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/system/FatalError.cxx b/src/system/FatalError.cxx index f02b4b581..cae6600dc 100644 --- a/src/system/FatalError.cxx +++ b/src/system/FatalError.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,12 +23,15 @@ #include "util/Domain.hxx" #include "LogV.hxx" +#ifdef HAVE_GLIB #include <glib.h> +#endif #include <unistd.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #ifdef WIN32 #include <windows.h> @@ -75,12 +78,16 @@ FatalError(const char *msg, const Error &error) FormatFatalError("%s: %s", msg, error.GetMessage()); } +#ifdef HAVE_GLIB + void FatalError(const char *msg, GError *error) { FormatFatalError("%s: %s", msg, error->message); } +#endif + void FatalSystemError(const char *msg) { @@ -88,7 +95,7 @@ FatalSystemError(const char *msg) #ifdef WIN32 system_error = g_win32_error_message(GetLastError()); #else - system_error = g_strerror(errno); + system_error = strerror(errno); #endif FormatError(fatal_error_domain, "%s: %s", msg, system_error); diff --git a/src/system/FatalError.hxx b/src/system/FatalError.hxx index 2845359ef..8d5f73aa2 100644 --- a/src/system/FatalError.hxx +++ b/src/system/FatalError.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -45,9 +45,11 @@ gcc_noreturn void FatalError(const char *msg, const Error &error); +#ifdef HAVE_GLIB gcc_noreturn void FatalError(const char *msg, GError *error); +#endif /** * Call this after a system call has failed that is not supposed to diff --git a/src/system/PeriodClock.hxx b/src/system/PeriodClock.hxx new file mode 100644 index 000000000..1ba74ece2 --- /dev/null +++ b/src/system/PeriodClock.hxx @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PERIOD_CLOCK_HXX +#define MPD_PERIOD_CLOCK_HXX + +#include "Clock.hxx" + +/** + * This is a stopwatch which saves the timestamp of an event, and can + * check whether a specified time span has passed since then. + */ +class PeriodClock { +protected: + typedef unsigned Stamp; + +private: + Stamp last; + +public: + /** + * Initializes the object, setting the last time stamp to "0", + * i.e. a Check() will always succeed. If you do not want this + * default behaviour, call Update() immediately after creating the + * object. + */ + constexpr + PeriodClock():last(0) {} + +protected: + static Stamp GetNow() { + return MonotonicClockMS(); + } + + constexpr int Elapsed(Stamp now) const { + return last == 0 + ? -1 + : now - last; + } + + constexpr bool Check(Stamp now, unsigned duration) const { + return now >= last + duration; + } + + void Update(Stamp now) { + last = now; + } + +public: + bool IsDefined() const { + return last != 0; + } + + /** + * Resets the clock. + */ + void Reset() { + last = 0; + } + + /** + * Returns the number of milliseconds elapsed since the last + * update(). Returns -1 if update() was never called. + */ + int Elapsed() const { + return Elapsed(GetNow()); + } + + /** + * Combines a call to Elapsed() and Update(). + */ + int ElapsedUpdate() { + const auto now = GetNow(); + int result = Elapsed(now); + Update(now); + return result; + } + + /** + * Checks whether the specified duration has passed since the last + * update. + * + * @param duration the duration in milliseconds + */ + bool Check(unsigned duration) const { + return Check(GetNow(), duration); + } + + /** + * Updates the time stamp, setting it to the current clock. + */ + void Update() { + Update(GetNow()); + } + + /** + * Updates the time stamp, setting it to the current clock plus the + * specified offset. + */ + void UpdateWithOffset(int offset) { + Update(GetNow() + offset); + } + + /** + * Checks whether the specified duration has passed since the last + * update. If yes, it updates the time stamp. + * + * @param duration the duration in milliseconds + */ + bool CheckUpdate(unsigned duration) { + Stamp now = GetNow(); + if (Check(now, duration)) { + Update(now); + return true; + } else + return false; + } + + /** + * Checks whether the specified duration has passed since the last + * update. After that, it updates the time stamp. + * + * @param duration the duration in milliseconds + */ + bool CheckAlwaysUpdate(unsigned duration) { + Stamp now = GetNow(); + bool ret = Check(now, duration); + Update(now); + return ret; + } +}; + +#endif diff --git a/src/system/Resolver.cxx b/src/system/Resolver.cxx index 5e6ea590b..910e1629c 100644 --- a/src/system/Resolver.cxx +++ b/src/system/Resolver.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,11 +22,12 @@ #include "util/Error.hxx" #include "util/Domain.hxx" -#include <glib.h> - #ifndef WIN32 #include <sys/socket.h> #include <netdb.h> +#ifdef HAVE_TCP +#include <netinet/in.h> +#endif #else #include <ws2tcpip.h> #include <winsock.h> @@ -41,17 +42,17 @@ const Domain resolver_domain("resolver"); -char * -sockaddr_to_string(const struct sockaddr *sa, size_t length, Error &error) +std::string +sockaddr_to_string(const struct sockaddr *sa, size_t length) { #ifdef HAVE_UN if (sa->sa_family == AF_UNIX) { /* return path of UNIX domain sockets */ const sockaddr_un &s_un = *(const sockaddr_un *)sa; if (length < sizeof(s_un) || s_un.sun_path[0] == 0) - return g_strdup("local"); + return "local"; - return g_strdup(s_un.sun_path); + return s_un.sun_path; } #endif @@ -80,17 +81,23 @@ sockaddr_to_string(const struct sockaddr *sa, size_t length, Error &error) 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; - } + if (ret != 0) + return "unknown"; #ifdef HAVE_IPV6 - if (strchr(host, ':') != NULL) - return g_strconcat("[", host, "]:", serv, NULL); + if (strchr(host, ':') != NULL) { + std::string result("["); + result.append(host); + result.append("]:"); + result.append(serv); + return result; + } #endif - return g_strconcat(host, ":", serv, NULL); + std::string result(host); + result.push_back(':'); + result.append(serv); + return result; } struct addrinfo * @@ -98,19 +105,19 @@ 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; + std::string p(host_port); + const char *host = p.c_str(), *port = nullptr; 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; + size_t q = p.find(']', 1); + if (q != p.npos && p[q + 1] == ':' && p[q + 2] != 0) { + p[q] = 0; + port = host + q + 2; ++host; - port = q + 2; } } @@ -118,10 +125,11 @@ resolve_host_port(const char *host_port, unsigned default_port, /* 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; + auto q = p.find(':'); + if (q != p.npos && p[q + 1] != 0 && + p.find(':', q + 1) == p.npos) { + p[q] = 0; + port = host + q + 1; } } @@ -142,7 +150,6 @@ resolve_host_port(const char *host_port, unsigned default_port, 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", diff --git a/src/system/Resolver.hxx b/src/system/Resolver.hxx index 62ef455a1..54922d98f 100644 --- a/src/system/Resolver.hxx +++ b/src/system/Resolver.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,27 +22,27 @@ #include "Compiler.h" +#include <string> + #include <stddef.h> struct sockaddr; struct addrinfo; class Error; +class Domain; -extern const class Domain resolver_domain; +extern const 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. + * "IP:PORT". * * @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); +gcc_pure +std::string +sockaddr_to_string(const sockaddr *sa, size_t length); /** * Resolve a specification in the form "host", "host:port", @@ -54,7 +54,7 @@ sockaddr_to_string(const struct sockaddr *sa, size_t length, Error &error); * @return an #addrinfo linked list that must be freed with * freeaddrinfo(), or NULL on error */ -struct addrinfo * +addrinfo * resolve_host_port(const char *host_port, unsigned default_port, int flags, int socktype, Error &error); diff --git a/src/system/SignalFD.cxx b/src/system/SignalFD.cxx index b89775dcd..173a0cc8c 100644 --- a/src/system/SignalFD.cxx +++ b/src/system/SignalFD.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,6 @@ #include "config.h" #ifdef USE_SIGNALFD #include "SignalFD.hxx" -#include "fd_util.h" #include "FatalError.hxx" #include <assert.h> diff --git a/src/system/SignalFD.hxx b/src/system/SignalFD.hxx index 7163782d1..11bf30f74 100644 --- a/src/system/SignalFD.hxx +++ b/src/system/SignalFD.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/system/SocketError.cxx b/src/system/SocketError.cxx index 315a86e1f..e138f4dd3 100644 --- a/src/system/SocketError.cxx +++ b/src/system/SocketError.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,7 +21,7 @@ #include "SocketError.hxx" #include "util/Domain.hxx" -#include <glib.h> +#include <string.h> const Domain socket_domain("socket"); @@ -41,6 +41,6 @@ SocketErrorMessage::SocketErrorMessage(socket_error_t code) #else SocketErrorMessage::SocketErrorMessage(socket_error_t code) - :msg(g_strerror(code)) {} + :msg(strerror(code)) {} #endif diff --git a/src/system/SocketError.hxx b/src/system/SocketError.hxx index 22fbd2441..01abc9884 100644 --- a/src/system/SocketError.hxx +++ b/src/system/SocketError.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,7 +21,7 @@ #define MPD_SOCKET_ERROR_HXX #include "Compiler.h" -#include "util/Error.hxx" +#include "util/Error.hxx" // IWYU pragma: export #ifdef WIN32 #include <winsock2.h> @@ -31,11 +31,13 @@ typedef DWORD socket_error_t; typedef int socket_error_t; #endif +class Domain; + /** * 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; +extern const Domain socket_domain; gcc_pure static inline socket_error_t diff --git a/src/system/SocketUtil.cxx b/src/system/SocketUtil.cxx index 9c4002386..b9df0d59d 100644 --- a/src/system/SocketUtil.cxx +++ b/src/system/SocketUtil.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,8 +22,6 @@ #include "SocketError.hxx" #include "fd_util.h" -#include <glib.h> - #include <unistd.h> #ifndef WIN32 diff --git a/src/system/SocketUtil.hxx b/src/system/SocketUtil.hxx index 5e582ec0d..652788759 100644 --- a/src/system/SocketUtil.hxx +++ b/src/system/SocketUtil.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/system/fd_util.c b/src/system/fd_util.c index b4a7032e6..0e4b5663d 100644 --- a/src/system/fd_util.c +++ b/src/system/fd_util.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * Redistribution and use in source and binary forms, with or without diff --git a/src/system/fd_util.h b/src/system/fd_util.h index b7a9a6dd3..b73a2012f 100644 --- a/src/system/fd_util.h +++ b/src/system/fd_util.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * Redistribution and use in source and binary forms, with or without diff --git a/src/tag/Aiff.cxx b/src/tag/Aiff.cxx index 73e46e49f..c2498c9e9 100644 --- a/src/tag/Aiff.cxx +++ b/src/tag/Aiff.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,9 +26,7 @@ #include <limits> #include <stdint.h> -#include <sys/types.h> #include <sys/stat.h> -#include <unistd.h> #include <string.h> static constexpr Domain aiff_domain("aiff"); diff --git a/src/tag/Aiff.hxx b/src/tag/Aiff.hxx index 9000be7f8..cd323ee2e 100644 --- a/src/tag/Aiff.hxx +++ b/src/tag/Aiff.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag/ApeLoader.cxx b/src/tag/ApeLoader.cxx index 8251efe10..f473c910e 100644 --- a/src/tag/ApeLoader.cxx +++ b/src/tag/ApeLoader.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,8 +22,6 @@ #include "system/ByteOrder.hxx" #include "fs/FileSystem.hxx" -#include <glib.h> - #include <stdint.h> #include <assert.h> #include <stdio.h> @@ -61,9 +59,9 @@ ape_scan_internal(FILE *fp, ApeTagCallback callback) remaining -= sizeof(footer); assert(remaining > 10); - char *buffer = (char *)g_malloc(remaining); + char *buffer = new char[remaining]; if (fread(buffer, 1, remaining, fp) != remaining) { - g_free(buffer); + delete[] buffer; return false; } @@ -98,7 +96,7 @@ ape_scan_internal(FILE *fp, ApeTagCallback callback) remaining -= size; } - g_free(buffer); + delete[] buffer; return true; } diff --git a/src/tag/ApeLoader.hxx b/src/tag/ApeLoader.hxx index 915c363b4..ce82cc35d 100644 --- a/src/tag/ApeLoader.hxx +++ b/src/tag/ApeLoader.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag/ApeReplayGain.cxx b/src/tag/ApeReplayGain.cxx index cc65fb79d..6afb3e96e 100644 --- a/src/tag/ApeReplayGain.cxx +++ b/src/tag/ApeReplayGain.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag/ApeReplayGain.hxx b/src/tag/ApeReplayGain.hxx index 865add6f1..03c899c5c 100644 --- a/src/tag/ApeReplayGain.hxx +++ b/src/tag/ApeReplayGain.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag/ApeTag.cxx b/src/tag/ApeTag.cxx index 2df53947a..f714a1624 100644 --- a/src/tag/ApeTag.cxx +++ b/src/tag/ApeTag.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag/ApeTag.hxx b/src/tag/ApeTag.hxx index e35edc381..edebf076c 100644 --- a/src/tag/ApeTag.hxx +++ b/src/tag/ApeTag.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -34,6 +34,6 @@ extern const struct tag_table ape_tags[]; */ bool tag_ape_scan2(Path path_fs, - const struct tag_handler *handler, void *handler_ctx); + const tag_handler *handler, void *handler_ctx); #endif diff --git a/src/tag/Riff.cxx b/src/tag/Riff.cxx index ac162bc24..c630f082d 100644 --- a/src/tag/Riff.cxx +++ b/src/tag/Riff.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,7 +26,6 @@ #include <limits> #include <stdint.h> -#include <sys/types.h> #include <sys/stat.h> #include <string.h> diff --git a/src/tag/Riff.hxx b/src/tag/Riff.hxx index fbbdfaaf6..a9af67b7a 100644 --- a/src/tag/Riff.hxx +++ b/src/tag/Riff.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag/Tag.cxx b/src/tag/Tag.cxx index 6bf070429..448f3b26a 100644 --- a/src/tag/Tag.cxx +++ b/src/tag/Tag.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,9 +22,9 @@ #include "TagPool.hxx" #include "TagString.hxx" #include "TagSettings.h" +#include "TagBuilder.hxx" #include "util/ASCII.hxx" -#include <glib.h> #include <assert.h> #include <string.h> @@ -58,12 +58,6 @@ tag_name_parse_i(const char *name) return TAG_NUM_OF_ITEM_TYPES; } -static size_t -items_size(const Tag &tag) -{ - return tag.num_items * sizeof(TagItem *); -} - void Tag::Clear() { @@ -75,28 +69,18 @@ Tag::Clear() tag_pool_put_item(items[i]); tag_pool_lock.unlock(); - g_free(items); + delete[] 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) + num_items(other.num_items), + items(nullptr) { if (num_items > 0) { - items = (TagItem **)g_malloc(items_size(other)); + items = new TagItem *[num_items]; tag_pool_lock.lock(); for (unsigned i = 0; i < num_items; i++) @@ -108,46 +92,9 @@ Tag::Tag(const Tag &other) 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; + TagBuilder builder(add); + builder.Complement(base); + return builder.CommitNew(); } Tag * @@ -183,43 +130,3 @@ Tag::HasType(TagType type) const { return GetValue(type) != nullptr; } - -void -Tag::AddItemInternal(TagType 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(TagType 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(TagType type, const char *value) -{ - AddItem(type, value, strlen(value)); -} diff --git a/src/tag/Tag.hxx b/src/tag/Tag.hxx index 5846e7a9d..5d3aaad0c 100644 --- a/src/tag/Tag.hxx +++ b/src/tag/Tag.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,8 +20,8 @@ #ifndef MPD_TAG_HXX #define MPD_TAG_HXX -#include "TagType.h" -#include "TagItem.hxx" +#include "TagType.h" // IWYU pragma: export +#include "TagItem.hxx" // IWYU pragma: export #include "Compiler.h" #include <algorithm> @@ -47,23 +47,23 @@ struct Tag { */ bool has_playlist; + /** the total number of tag items in the #items array */ + unsigned short num_items; + /** 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) {} + num_items(0), items(nullptr) {} Tag(const Tag &other); Tag(Tag &&other) :time(other.time), has_playlist(other.has_playlist), - items(other.items), num_items(other.num_items) { + num_items(other.num_items), items(other.items) { other.items = nullptr; other.num_items = 0; } @@ -71,7 +71,9 @@ struct Tag { /** * Free the tag object and all its items. */ - ~Tag(); + ~Tag() { + Clear(); + } Tag &operator=(const Tag &other) = delete; @@ -104,24 +106,6 @@ struct Tag { 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(TagType 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(TagType type, const char *value); - - /** * Merges the data from two tags. If both tags share data for the * same TagType, only data from "add" is used. * @@ -152,9 +136,6 @@ struct Tag { */ gcc_pure bool HasType(TagType type) const; - -private: - void AddItemInternal(TagType type, const char *value, size_t len); }; /** diff --git a/src/tag/TagBuilder.cxx b/src/tag/TagBuilder.cxx index 25e5cc24b..40ccff752 100644 --- a/src/tag/TagBuilder.cxx +++ b/src/tag/TagBuilder.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,23 +24,90 @@ #include "TagString.hxx" #include "Tag.hxx" -#include <glib.h> - #include <assert.h> #include <string.h> +#include <stdlib.h> -void -TagBuilder::Clear() +TagBuilder::TagBuilder(const Tag &other) + :time(other.time), has_playlist(other.has_playlist) { - time = -1; - has_playlist = false; + items.reserve(other.num_items); tag_pool_lock.lock(); + for (unsigned i = 0, n = other.num_items; i != n; ++i) + items.push_back(tag_pool_dup_item(other.items[i])); + tag_pool_lock.unlock(); +} + +TagBuilder::TagBuilder(Tag &&other) + :time(other.time), has_playlist(other.has_playlist) +{ + /* move all TagItem pointers from the Tag object; we don't + need to contact the tag pool, because all we do is move + references */ + items.reserve(other.num_items); + std::copy_n(other.items, other.num_items, std::back_inserter(items)); + + /* discard the pointers from the Tag object */ + other.num_items = 0; + delete[] other.items; + other.items = nullptr; +} + +TagBuilder & +TagBuilder::operator=(const TagBuilder &other) +{ + /* copy all attributes */ + time = other.time; + has_playlist = other.has_playlist; + items = other.items; + + /* increment the tag pool refcounters */ + tag_pool_lock.lock(); for (auto i : items) - tag_pool_put_item(i); + tag_pool_dup_item(i); tag_pool_lock.unlock(); + return *this; +} + +TagBuilder & +TagBuilder::operator=(TagBuilder &&other) +{ + time = other.time; + has_playlist = other.has_playlist; + items = std::move(other.items); + + return *this; +} + +TagBuilder & +TagBuilder::operator=(Tag &&other) +{ + time = other.time; + has_playlist = other.has_playlist; + + /* move all TagItem pointers from the Tag object; we don't + need to contact the tag pool, because all we do is move + references */ items.clear(); + items.reserve(other.num_items); + std::copy_n(other.items, other.num_items, std::back_inserter(items)); + + /* discard the pointers from the Tag object */ + other.num_items = 0; + delete[] other.items; + other.items = nullptr; + + return *this; +} + +void +TagBuilder::Clear() +{ + time = -1; + has_playlist = false; + RemoveAll(); } void @@ -57,7 +124,7 @@ TagBuilder::Commit(Tag &tag) object */ const unsigned n_items = items.size(); tag.num_items = n_items; - tag.items = g_new(TagItem *, n_items); + tag.items = new TagItem *[n_items]; std::copy_n(items.begin(), n_items, tag.items); items.clear(); @@ -66,14 +133,51 @@ TagBuilder::Commit(Tag &tag) Clear(); } -Tag * +Tag TagBuilder::Commit() { + Tag tag; + Commit(tag); + return tag; +} + +Tag * +TagBuilder::CommitNew() +{ Tag *tag = new Tag(); Commit(*tag); return tag; } +bool +TagBuilder::HasType(TagType type) const +{ + for (auto i : items) + if (i->type == type) + return true; + + return false; +} + +void +TagBuilder::Complement(const Tag &other) +{ + if (time <= 0) + time = other.time; + + has_playlist |= other.has_playlist; + + items.reserve(items.size() + other.num_items); + + tag_pool_lock.lock(); + for (unsigned i = 0, n = other.num_items; i != n; ++i) { + TagItem *item = other.items[i]; + if (!HasType(item->type)) + items.push_back(tag_pool_dup_item(item)); + } + tag_pool_lock.unlock(); +} + inline void TagBuilder::AddItemInternal(TagType type, const char *value, size_t length) { @@ -90,7 +194,7 @@ TagBuilder::AddItemInternal(TagType type, const char *value, size_t length) auto i = tag_pool_get_item(type, value, length); tag_pool_lock.unlock(); - g_free(p); + free(p); items.push_back(i); } @@ -113,3 +217,29 @@ TagBuilder::AddItem(TagType type, const char *value) AddItem(type, value, strlen(value)); } + +void +TagBuilder::RemoveAll() +{ + tag_pool_lock.lock(); + for (auto i : items) + tag_pool_put_item(i); + tag_pool_lock.unlock(); + + items.clear(); +} + +void +TagBuilder::RemoveType(TagType type) +{ + const auto begin = items.begin(), end = items.end(); + + items.erase(std::remove_if(begin, end, + [type](TagItem *item) { + if (item->type != type) + return false; + tag_pool_put_item(item); + return true; + }), + end); +} diff --git a/src/tag/TagBuilder.hxx b/src/tag/TagBuilder.hxx index ffc60a1b2..c0b4bebfa 100644 --- a/src/tag/TagBuilder.hxx +++ b/src/tag/TagBuilder.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -63,7 +63,14 @@ public: } TagBuilder(const TagBuilder &other) = delete; - TagBuilder &operator=(const TagBuilder &other) = delete; + + explicit TagBuilder(const Tag &other); + explicit TagBuilder(Tag &&other); + + TagBuilder &operator=(const TagBuilder &other); + TagBuilder &operator=(TagBuilder &&other); + + TagBuilder &operator=(Tag &&other); /** * Returns true if the tag contains no items. This ignores the "time" @@ -90,11 +97,17 @@ public: void Commit(Tag &tag); /** + * Create a new #Tag instance from data in this object. This + * object is empty afterwards. + */ + Tag Commit(); + + /** * 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(); + Tag *CommitNew(); void SetTime(int _time) { time = _time; @@ -109,6 +122,19 @@ public: } /** + * Checks whether the tag contains one or more items with + * the specified type. + */ + gcc_pure + bool HasType(TagType type) const; + + /** + * Copy attributes and items from the other object that do not + * exist in this object. + */ + void Complement(const Tag &other); + + /** * Appends a new tag item. * * @param type the type of the new tag item @@ -127,6 +153,16 @@ public: gcc_nonnull_all void AddItem(TagType type, const char *value); + /** + * Removes all tag items. + */ + void RemoveAll(); + + /** + * Removes all tag items of the specified type. + */ + void RemoveType(TagType type); + private: gcc_nonnull_all void AddItemInternal(TagType type, const char *value, size_t length); diff --git a/src/tag/TagConfig.cxx b/src/tag/TagConfig.cxx index b8be4fc4c..3e941c8d9 100644 --- a/src/tag/TagConfig.cxx +++ b/src/tag/TagConfig.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,16 +21,17 @@ #include "TagConfig.hxx" #include "TagSettings.h" #include "Tag.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" #include "system/FatalError.hxx" +#include "util/Alloc.hxx" #include "util/ASCII.hxx" #include <glib.h> #include <algorithm> -#include <string.h> +#include <stdlib.h> void TagLoadConfig() @@ -46,7 +47,7 @@ TagLoadConfig() bool quit = false; char *temp, *c, *s; - temp = c = s = g_strdup(value); + temp = c = s = xstrdup(value); do { if (*s == ',' || *s == '\0') { if (*s == '\0') @@ -70,5 +71,5 @@ TagLoadConfig() s++; } while (!quit); - g_free(temp); + free(temp); } diff --git a/src/tag/TagConfig.hxx b/src/tag/TagConfig.hxx index 5ec6766d4..0088e9757 100644 --- a/src/tag/TagConfig.hxx +++ b/src/tag/TagConfig.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,8 +20,6 @@ #ifndef MPD_TAG_CONFIG_HXX #define MPD_TAG_CONFIG_HXX -#include "TagType.h" - void TagLoadConfig(); diff --git a/src/tag/TagHandler.cxx b/src/tag/TagHandler.cxx index 80982ef50..014834b88 100644 --- a/src/tag/TagHandler.cxx +++ b/src/tag/TagHandler.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag/TagHandler.hxx b/src/tag/TagHandler.hxx index b8c3c6b79..6f737bf56 100644 --- a/src/tag/TagHandler.hxx +++ b/src/tag/TagHandler.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag/TagId3.cxx b/src/tag/TagId3.cxx index df70a95e5..4ecd4baa6 100644 --- a/src/tag/TagId3.cxx +++ b/src/tag/TagId3.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,12 +21,11 @@ #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 "config/ConfigGlobal.hxx" #include "Riff.hxx" #include "Aiff.hxx" #include "fs/Path.hxx" @@ -35,9 +34,10 @@ #include <glib.h> #include <id3tag.h> +#include <string> + #include <stdio.h> #include <stdlib.h> -#include <errno.h> #include <string.h> # ifndef ID3_FRAME_COMPOSER @@ -70,14 +70,11 @@ tag_is_id3v1(struct id3_tag *tag) 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); + id3_field *field = id3_frame_field(frame, i); if (field == nullptr) return nullptr; - ucs4 = id3_field_getstring(field); + const id3_ucs4_t *ucs4 = id3_field_getstring(field); if (ucs4 == nullptr) return nullptr; @@ -89,17 +86,15 @@ tag_id3_getstring(const struct id3_frame *frame, unsigned i) 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; + id3_utf8_t *utf8; /* use encoding field here? */ + const char *encoding; if (is_id3v1 && (encoding = config_get_string(CONF_ID3V1_ENCODING, nullptr)) != nullptr) { - isostr = id3_ucs4_latin1duplicate(ucs4); - if (G_UNLIKELY(!isostr)) { + id3_latin1_t *isostr = id3_ucs4_latin1duplicate(ucs4); + if (gcc_unlikely(isostr == nullptr)) return nullptr; - } utf8 = (id3_utf8_t *) g_convert_with_fallback((const char*)isostr, -1, @@ -110,19 +105,19 @@ import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4) FormatWarning(id3_domain, "Unable to convert %s string to UTF-8: '%s'", encoding, isostr); - g_free(isostr); + free(isostr); return nullptr; } - g_free(isostr); + free(isostr); } else { utf8 = id3_ucs4_utf8duplicate(ucs4); - if (G_UNLIKELY(!utf8)) { + if (gcc_unlikely(utf8 == nullptr)) return nullptr; - } } - utf8_stripped = (id3_utf8_t *)g_strdup(g_strstrip((gchar *)utf8)); - g_free(utf8); + id3_utf8_t *utf8_stripped = (id3_utf8_t *) + g_strdup(g_strstrip((gchar *)utf8)); + free(utf8); return utf8_stripped; } @@ -139,17 +134,12 @@ tag_id3_import_text_frame(struct id3_tag *tag, const struct id3_frame *frame, TagType 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); + const id3_field *field = id3_frame_field(frame, 0); if (field == nullptr || field->type != ID3_FIELD_TYPE_TEXTENCODING) return; @@ -160,16 +150,16 @@ tag_id3_import_text_frame(struct id3_tag *tag, const struct id3_frame *frame, return; /* Get the number of strings available */ - nstrings = id3_field_getnstrings(field); - for (i = 0; i < nstrings; i++) { - ucs4 = id3_field_getstrings(field, i); + const unsigned nstrings = id3_field_getnstrings(field); + for (unsigned i = 0; i < nstrings; i++) { + const id3_ucs4_t *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); + id3_utf8_t *utf8 = import_id3_string(tag_is_id3v1(tag), ucs4); if (utf8 == nullptr) continue; @@ -209,23 +199,19 @@ tag_id3_import_comment_frame(struct id3_tag *tag, 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); + const id3_field *field = id3_frame_field(frame, 3); if (field == nullptr) return; - ucs4 = id3_field_getfullstring(field); + const id3_ucs4_t *ucs4 = id3_field_getfullstring(field); if (ucs4 == nullptr) return; - utf8 = import_id3_string(tag_is_id3v1(tag), ucs4); + id3_utf8_t *utf8 = import_id3_string(tag_is_id3v1(tag), ucs4); if (utf8 == nullptr) return; @@ -277,19 +263,15 @@ tag_id3_import_musicbrainz(struct id3_tag *id3_tag, void *handler_ctx) { for (unsigned i = 0;; ++i) { - const struct id3_frame *frame; - id3_utf8_t *name, *value; - TagType type; - - frame = id3_tag_findframe(id3_tag, "TXXX", i); + const id3_frame *frame = id3_tag_findframe(id3_tag, "TXXX", i); if (frame == nullptr) break; - name = tag_id3_getstring(frame, 1); + id3_utf8_t *name = tag_id3_getstring(frame, 1); if (name == nullptr) continue; - value = tag_id3_getstring(frame, 2); + id3_utf8_t *value = tag_id3_getstring(frame, 2); if (value == nullptr) continue; @@ -297,7 +279,7 @@ tag_id3_import_musicbrainz(struct id3_tag *id3_tag, (const char *)name, (const char *)value); - type = tag_id3_parse_txxx_name((const char*)name); + TagType type = tag_id3_parse_txxx_name((const char*)name); free(name); if (type != TAG_NUM_OF_ITEM_TYPES) @@ -316,21 +298,15 @@ 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); + const id3_frame *frame = id3_tag_findframe(id3_tag, "UFID", i); if (frame == nullptr) break; - field = id3_frame_field(frame, 0); + id3_field *field = id3_frame_field(frame, 0); if (field == nullptr) continue; - name = id3_field_getlatin1(field); + const id3_latin1_t *name = id3_field_getlatin1(field); if (name == nullptr || strcmp((const char *)name, "http://musicbrainz.org") != 0) continue; @@ -339,14 +315,15 @@ tag_id3_import_ufid(struct id3_tag *id3_tag, if (field == nullptr) continue; - value = id3_field_getbinarydata(field, &length); + id3_length_t length; + const id3_byte_t *value = + id3_field_getbinarydata(field, &length); if (value == nullptr || length == 0) continue; - char *p = g_strndup((const char *)value, length); + std::string p((const char *)value, length); tag_handler_invoke_tag(handler, handler_ctx, - TAG_MUSICBRAINZ_TRACKID, p); - g_free(p); + TAG_MUSICBRAINZ_TRACKID, p.c_str()); } } @@ -393,73 +370,57 @@ tag_id3_import(struct id3_tag *tag) scan_id3_tag(tag, &add_tag_handler, &tag_builder); return tag_builder.IsEmpty() ? nullptr - : tag_builder.Commit(); + : tag_builder.CommitNew(); } -static int +static size_t 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 +static long 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; + size_t 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); + id3_byte_t query_buffer[ID3_TAG_QUERYSIZE]; + size_t 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); + long 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; - + id3_byte_t *tag_buffer = new id3_byte_t[tag_size]; int tag_buffer_size = fill_buffer(tag_buffer, tag_size, stream, offset, whence); if (tag_buffer_size < tag_size) { - g_free(tag_buffer); + delete[] tag_buffer; return nullptr; } - tag = id3_tag_parse(tag_buffer, tag_buffer_size); - - g_free(tag_buffer); - + id3_tag *tag = id3_tag_parse(tag_buffer, tag_buffer_size); + delete[] 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); + id3_tag *tag = tag_id3_read(stream, 0, SEEK_SET); if (!tag) { return nullptr; } else if (tag_is_id3v1(tag)) { @@ -469,14 +430,15 @@ tag_id3_find_from_beginning(FILE *stream) } /* We have an id3v2 tag, so let's look for SEEK frames */ + id3_frame *frame; 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)); + int 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); + id3_tag *seektag = tag_id3_read(stream, seek, SEEK_CUR); if (!seektag || tag_is_id3v1(seektag)) break; @@ -491,20 +453,16 @@ tag_id3_find_from_beginning(FILE *stream) static struct id3_tag * tag_id3_find_from_end(FILE *stream) { - struct id3_tag *tag; - struct id3_tag *v1tag; - int tagsize; - /* Get an id3v1 tag from the end of file for later use */ - v1tag = tag_id3_read(stream, -128, SEEK_END); + id3_tag *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); + int 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); + id3_tag *tag = tag_id3_read(stream, tagsize, SEEK_CUR); if (!tag) return v1tag; @@ -527,16 +485,16 @@ tag_id3_riff_aiff_load(FILE *file) /* too large, don't allocate so much memory */ return nullptr; - id3_byte_t *buffer = (id3_byte_t *)g_malloc(size); + id3_byte_t *buffer = new id3_byte_t[size]; size_t ret = fread(buffer, size, 1, file); if (ret != 1) { LogWarning(id3_domain, "Failed to read RIFF chunk"); - g_free(buffer); + delete[] buffer; return nullptr; } struct id3_tag *tag = id3_tag_parse(buffer, size); - g_free(buffer); + delete[] buffer; return tag; } diff --git a/src/tag/TagId3.hxx b/src/tag/TagId3.hxx index 749166116..1928d539d 100644 --- a/src/tag/TagId3.hxx +++ b/src/tag/TagId3.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -33,10 +33,10 @@ class Error; bool tag_id3_scan(Path path_fs, - const struct tag_handler *handler, void *handler_ctx); + const tag_handler *handler, void *handler_ctx); Tag * -tag_id3_import(struct id3_tag *); +tag_id3_import(id3_tag *); /** * Loads the ID3 tags from the file into a libid3tag object. The @@ -53,8 +53,8 @@ tag_id3_load(Path path_fs, Error &error); * */ void -scan_id3_tag(struct id3_tag *tag, - const struct tag_handler *handler, void *handler_ctx); +scan_id3_tag(id3_tag *tag, + const tag_handler *handler, void *handler_ctx); #else @@ -62,7 +62,7 @@ scan_id3_tag(struct id3_tag *tag, static inline bool tag_id3_scan(gcc_unused Path path_fs, - gcc_unused const struct tag_handler *handler, + gcc_unused const tag_handler *handler, gcc_unused void *handler_ctx) { return false; diff --git a/src/tag/TagItem.hxx b/src/tag/TagItem.hxx index 7c3100393..489ecde3a 100644 --- a/src/tag/TagItem.hxx +++ b/src/tag/TagItem.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag/TagNames.c b/src/tag/TagNames.c index eac6fc59a..477ed5f21 100644 --- a/src/tag/TagNames.c +++ b/src/tag/TagNames.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag/TagPool.cxx b/src/tag/TagPool.cxx index cc28ea9a6..2108a98c3 100644 --- a/src/tag/TagPool.cxx +++ b/src/tag/TagPool.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,23 +20,46 @@ #include "config.h" #include "TagPool.hxx" #include "TagItem.hxx" - -#include <glib.h> +#include "util/Cast.hxx" +#include "util/VarSize.hxx" #include <assert.h> #include <string.h> +#include <stdlib.h> Mutex tag_pool_lock; #define NUM_SLOTS 4096 -struct slot { - struct slot *next; +struct TagPoolSlot { + TagPoolSlot *next; unsigned char ref; TagItem item; -} mpd_packed; -static struct slot *slots[NUM_SLOTS]; + TagPoolSlot(TagPoolSlot *_next, TagType type, + const char *value, size_t length) + :next(_next), ref(1) { + item.type = type; + memcpy(item.value, value, length); + item.value[length] = 0; + } + + static TagPoolSlot *Create(TagPoolSlot *_next, TagType type, + const char *value, size_t length); +} gcc_packed; + +TagPoolSlot * +TagPoolSlot::Create(TagPoolSlot *_next, TagType type, + const char *value, size_t length) +{ + TagPoolSlot *dummy; + return NewVarSize<TagPoolSlot>(sizeof(dummy->item.value), + length + 1, + _next, type, + value, length); +} + +static TagPoolSlot *slots[NUM_SLOTS]; static inline unsigned calc_hash_n(TagType type, const char *p, size_t length) @@ -64,32 +87,16 @@ calc_hash(TagType type, const char *p) return hash ^ type; } -static inline struct slot * +static inline constexpr TagPoolSlot * tag_item_to_slot(TagItem *item) { - return (struct slot*)(((char*)item) - offsetof(struct slot, item)); -} - -static struct slot *slot_alloc(struct slot *next, - TagType 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; + return ContainerCast(item, TagPoolSlot, item); } TagItem * tag_pool_get_item(TagType type, const char *value, size_t length) { - struct slot **slot_p, *slot; + TagPoolSlot **slot_p, *slot; slot_p = &slots[calc_hash_n(type, value, length) % NUM_SLOTS]; for (slot = *slot_p; slot != nullptr; slot = slot->next) { @@ -103,7 +110,7 @@ tag_pool_get_item(TagType type, const char *value, size_t length) } } - slot = slot_alloc(*slot_p, type, value, length); + slot = TagPoolSlot::Create(*slot_p, type, value, length); *slot_p = slot; return &slot->item; } @@ -111,7 +118,7 @@ tag_pool_get_item(TagType type, const char *value, size_t length) TagItem * tag_pool_dup_item(TagItem *item) { - struct slot *slot = tag_item_to_slot(item); + TagPoolSlot *slot = tag_item_to_slot(item); assert(slot->ref > 0); @@ -122,11 +129,11 @@ tag_pool_dup_item(TagItem *item) /* the reference counter overflows above 0xff; duplicate the item, and start with 1 */ size_t length = strlen(item->value); - struct slot **slot_p = + TagPoolSlot **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 = TagPoolSlot::Create(*slot_p, item->type, + item->value, strlen(item->value)); *slot_p = slot; return &slot->item; } @@ -135,7 +142,7 @@ tag_pool_dup_item(TagItem *item) void tag_pool_put_item(TagItem *item) { - struct slot **slot_p, *slot; + TagPoolSlot **slot_p, *slot; slot = tag_item_to_slot(item); assert(slot->ref > 0); @@ -151,5 +158,5 @@ tag_pool_put_item(TagItem *item) } *slot_p = slot->next; - g_free(slot); + DeleteVarSize(slot); } diff --git a/src/tag/TagPool.hxx b/src/tag/TagPool.hxx index f41f3a724..990ee87bd 100644 --- a/src/tag/TagPool.hxx +++ b/src/tag/TagPool.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag/TagRva2.cxx b/src/tag/TagRva2.cxx index 204001aa7..bbb6d11e6 100644 --- a/src/tag/TagRva2.cxx +++ b/src/tag/TagRva2.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,10 +21,10 @@ #include "TagRva2.hxx" #include "ReplayGainInfo.hxx" +#include <id3tag.h> + #include <stdint.h> #include <string.h> -#include <glib.h> -#include <id3tag.h> enum rva2_channel { CHANNEL_OTHER = 0x00, diff --git a/src/tag/TagRva2.hxx b/src/tag/TagRva2.hxx index 98154041a..df559f472 100644 --- a/src/tag/TagRva2.hxx +++ b/src/tag/TagRva2.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -32,6 +32,6 @@ struct ReplayGainInfo; * @return true on success */ bool -tag_rva2_parse(struct id3_tag *tag, ReplayGainInfo &replay_gain_info); +tag_rva2_parse(id3_tag *tag, ReplayGainInfo &replay_gain_info); #endif diff --git a/src/tag/TagSettings.c b/src/tag/TagSettings.c index eb2db2ba5..e0c577c2b 100644 --- a/src/tag/TagSettings.c +++ b/src/tag/TagSettings.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag/TagSettings.h b/src/tag/TagSettings.h index 245de2df5..33f89d4be 100644 --- a/src/tag/TagSettings.h +++ b/src/tag/TagSettings.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag/TagString.cxx b/src/tag/TagString.cxx index 3e8d8c1b0..a519080b4 100644 --- a/src/tag/TagString.cxx +++ b/src/tag/TagString.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,11 +19,13 @@ #include "config.h" #include "TagString.hxx" +#include "util/Alloc.hxx" #include <glib.h> #include <assert.h> #include <string.h> +#include <stdlib.h> /** * Replace invalid sequences with the question mark. @@ -33,7 +35,7 @@ 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); + char *dest = xstrdup(src); do { dest[end - src] = '?'; @@ -58,9 +60,12 @@ fix_utf8(const char *str, size_t length) /* 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) + if (temp != nullptr) { /* success! */ - return temp; + char *p = xstrdup(temp); + g_free(temp); + return p; + } /* no, still broken - there's no medication, just patch invalid sequences */ @@ -96,7 +101,7 @@ clear_non_printable(const char *p, size_t length) if (first == nullptr) return nullptr; - dest = g_strndup(p, length); + dest = xstrndup(p, length); for (size_t i = first - p; i < length; ++i) if (char_is_non_printable(dest[i])) @@ -120,7 +125,7 @@ FixTagString(const char *p, size_t length) if (cleared == nullptr) cleared = utf8; else - g_free(utf8); + free(utf8); return cleared; } diff --git a/src/tag/TagString.hxx b/src/tag/TagString.hxx index 79255dcd3..a1a9d9d15 100644 --- a/src/tag/TagString.hxx +++ b/src/tag/TagString.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag/TagTable.cxx b/src/tag/TagTable.cxx index b51bc35ba..c6e1cff54 100644 --- a/src/tag/TagTable.cxx +++ b/src/tag/TagTable.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -51,3 +51,13 @@ tag_table_lookup_i(const struct tag_table *table, const char *name) return TAG_NUM_OF_ITEM_TYPES; } + +const char * +tag_table_lookup(const tag_table *table, TagType type) +{ + for (; table->name != nullptr; ++table) + if (table->type == type) + return table->name; + + return nullptr; +} diff --git a/src/tag/TagTable.hxx b/src/tag/TagTable.hxx index c050f61ac..095b4cbff 100644 --- a/src/tag/TagTable.hxx +++ b/src/tag/TagTable.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -47,4 +47,13 @@ gcc_pure TagType tag_table_lookup_i(const tag_table *table, const char *name); +/** + * Looks up a #TagType in a tag translation table and returns its + * string representation. Returns nullptr if the specified type was + * not found in the table. + */ +gcc_pure +const char * +tag_table_lookup(const tag_table *table, TagType type); + #endif diff --git a/src/tag/TagType.h b/src/tag/TagType.h index 0b8aa55d4..098198445 100644 --- a/src/tag/TagType.h +++ b/src/tag/TagType.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/thread/Cond.hxx b/src/thread/Cond.hxx index ed663dc9d..93d5deb74 100644 --- a/src/thread/Cond.hxx +++ b/src/thread/Cond.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/thread/Id.hxx b/src/thread/Id.hxx index fa1cf2cab..7b10de074 100644 --- a/src/thread/Id.hxx +++ b/src/thread/Id.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/thread/Mutex.hxx b/src/thread/Mutex.hxx index 4ed48c972..dbf3fb7fe 100644 --- a/src/thread/Mutex.hxx +++ b/src/thread/Mutex.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/thread/Name.hxx b/src/thread/Name.hxx new file mode 100644 index 000000000..a6faa0508 --- /dev/null +++ b/src/thread/Name.hxx @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_NAME_HXX +#define MPD_THREAD_NAME_HXX + +#ifdef HAVE_PTHREAD_SETNAME_NP +#include <pthread.h> +#include <stdio.h> +#endif + +static inline void +SetThreadName(const char *name) +{ +#ifdef HAVE_PTHREAD_SETNAME_NP + pthread_setname_np(pthread_self(), name); +#else + (void)name; +#endif +} + +template<typename... Args> +static inline void +FormatThreadName(const char *fmt, gcc_unused Args&&... args) +{ +#ifdef HAVE_PTHREAD_SETNAME_NP + char buffer[16]; + snprintf(buffer, sizeof(buffer), fmt, args...); + SetThreadName(buffer); +#else + (void)fmt; +#endif +} + +#endif diff --git a/src/thread/Thread.cxx b/src/thread/Thread.cxx index 67bcf7184..9016680e4 100644 --- a/src/thread/Thread.cxx +++ b/src/thread/Thread.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/thread/Thread.hxx b/src/thread/Thread.hxx index d3bd75455..1b1bca0e8 100644 --- a/src/thread/Thread.hxx +++ b/src/thread/Thread.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/thread/Util.hxx b/src/thread/Util.hxx new file mode 100644 index 000000000..d71230c20 --- /dev/null +++ b/src/thread/Util.hxx @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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_UTIL_HXX +#define MPD_THREAD_UTIL_HXX + +#ifdef __linux__ +#include <sched.h> +#include <sys/syscall.h> +#include <unistd.h> +#elif defined(WIN32) +#include <windows.h> +#endif + +#ifdef __linux__ + +static int +ioprio_set(int which, int who, int ioprio) +{ + return syscall(SYS_ioprio_set, which, who, ioprio); +} + +static void +ioprio_set_idle() +{ + static constexpr int _IOPRIO_WHO_PROCESS = 1; + static constexpr int _IOPRIO_CLASS_IDLE = 3; + static constexpr int _IOPRIO_CLASS_SHIFT = 13; + static constexpr int _IOPRIO_IDLE = + (_IOPRIO_CLASS_IDLE << _IOPRIO_CLASS_SHIFT) | 7; + + ioprio_set(_IOPRIO_WHO_PROCESS, 0, _IOPRIO_IDLE); +} + +#endif + +/** + * Lower the current thread's priority to "idle" (very low). + */ +static inline void +SetThreadIdlePriority() +{ +#ifdef __linux__ +#ifdef SCHED_IDLE + static struct sched_param sched_param; + sched_setscheduler(0, SCHED_IDLE, &sched_param); +#endif + + ioprio_set_idle(); + +#elif defined(WIN32) + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE); +#endif +}; + +/** + * Raise the current thread's priority to "real-time" (very high). + */ +static inline void +SetThreadRealtime() +{ +#ifdef __linux__ + struct sched_param sched_param; + sched_param.sched_priority = 50; + sched_setscheduler(0, SCHED_FIFO|SCHED_RESET_ON_FORK, &sched_param); +#endif +}; + +#endif diff --git a/src/unix/Daemon.cxx b/src/unix/Daemon.cxx new file mode 100644 index 000000000..490b2def5 --- /dev/null +++ b/src/unix/Daemon.cxx @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/AllocatedPath.hxx" +#include "fs/FileSystem.hxx" +#include "util/Domain.hxx" +#include "PidFile.hxx" +#include "Log.hxx" + +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> + +#ifndef WIN32 +#include <sys/wait.h> +#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 = (gid_t)-1; + +/** the absolute path of the pidfile */ +static AllocatedPath pidfile = AllocatedPath::Null(); + +/* whether "group" conf. option was given */ +static bool had_group = false; + +/** + * The write end of a pipe that is used to notify the parent process + * that initialization has finished and that it should detach. + */ +static int detach_fd = -1; + +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() && + setgid(user_gid) == -1) { + FormatFatalSystemError("Failed to set group %d", + (int)user_gid); + } + +#ifdef _BSD_SOURCE + /* init supplementary groups + * (must be done before we change our uid) + */ + if (!had_group && + /* no need to set the new user's supplementary groups if + we are already this user */ + user_uid != getuid() && + 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); + } +} + +void +daemonize_begin(bool detach) +{ + /* release the current working directory */ + if (chdir("/") < 0) + FatalError("problems changing to root directory"); + + if (!detach) + /* the rest of this function deals with detaching the + process */ + return; + + /* do this before daemonizing so we can fail gracefully if we + can't write to the pid file */ + PidFile pidfile2(pidfile); + + /* flush all file handles before duplicating the buffers */ + + fflush(nullptr); + + /* create a pipe to synchronize the parent and the child */ + + int fds[2]; + if (pipe(fds) < 0) + FatalSystemError("pipe() failed"); + + /* move to a child process */ + + pid_t pid = fork(); + if (pid < 0) + FatalSystemError("fork() failed"); + + if (pid == 0) { + /* in the child process */ + + pidfile2.Close(); + close(fds[0]); + detach_fd = fds[1]; + + /* detach from the current session */ + setsid(); + + /* continue starting MPD */ + return; + } + + /* in the parent process */ + + close(fds[1]); + + int result; + ssize_t nbytes = read(fds[0], &result, sizeof(result)); + if (nbytes == (ssize_t)sizeof(result)) { + /* the child process was successful */ + pidfile2.Write(pid); + exit(EXIT_SUCCESS); + } + + /* something bad happened in the child process */ + + pidfile2.Delete(pidfile); + + int status; + pid_t pid2 = waitpid(pid, &status, 0); + if (pid2 < 0) + FatalSystemError("waitpid() failed"); + + if (WIFSIGNALED(status)) + FormatFatalError("MPD died from signal %d%s", WTERMSIG(status), + WCOREDUMP(status) ? " (core dumped)" : ""); + + exit(WEXITSTATUS(status)); +} + +void +daemonize_commit() +{ + if (detach_fd >= 0) { + /* tell the parent process to let go of us and exit + indicating success */ + int result = 0; + write(detach_fd, &result, sizeof(result)); + close(detach_fd); + } else + /* the pidfile was not written by the parent because + there is no parent - do it now */ + PidFile(pidfile).Write(); +} + +void +daemonize_init(const char *user, const char *group, AllocatedPath &&_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 = strdup(user); + + /* this is needed by libs such as arts */ + 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 = AllocatedPath::Null(); + } + + free(user_name); +} + +#endif diff --git a/src/unix/Daemon.hxx b/src/unix/Daemon.hxx new file mode 100644 index 000000000..fe5681511 --- /dev/null +++ b/src/unix/Daemon.hxx @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 AllocatedPath; + +#ifndef WIN32 +void +daemonize_init(const char *user, const char *group, AllocatedPath &&pidfile); +#else +static inline void +daemonize_init(const char *user, const char *group, AllocatedPath &&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_begin(bool detach); +#else +static inline void +daemonize_begin(bool detach) +{ (void)detach; } +#endif + +#ifndef WIN32 +void +daemonize_commit(); +#else +static inline void +daemonize_commit() {} +#endif + +#endif diff --git a/src/unix/PidFile.hxx b/src/unix/PidFile.hxx new file mode 100644 index 000000000..a242c7810 --- /dev/null +++ b/src/unix/PidFile.hxx @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PID_FILE_HXX +#define MPD_PID_FILE_HXX + +#include "fs/FileSystem.hxx" +#include "fs/AllocatedPath.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> + +class PidFile { + FILE *file; + +public: + PidFile(const AllocatedPath &path):file(nullptr) { + if (path.IsNull()) + return; + + file = FOpen(path, "w"); + if (file == nullptr) { + const std::string utf8 = path.ToUTF8(); + FormatFatalSystemError("Failed to create pid file \"%s\"", + path.c_str()); + } + } + + PidFile(const PidFile &) = delete; + + void Close() { + if (file == nullptr) + return; + + fclose(file); + } + + void Delete(const AllocatedPath &path) { + if (file == nullptr) { + assert(path.IsNull()); + return; + } + + assert(!path.IsNull()); + + fclose(file); + RemoveFile(path); + } + + void Write(pid_t pid) { + if (file == nullptr) + return; + + fprintf(file, "%lu\n", (unsigned long)pid); + fclose(file); + } + + void Write() { + if (file == nullptr) + return; + + Write(getpid()); + } +}; + +#endif diff --git a/src/unix/SignalHandlers.cxx b/src/unix/SignalHandlers.cxx new file mode 100644 index 000000000..4aef4fa71 --- /dev/null +++ b/src/unix/SignalHandlers.cxx @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "event/Loop.hxx" +#include "system/FatalError.hxx" +#include "util/Domain.hxx" + +#include <signal.h> + +static constexpr Domain signal_handlers_domain("signal_handlers"); + +static void +HandleShutdownSignal() +{ + SignalMonitorGetEventLoop().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); + + SignalMonitorRegister(SIGINT, HandleShutdownSignal); + SignalMonitorRegister(SIGTERM, HandleShutdownSignal); + + SignalMonitorRegister(SIGHUP, handle_reload_event); +#endif +} + +void +SignalHandlersFinish() +{ + SignalMonitorFinish(); +} diff --git a/src/unix/SignalHandlers.hxx b/src/unix/SignalHandlers.hxx new file mode 100644 index 000000000..551b373c1 --- /dev/null +++ b/src/unix/SignalHandlers.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/util/Alloc.cxx b/src/util/Alloc.cxx new file mode 100644 index 000000000..ec3579470 --- /dev/null +++ b/src/util/Alloc.cxx @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "Alloc.hxx" + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +gcc_noreturn +static void +oom() +{ + (void)write(STDERR_FILENO, "Out of memory\n", 14); + _exit(1); +} + +void * +xalloc(size_t size) +{ + void *p = malloc(size); + if (gcc_unlikely(p == nullptr)) + oom(); + + return p; +} + +void * +xmemdup(const void *s, size_t size) +{ + void *p = xalloc(size); + memcpy(p, s, size); + return p; +} + +char * +xstrdup(const char *s) +{ + char *p = strdup(s); + if (gcc_unlikely(p == nullptr)) + oom(); + + return p; +} + +char * +xstrndup(const char *s, size_t n) +{ +#ifdef WIN32 + char *p = (char *)xalloc(n + 1); + memcpy(p, s, n); + p[n] = 0; +#else + char *p = strndup(s, n); + if (gcc_unlikely(p == nullptr)) + oom(); +#endif + + return p; +} diff --git a/src/util/Alloc.hxx b/src/util/Alloc.hxx new file mode 100644 index 000000000..15c123b7a --- /dev/null +++ b/src/util/Alloc.hxx @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ALLOC_HXX +#define MPD_ALLOC_HXX + +#include "Compiler.h" + +#include <stddef.h> + +/** + * Allocate memory. Use free() to free it. + * + * This function never fails; in out-of-memory situations, it aborts + * the process. + */ +gcc_malloc +void * +xalloc(size_t size); + +/** + * Duplicate memory. Use free() to free it. + * + * This function never fails; in out-of-memory situations, it aborts + * the process. + */ +gcc_malloc gcc_nonnull_all +void * +xmemdup(const void *s, size_t size); + +/** + * Duplicate a string. Use free() to free it. + * + * This function never fails; in out-of-memory situations, it aborts + * the process. + */ +gcc_malloc gcc_nonnull_all +char * +xstrdup(const char *s); + +/** + * Duplicate a string. Use free() to free it. + * + * This function never fails; in out-of-memory situations, it aborts + * the process. + */ +gcc_malloc gcc_nonnull_all +char * +xstrndup(const char *s, size_t n); + +#endif diff --git a/src/util/ByteReverse.cxx b/src/util/ByteReverse.cxx index 910c1e2a5..5cc8692a7 100644 --- a/src/util/ByteReverse.cxx +++ b/src/util/ByteReverse.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/util/ByteReverse.hxx b/src/util/ByteReverse.hxx index d6380213a..0c060c0cb 100644 --- a/src/util/ByteReverse.hxx +++ b/src/util/ByteReverse.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/util/Cast.hxx b/src/util/Cast.hxx new file mode 100644 index 000000000..69172e6de --- /dev/null +++ b/src/util/Cast.hxx @@ -0,0 +1,58 @@ +/* + * 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 CAST_HXX +#define CAST_HXX + +#include <stddef.h> + +/** + * Offset the given pointer by the specified number of bytes. + */ +static constexpr void * +OffsetPointer(void *p, ptrdiff_t offset) +{ + return (char *)p + offset; +} + +template<typename T, typename U> +static constexpr T * +OffsetCast(U *p, ptrdiff_t offset) +{ + return reinterpret_cast<T *>(OffsetPointer(p, offset)); +} + +/** + * Cast the given pointer to a struct member to its parent structure. + */ +#define ContainerCast(p, container, attribute) \ + OffsetCast<container, decltype(((container*)nullptr)->attribute)>\ + ((p), -ptrdiff_t(offsetof(container, attribute))) + +#endif diff --git a/src/util/Clamp.hxx b/src/util/Clamp.hxx new file mode 100644 index 000000000..3217ef9f7 --- /dev/null +++ b/src/util/Clamp.hxx @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2012 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 CLAMP_HPP +#define CLAMP_HPP + +#include "Compiler.h" + +/** + * Clamps the specified value in a range. Returns #min or #max if the + * value is outside. + */ +template<typename T> +static inline constexpr const T & +Clamp(const T &value, const T &min, const T &max) +{ + return gcc_unlikely(value < min) + ? min + : (gcc_unlikely(value > max) + ? max : value); +} + +#endif diff --git a/src/util/ConstBuffer.hxx b/src/util/ConstBuffer.hxx new file mode 100644 index 000000000..bd3c405e5 --- /dev/null +++ b/src/util/ConstBuffer.hxx @@ -0,0 +1,118 @@ +/* + * 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 CONST_BUFFER_HPP +#define CONST_BUFFER_HPP + +#include "Compiler.h" + +#include <cstddef> + +#ifndef NDEBUG +#include <assert.h> +#endif + +/** + * A reference to a memory area that is read-only. + */ +template<typename T> +struct ConstBuffer { + typedef size_t size_type; + typedef const T *pointer_type; + typedef pointer_type const_pointer_type; + typedef pointer_type iterator; + typedef pointer_type const_iterator; + + pointer_type data; + size_type size; + + ConstBuffer() = default; + + constexpr ConstBuffer(std::nullptr_t):data(nullptr), size(0) {} + + constexpr ConstBuffer(pointer_type _data, size_type _size) + :data(_data), size(_size) {} + + constexpr static ConstBuffer Null() { + return ConstBuffer(nullptr, 0); + } + + /** + * Cast a ConstBuffer<void> to a ConstBuffer<T>. A "void" + * buffer records its size in bytes, and when casting to "T", + * the assertion below ensures that the size is a multiple of + * sizeof(T). + */ +#ifdef NDEBUG + constexpr +#endif + static ConstBuffer<T> FromVoid(ConstBuffer<void> other) { + static_assert(sizeof(T) > 0, "Empty base type"); +#ifndef NDEBUG + assert(other.size % sizeof(T) == 0); +#endif + return ConstBuffer<T>(pointer_type(other.data), + other.size / sizeof(T)); + } + + constexpr ConstBuffer<void> ToVoid() const { + static_assert(sizeof(T) > 0, "Empty base type"); + return ConstBuffer<void>(data, size * sizeof(T)); + } + + constexpr bool IsNull() const { + return data == nullptr; + } + + constexpr bool IsEmpty() const { + return size == 0; + } + + constexpr iterator begin() const { + return data; + } + + constexpr iterator end() const { + return data + size; + } + + constexpr const_iterator cbegin() const { + return data; + } + + constexpr const_iterator cend() const { + return data + size; + } + + constexpr operator ConstBuffer<void>() const { + return { data, size }; + } +}; + +#endif diff --git a/src/util/Domain.hxx b/src/util/Domain.hxx index bbdbf8371..6dce7b731 100644 --- a/src/util/Domain.hxx +++ b/src/util/Domain.hxx @@ -1,24 +1,34 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org + * Copyright (C) 2013 Max Kellermann <max@duempel.org> * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * - 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_DOMAIN_HXX -#define MPD_DOMAIN_HXX +#ifndef DOMAIN_HXX +#define DOMAIN_HXX class Domain { const char *const name; diff --git a/src/util/DynamicFifoBuffer.hxx b/src/util/DynamicFifoBuffer.hxx new file mode 100644 index 000000000..df50328b4 --- /dev/null +++ b/src/util/DynamicFifoBuffer.hxx @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2003-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 FIFO_BUFFER_HPP +#define FIFO_BUFFER_HPP + +#include "WritableBuffer.hxx" + +#include <utility> +#include <algorithm> + +#include <assert.h> +#include <stddef.h> +#include <stdint.h> + +/** + * A first-in-first-out buffer: you can append data at the end, and + * read data from the beginning. This class automatically shifts the + * buffer as needed. It is not thread safe. + */ +template<typename T> +class DynamicFifoBuffer { +public: + typedef size_t size_type; + typedef WritableBuffer<T> Range; + typedef typename Range::pointer_type pointer_type; + typedef typename Range::const_pointer_type const_pointer_type; + +protected: + size_type head, tail, capacity; + T *data; + +public: + explicit DynamicFifoBuffer(size_type _capacity) + :head(0), tail(0), capacity(_capacity), + data(new T[capacity]) {} + ~DynamicFifoBuffer() { + delete[] data; + } + + DynamicFifoBuffer(const DynamicFifoBuffer &) = delete; + + size_type GetCapacity() { + return capacity; + } + + void Grow(size_type new_capacity) { + assert(new_capacity > capacity); + + T *new_data = new T[new_capacity]; + std::move(data + head, data + tail, new_data); + delete[] data; + data = new_data; + capacity = new_capacity; + tail -= head; + head = 0; + } + + void Clear() { + head = tail = 0; + } + + bool IsEmpty() const { + return head == tail; + } + + bool IsFull() const { + return head == 0 && tail == capacity; + } + + /** + * Prepares writing. Returns a buffer range which may be written. + * When you are finished, call append(). + */ + Range Write() { + Shift(); + return Range(data + tail, capacity - tail); + } + + /** + * Expands the tail of the buffer, after data has been written to + * the buffer returned by write(). + */ + void Append(size_type n) { + assert(tail <= capacity); + assert(n <= capacity); + assert(tail + n <= capacity); + + tail += n; + } + + void WantWrite(size_type n) { + if (tail + n <= capacity) + /* enough space after the tail */ + return; + + const size_type in_use = tail - head; + const size_type required_capacity = in_use + n; + if (capacity >= required_capacity) { + Shift(); + } else { + size_type new_capacity = capacity; + do { + new_capacity <<= 1; + } while (new_capacity < required_capacity); + + Grow(new_capacity); + } + } + + /** + * Write data to the bfufer, growing it as needed. Returns a + * writable pointer. + */ + pointer_type Write(size_type n) { + WantWrite(n); + return data + tail; + } + + /** + * Append data to the buffer, growing it as needed. + */ + void Append(const_pointer_type p, size_type n) { + std::copy_n(p, n, Write(n)); + Append(n); + } + + /** + * Return a buffer range which may be read. The buffer pointer is + * writable, to allow modifications while parsing. + */ + Range Read() { + return Range(data + head, tail - head); + } + + /** + * Marks a chunk as consumed. + */ + void Consume(size_type n) { + assert(tail <= capacity); + assert(head <= tail); + assert(n <= tail); + assert(head + n <= tail); + + head += n; + } + + size_type Read(pointer_type p, size_type n) { + auto range = Read(); + if (n > range.size) + n = range.size; + std::copy_n(range.data, n, p); + Consume(n); + return n; + } + +protected: + void Shift() { + if (head == 0) + return; + + assert(head <= capacity); + assert(tail <= capacity); + assert(tail >= head); + + std::move(data + head, data + tail, data); + + tail -= head; + head = 0; + } +}; + +#endif diff --git a/src/util/Error.cxx b/src/util/Error.cxx index 5675f4d81..649276b20 100644 --- a/src/util/Error.cxx +++ b/src/util/Error.cxx @@ -1,31 +1,44 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org + * Copyright (C) 2013 Max Kellermann <max@duempel.org> * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * - 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 "Error.hxx" #include "Domain.hxx" +#ifdef WIN32 #include <glib.h> +#endif #include <errno.h> #include <stdarg.h> #include <stdio.h> +#include <string.h> const Domain errno_domain("errno"); @@ -70,7 +83,7 @@ Error::FormatPrefix(const char *fmt, ...) void Error::SetErrno(int e) { - Set(errno_domain, e, g_strerror(e)); + Set(errno_domain, e, strerror(e)); } void @@ -82,7 +95,7 @@ Error::SetErrno() void Error::SetErrno(int e, const char *prefix) { - Format(errno_domain, e, "%s: %s", prefix, g_strerror(e)); + Format(errno_domain, e, "%s: %s", prefix, strerror(e)); } void diff --git a/src/util/Error.hxx b/src/util/Error.hxx index ec8867c6c..898a8f1c1 100644 --- a/src/util/Error.hxx +++ b/src/util/Error.hxx @@ -1,30 +1,40 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project - * http://www.musicpd.org + * Copyright (C) 2013 Max Kellermann <max@duempel.org> * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * - 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_ERROR_HXX -#define MPD_ERROR_HXX +#ifndef ERROR_HXX +#define ERROR_HXX #include "check.h" #include "Compiler.h" #include <string> -#include <algorithm> +#include <utility> #include <assert.h> diff --git a/src/util/FormatString.cxx b/src/util/FormatString.cxx index c13d0fb52..d222a505c 100644 --- a/src/util/FormatString.cxx +++ b/src/util/FormatString.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,10 +19,13 @@ #include "FormatString.hxx" -#include <string.h> #include <stdio.h> #include <stdlib.h> +#ifdef WIN32 +#include <string.h> +#endif + char * FormatNewV(const char *fmt, va_list args) { diff --git a/src/util/FormatString.hxx b/src/util/FormatString.hxx index bb4263107..dc1ac3c67 100644 --- a/src/util/FormatString.hxx +++ b/src/util/FormatString.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/util/HugeAllocator.cxx b/src/util/HugeAllocator.cxx index d1c55c965..e7221da83 100644 --- a/src/util/HugeAllocator.cxx +++ b/src/util/HugeAllocator.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/util/HugeAllocator.hxx b/src/util/HugeAllocator.hxx index f44a6e3b8..ac8601c7b 100644 --- a/src/util/HugeAllocator.hxx +++ b/src/util/HugeAllocator.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/util/LazyRandomEngine.cxx b/src/util/LazyRandomEngine.cxx index 0f90ebb2e..b0aac913c 100644 --- a/src/util/LazyRandomEngine.cxx +++ b/src/util/LazyRandomEngine.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/util/LazyRandomEngine.hxx b/src/util/LazyRandomEngine.hxx index bfe4bb60c..4156b3bb1 100644 --- a/src/util/LazyRandomEngine.hxx +++ b/src/util/LazyRandomEngine.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/util/OptionDef.hxx b/src/util/OptionDef.hxx new file mode 100644 index 000000000..dd82154c4 --- /dev/null +++ b/src/util/OptionDef.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UTIL_OPTIONDEF_HXX +#define MPD_UTIL_OPTIONDEF_HXX + +/** + * Command line option definition. + */ +class OptionDef +{ + const char *long_option; + char short_option; + const char *desc; +public: + constexpr OptionDef(const char *_long_option, const char *_desc) + : long_option(_long_option), + short_option(0), + desc(_desc) { } + + constexpr OptionDef(const char *_long_option, + char _short_option, const char *_desc) + : long_option(_long_option), + short_option(_short_option), + desc(_desc) { } + + bool HasLongOption() const { return long_option != nullptr; } + bool HasShortOption() const { return short_option != 0; } + bool HasDescription() const { return desc != nullptr; } + + const char *GetLongOption() const { + assert(HasLongOption()); + return long_option; + } + + char GetShortOption() const { + assert(HasShortOption()); + return short_option; + } + + const char *GetDescription() const { + assert(HasDescription()); + return desc; + } +}; + +#endif diff --git a/src/util/OptionParser.cxx b/src/util/OptionParser.cxx new file mode 100644 index 000000000..b10008527 --- /dev/null +++ b/src/util/OptionParser.cxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "OptionParser.hxx" +#include "OptionDef.hxx" + +#include <string.h> + +bool OptionParser::CheckOption(const OptionDef &opt) +{ + assert(option != nullptr); + + if (is_long) + return opt.HasLongOption() && + strcmp(option, opt.GetLongOption()) == 0; + + return opt.HasShortOption() && + option[0] == opt.GetShortOption() && + option[1] == '\0'; +} + +bool OptionParser::ParseNext() +{ + assert(HasEntries()); + char *arg = *argv; + ++argv; + --argc; + if (arg[0] == '-') { + if (arg[1] == '-') { + option = arg + 2; + is_long = true; + } + else { + option = arg + 1; + is_long = false; + } + option_raw = arg; + return true; + } + option = nullptr; + option_raw = nullptr; + return false; +} diff --git a/src/util/OptionParser.hxx b/src/util/OptionParser.hxx new file mode 100644 index 000000000..b9d34adbb --- /dev/null +++ b/src/util/OptionParser.hxx @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UTIL_OPTIONPARSER_HXX +#define MPD_UTIL_OPTIONPARSER_HXX + +#include <assert.h> + +class OptionDef; + +/** + * Command line option parser. + */ +class OptionParser +{ + int argc; + char **argv; + char *option; + char *option_raw; + bool is_long; +public: + /** + * Constructs #OptionParser. + */ + OptionParser(int _argc, char **_argv) + : argc(_argc - 1), argv(_argv + 1), + option(nullptr), option_raw(nullptr), is_long(false) { } + + /** + * Checks if there are command line entries to process. + */ + bool HasEntries() const { return argc > 0; } + + /** + * Gets the last parsed option. + */ + char *GetOption() { + assert(option_raw != nullptr); + return option_raw; + } + + /** + * Checks if current option is a specified option. + */ + bool CheckOption(const OptionDef& opt); + + /** + * Checks if current option is a specified option + * or specified alternative option. + */ + bool CheckOption(const OptionDef& opt, const OptionDef &alt_opt) { + return CheckOption(opt) || CheckOption(alt_opt); + } + + /** + * Parses current command line entry. + * Returns true on success, false otherwise. + * Regardless of result, advances current position to the next + * command line entry. + */ + bool ParseNext(); + + /** + * Checks if specified string is a command line option. + */ + static bool IsOption(const char *s) { + assert(s != nullptr); + return s[0] == '-'; + } +}; + +#endif diff --git a/src/util/PeakBuffer.cxx b/src/util/PeakBuffer.cxx index d9b193dd1..e4624bbec 100644 --- a/src/util/PeakBuffer.cxx +++ b/src/util/PeakBuffer.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,46 +18,39 @@ */ #include "PeakBuffer.hxx" -#include "HugeAllocator.hxx" -#include "fifo_buffer.h" +#include "DynamicFifoBuffer.hxx" #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); + delete normal_buffer; + delete peak_buffer; } bool PeakBuffer::IsEmpty() const { - return (normal_buffer == nullptr || - fifo_buffer_is_empty(normal_buffer)) && - (peak_buffer == nullptr || - fifo_buffer_is_empty(peak_buffer)); + return (normal_buffer == nullptr || normal_buffer->IsEmpty()) && + (peak_buffer == nullptr || peak_buffer->IsEmpty()); } -const void * -PeakBuffer::Read(size_t *length_r) const +WritableBuffer<void> +PeakBuffer::Read() const { if (normal_buffer != nullptr) { - const void *p = fifo_buffer_read(normal_buffer, length_r); - if (p != nullptr) - return p; + const auto p = normal_buffer->Read(); + if (!p.IsEmpty()) + return p.ToVoid(); } if (peak_buffer != nullptr) { - const void *p = fifo_buffer_read(peak_buffer, length_r); - if (p != nullptr) - return p; + const auto p = peak_buffer->Read(); + if (!p.IsEmpty()) + return p.ToVoid(); } return nullptr; @@ -66,15 +59,15 @@ PeakBuffer::Read(size_t *length_r) const void PeakBuffer::Consume(size_t length) { - if (normal_buffer != nullptr && !fifo_buffer_is_empty(normal_buffer)) { - fifo_buffer_consume(normal_buffer, length); + if (normal_buffer != nullptr && !normal_buffer->IsEmpty()) { + normal_buffer->Consume(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); + if (peak_buffer != nullptr && !peak_buffer->IsEmpty()) { + peak_buffer->Consume(length); + if (peak_buffer->IsEmpty()) { + delete peak_buffer; peak_buffer = nullptr; } @@ -83,7 +76,7 @@ PeakBuffer::Consume(size_t length) } static size_t -AppendTo(fifo_buffer *buffer, const void *data, size_t length) +AppendTo(DynamicFifoBuffer<uint8_t> &buffer, const void *data, size_t length) { assert(data != nullptr); assert(length > 0); @@ -91,14 +84,13 @@ AppendTo(fifo_buffer *buffer, const void *data, size_t length) size_t total = 0; do { - size_t max_length; - void *p = fifo_buffer_write(buffer, &max_length); - if (p == nullptr) + const auto p = buffer.Write(); + if (p.IsEmpty()) break; - const size_t nbytes = std::min(length, max_length); - memcpy(p, data, nbytes); - fifo_buffer_append(buffer, nbytes); + const size_t nbytes = std::min(length, p.size); + memcpy(p.data, data, nbytes); + buffer.Append(nbytes); data = (const uint8_t *)data + nbytes; length -= nbytes; @@ -114,15 +106,15 @@ 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); + if (peak_buffer != nullptr && !peak_buffer->IsEmpty()) { + size_t nbytes = AppendTo(*peak_buffer, data, length); return nbytes == length; } if (normal_buffer == nullptr) - normal_buffer = fifo_buffer_new(normal_size); + normal_buffer = new DynamicFifoBuffer<uint8_t>(normal_size); - size_t nbytes = AppendTo(normal_buffer, data, length); + size_t nbytes = AppendTo(*normal_buffer, data, length); if (nbytes > 0) { data = (const uint8_t *)data + nbytes; length -= nbytes; @@ -132,13 +124,11 @@ PeakBuffer::Append(const void *data, size_t length) if (peak_buffer == nullptr) { if (peak_size > 0) - peak_buffer = (fifo_buffer *)HugeAllocate(peak_size); + peak_buffer = new DynamicFifoBuffer<uint8_t>(peak_size); if (peak_buffer == nullptr) return false; - - fifo_buffer_init(peak_buffer, peak_size); } - nbytes = AppendTo(peak_buffer, data, length); + nbytes = AppendTo(*peak_buffer, data, length); return nbytes == length; } diff --git a/src/util/PeakBuffer.hxx b/src/util/PeakBuffer.hxx index a3f385e3e..702a3dee0 100644 --- a/src/util/PeakBuffer.hxx +++ b/src/util/PeakBuffer.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,11 +20,14 @@ #ifndef MPD_PEAK_BUFFER_HXX #define MPD_PEAK_BUFFER_HXX +#include "WritableBuffer.hxx" #include "Compiler.h" #include <stddef.h> +#include <stdint.h> -struct fifo_buffer; +template<typename T> struct WritableBuffer; +template<typename T> class DynamicFifoBuffer; /** * A FIFO-like buffer that will allocate more memory on demand to @@ -34,7 +37,7 @@ struct fifo_buffer; class PeakBuffer { size_t normal_size, peak_size; - fifo_buffer *normal_buffer, *peak_buffer; + DynamicFifoBuffer<uint8_t> *normal_buffer, *peak_buffer; public: PeakBuffer(size_t _normal_size, size_t _peak_size) @@ -57,7 +60,9 @@ public: gcc_pure bool IsEmpty() const; - const void *Read(size_t *length_r) const; + gcc_pure + WritableBuffer<void> Read() const; + void Consume(size_t length); bool Append(const void *data, size_t length); diff --git a/src/util/RefCount.hxx b/src/util/RefCount.hxx index dff850036..02ef8818c 100644 --- a/src/util/RefCount.hxx +++ b/src/util/RefCount.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * Redistribution and use in source and binary forms, with or without diff --git a/src/util/SliceBuffer.hxx b/src/util/SliceBuffer.hxx index 6cde75f34..63ca087ae 100644 --- a/src/util/SliceBuffer.hxx +++ b/src/util/SliceBuffer.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/util/SplitString.cxx b/src/util/SplitString.cxx new file mode 100644 index 000000000..75e799279 --- /dev/null +++ b/src/util/SplitString.cxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "SplitString.hxx" + +#include <string.h> + +SplitString::SplitString(const char *s, char separator) + :first(nullptr) +{ + const char *x = strchr(s, separator); + if (x == nullptr) + return; + + size_t length = x - s; + second = x + 1; + + first = new char[length + 1]; + memcpy(first, s, length); + first[length] = 0; +} diff --git a/src/util/SplitString.hxx b/src/util/SplitString.hxx new file mode 100644 index 000000000..96ffb21ec --- /dev/null +++ b/src/util/SplitString.hxx @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SPLIT_STRING_HXX +#define MPD_SPLIT_STRING_HXX + +#include "Compiler.h" + +#include <assert.h> + +/** + * Split a given constant string at a separator character. Duplicates + * the first part to be able to null-terminate it. + */ +class SplitString { + char *first; + const char *second; + +public: + SplitString(const char *s, char separator); + + ~SplitString() { + delete[] first; + } + + /** + * Was the separator found? + */ + bool IsDefined() const { + return first != nullptr; + } + + /** + * Is the first part empty? + */ + bool IsEmpty() const { + assert(IsDefined()); + + return *first == 0; + } + + const char *GetFirst() const { + assert(IsDefined()); + + return first; + } + + const char *GetSecond() const { + assert(IsDefined()); + + return second; + } +}; + +#endif diff --git a/src/util/StringUtil.cxx b/src/util/StringUtil.cxx index 7e295bf90..57fc5188b 100644 --- a/src/util/StringUtil.cxx +++ b/src/util/StringUtil.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,6 +22,7 @@ #include "ASCII.hxx" #include <assert.h> +#include <string.h> const char * strchug_fast(const char *p) @@ -33,6 +34,13 @@ strchug_fast(const char *p) } bool +StringStartsWith(const char *haystack, const char *needle) +{ + const size_t length = strlen(needle); + return memcmp(haystack, needle, length) == 0; +} + +bool string_array_contains(const char *const* haystack, const char *needle) { assert(haystack != nullptr); diff --git a/src/util/StringUtil.hxx b/src/util/StringUtil.hxx index 1c67910a9..933d82c16 100644 --- a/src/util/StringUtil.hxx +++ b/src/util/StringUtil.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -40,6 +40,10 @@ strchug_fast(char *p) return const_cast<char *>(strchug_fast((const char *)p)); } +gcc_pure +bool +StringStartsWith(const char *haystack, const char *needle); + /** * Checks whether a string array contains the specified string. * diff --git a/src/util/Tokenizer.cxx b/src/util/Tokenizer.cxx index 1c8af23fd..993913b47 100644 --- a/src/util/Tokenizer.cxx +++ b/src/util/Tokenizer.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,11 +24,6 @@ #include "Error.hxx" #include "Domain.hxx" -#include <glib.h> - -#include <assert.h> -#include <string.h> - static constexpr Domain tokenizer_domain("tokenizer"); static inline bool diff --git a/src/util/Tokenizer.hxx b/src/util/Tokenizer.hxx index a689dc31d..bc92d3c7c 100644 --- a/src/util/Tokenizer.hxx +++ b/src/util/Tokenizer.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/util/UriUtil.cxx b/src/util/UriUtil.cxx index 2609db2cf..3864c5e83 100644 --- a/src/util/UriUtil.cxx +++ b/src/util/UriUtil.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,6 +27,16 @@ bool uri_has_scheme(const char *uri) return strstr(uri, "://") != nullptr; } +std::string +uri_get_scheme(const char *uri) +{ + const char *end = strstr(uri, "://"); + if (end == nullptr) + end = uri; + + return std::string(uri, end); +} + /* suffixes should be ascii only characters */ const char * uri_get_suffix(const char *uri) diff --git a/src/util/UriUtil.hxx b/src/util/UriUtil.hxx index 78d0a6bff..8e00f8cd8 100644 --- a/src/util/UriUtil.hxx +++ b/src/util/UriUtil.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -31,6 +31,13 @@ gcc_pure bool uri_has_scheme(const char *uri); +/** + * Returns the scheme name of the specified URI, or an empty string. + */ +gcc_pure +std::string +uri_get_scheme(const char *uri); + gcc_pure const char * uri_get_suffix(const char *uri); diff --git a/src/util/VarSize.hxx b/src/util/VarSize.hxx new file mode 100644 index 000000000..04f1bf580 --- /dev/null +++ b/src/util/VarSize.hxx @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2008-2014 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_VAR_SIZE_HXX +#define MPD_VAR_SIZE_HXX + +#include "Alloc.hxx" +#include "Compiler.h" + +#include <type_traits> +#include <utility> +#include <new> + +/** + * Allocate and construct a variable-size object. That is useful for + * example when you want to store a variable-length string as the last + * attribute without the overhead of a second allocation. + * + * @param T a struct/class with a variable-size last attribute + * @param declared_tail_size the declared size of the last element in + * #T + * @param real_tail_size the real required size of the last element in + * #T + */ +template<class T, typename... Args> +gcc_malloc +T * +NewVarSize(size_t declared_tail_size, size_t real_tail_size, Args&&... args) +{ + static_assert(std::is_standard_layout<T>::value, + "Not standard-layout"); + + /* determine the total size of this instance */ + size_t size = sizeof(T) - declared_tail_size + real_tail_size; + + /* allocate memory */ + T *instance = (T *)xalloc(size); + + /* call the constructor */ + new(instance) T(std::forward<Args>(args)...); + + return instance; +} + +template<typename T> +gcc_nonnull_all +void +DeleteVarSize(T *instance) +{ + /* call the destructor */ + instance->T::~T(); + + /* free memory */ + free(instance); +} + +#endif diff --git a/src/util/WritableBuffer.hxx b/src/util/WritableBuffer.hxx index 4e529cfad..7267813f5 100644 --- a/src/util/WritableBuffer.hxx +++ b/src/util/WritableBuffer.hxx @@ -32,7 +32,11 @@ #include "Compiler.h" -#include <stddef.h> +#include <cstddef> + +#ifndef NDEBUG +#include <assert.h> +#endif /** * A reference to a memory area that is writable. @@ -41,47 +45,72 @@ */ template<typename T> struct WritableBuffer { - typedef size_t size_type; - typedef T *pointer_type; - typedef const T *const_pointer_type; - typedef pointer_type iterator; - typedef const_pointer_type const_iterator; + typedef size_t size_type; + typedef T *pointer_type; + typedef const T *const_pointer_type; + typedef pointer_type iterator; + typedef const_pointer_type const_iterator; + + pointer_type data; + size_type size; + + WritableBuffer() = default; - pointer_type data; - size_type size; + constexpr WritableBuffer(std::nullptr_t):data(nullptr), size(0) {} - WritableBuffer() = default; + constexpr WritableBuffer(pointer_type _data, size_type _size) + :data(_data), size(_size) {} - constexpr WritableBuffer(pointer_type _data, size_type _size) - :data(_data), size(_size) {} + constexpr static WritableBuffer Null() { + return { nullptr, 0 }; + } + + /** + * Cast a WritableBuffer<void> to a WritableBuffer<T>. A "void" + * buffer records its size in bytes, and when casting to "T", + * the assertion below ensures that the size is a multiple of + * sizeof(T). + */ +#ifdef NDEBUG + constexpr +#endif + static WritableBuffer<T> FromVoid(WritableBuffer<void> other) { + static_assert(sizeof(T) > 0, "Empty base type"); +#ifndef NDEBUG + assert(other.size % sizeof(T) == 0); +#endif + return WritableBuffer<T>(pointer_type(other.data), + other.size / sizeof(T)); + } - constexpr static WritableBuffer Null() { - return { nullptr, 0 }; - } + constexpr WritableBuffer<void> ToVoid() const { + static_assert(sizeof(T) > 0, "Empty base type"); + return WritableBuffer<void>(data, size * sizeof(T)); + } - constexpr bool IsNull() const { - return data == nullptr; - } + constexpr bool IsNull() const { + return data == nullptr; + } - constexpr bool IsEmpty() const { - return size == 0; - } + constexpr bool IsEmpty() const { + return size == 0; + } - constexpr iterator begin() const { - return data; - } + constexpr iterator begin() const { + return data; + } - constexpr iterator end() const { - return data + size; - } + constexpr iterator end() const { + return data + size; + } - constexpr const_iterator cbegin() const { - return data; - } + constexpr const_iterator cbegin() const { + return data; + } - constexpr const_iterator cend() const { - return data + size; - } + constexpr const_iterator cend() const { + return data + size; + } }; #endif diff --git a/src/util/bit_reverse.c b/src/util/bit_reverse.c index ba8a23ef1..9226c4261 100644 --- a/src/util/bit_reverse.c +++ b/src/util/bit_reverse.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/util/bit_reverse.h b/src/util/bit_reverse.h index a02f01b3f..b39b02e92 100644 --- a/src/util/bit_reverse.h +++ b/src/util/bit_reverse.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/util/fifo_buffer.c b/src/util/fifo_buffer.c deleted file mode 100644 index 162ddf946..000000000 --- a/src/util/fifo_buffer.c +++ /dev/null @@ -1,218 +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; -} - -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 deleted file mode 100644 index ccea97d86..000000000 --- a/src/util/fifo_buffer.h +++ /dev/null @@ -1,164 +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; - -#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/util/growing_fifo.c b/src/util/growing_fifo.c deleted file mode 100644 index 88431f60e..000000000 --- a/src/util/growing_fifo.c +++ /dev/null @@ -1,90 +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 "growing_fifo.h" -#include "fifo_buffer.h" - -#include <assert.h> -#include <string.h> - -/** - * Align buffer sizes at 8 kB boundaries. Must be a power of two. - */ -static const size_t GROWING_FIFO_ALIGN = 8192; - -/** - * Align the specified size to the next #GROWING_FIFO_ALIGN boundary. - */ -static size_t -align(size_t size) -{ - return ((size - 1) | (GROWING_FIFO_ALIGN - 1)) + 1; -} - -struct fifo_buffer * -growing_fifo_new(void) -{ - return fifo_buffer_new(GROWING_FIFO_ALIGN); -} - -void * -growing_fifo_write(struct fifo_buffer **buffer_p, size_t length) -{ - assert(buffer_p != NULL); - - struct fifo_buffer *buffer = *buffer_p; - assert(buffer != NULL); - - size_t max_length; - void *p = fifo_buffer_write(buffer, &max_length); - if (p != NULL && max_length >= length) - return p; - - /* grow */ - size_t new_size = fifo_buffer_available(buffer) + length; - assert(new_size > fifo_buffer_capacity(buffer)); - *buffer_p = buffer = fifo_buffer_realloc(buffer, align(new_size)); - - /* try again */ - p = fifo_buffer_write(buffer, &max_length); - assert(p != NULL); - assert(max_length >= length); - - return p; -} - -void -growing_fifo_append(struct fifo_buffer **buffer_p, - const void *data, size_t length) -{ - void *p = growing_fifo_write(buffer_p, length); - memcpy(p, data, length); - fifo_buffer_append(*buffer_p, length); -} diff --git a/src/util/growing_fifo.h b/src/util/growing_fifo.h deleted file mode 100644 index 723c3b3ff..000000000 --- a/src/util/growing_fifo.h +++ /dev/null @@ -1,73 +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 - * - * Helper functions for our FIFO buffer library (fifo_buffer.h) that - * allows growing the buffer on demand. - * - * This library is not thread safe. - */ - -#ifndef MPD_GROWING_FIFO_H -#define MPD_GROWING_FIFO_H - -#include <stddef.h> - -struct fifo_buffer; - -/** - * Allocate a new #fifo_buffer with the default size. - */ -struct fifo_buffer * -growing_fifo_new(void); - -/** - * Prepares writing to the buffer, see fifo_buffer_write() for - * details. The difference is that this function will automatically - * grow the buffer if it is too small. - * - * The caller is responsible for limiting the capacity of the buffer. - * - * @param length the number of bytes that will be written - * @return a pointer to the end of the buffer (will not be NULL) - */ -void * -growing_fifo_write(struct fifo_buffer **buffer_p, size_t length); - -/** - * A helper function that combines growing_fifo_write(), memcpy(), - * fifo_buffer_append(). - */ -void -growing_fifo_append(struct fifo_buffer **buffer_p, - const void *data, size_t length); - -#endif diff --git a/src/util/list.h b/src/util/list.h index 73d99befa..b02e8b96d 100644 --- a/src/util/list.h +++ b/src/util/list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/util/list_sort.c b/src/util/list_sort.c index 8534f3360..d4eeb9b36 100644 --- a/src/util/list_sort.c +++ b/src/util/list_sort.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/util/list_sort.h b/src/util/list_sort.h index 7a65020b9..f0e6d7893 100644 --- a/src/util/list_sort.h +++ b/src/util/list_sort.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/win/mpd_win32_rc.rc.in b/src/win/mpd_win32_rc.rc.in deleted file mode 100644 index a31118a0c..000000000 --- a/src/win/mpd_win32_rc.rc.in +++ /dev/null @@ -1,34 +0,0 @@ -#include <windows.h> - -#define VERSION_NUMBER @VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@ -#define VERSION_NUMBER_STR "@VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@" - -MPD_ICON ICON "@top_srcdir@/src/win/mpd.ico" - -1 VERSIONINFO -FILETYPE VFT_APP -FILEOS VOS__WINDOWS32 -PRODUCTVERSION VERSION_NUMBER - -FILEVERSION VERSION_NUMBER -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904B0" - BEGIN - VALUE "CompanyName", "Music Player Daemon Project" - VALUE "ProductName", "Music Player Daemon" - VALUE "ProductVersion", VERSION_NUMBER_STR - VALUE "InternalName", "mpd" - VALUE "OriginalFilename", "mpd.exe" - VALUE "FileVersion", "@VERSION@" - VALUE "FileDescription", "Music Player Daemon @VERSION@" - VALUE "LegalCopyright", "Copyright \251 The Music Player Daemon Project" - END - END - - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 - END -END diff --git a/src/win32/Win32Main.cxx b/src/win32/Win32Main.cxx new file mode 100644 index 000000000..75a1e9a23 --- /dev/null +++ b/src/win32/Win32Main.cxx @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "Compiler.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}, + {nullptr, nullptr} +}; + +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, nullptr); + + 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/win/mpd.ico b/src/win32/mpd.ico Binary files differindex 86fd9fe43..86fd9fe43 100644 --- a/src/win/mpd.ico +++ b/src/win32/mpd.ico diff --git a/src/win32/mpd_win32_rc.rc.in b/src/win32/mpd_win32_rc.rc.in new file mode 100644 index 000000000..e5312dc78 --- /dev/null +++ b/src/win32/mpd_win32_rc.rc.in @@ -0,0 +1,34 @@ +#include <windows.h> + +#define VERSION_NUMBER @VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@ +#define VERSION_NUMBER_STR "@VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@" + +MPD_ICON ICON "@top_srcdir@/src/win32/mpd.ico" + +1 VERSIONINFO +FILETYPE VFT_APP +FILEOS VOS__WINDOWS32 +PRODUCTVERSION VERSION_NUMBER + +FILEVERSION VERSION_NUMBER +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "Music Player Daemon Project" + VALUE "ProductName", "Music Player Daemon" + VALUE "ProductVersion", VERSION_NUMBER_STR + VALUE "InternalName", "mpd" + VALUE "OriginalFilename", "mpd.exe" + VALUE "FileVersion", "@VERSION@" + VALUE "FileDescription", "Music Player Daemon @VERSION@" + VALUE "LegalCopyright", "Copyright \251 The Music Player Daemon Project" + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/zeroconf/AvahiPoll.cxx b/src/zeroconf/AvahiPoll.cxx new file mode 100644 index 000000000..20d5d74e6 --- /dev/null +++ b/src/zeroconf/AvahiPoll.cxx @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "AvahiPoll.hxx" +#include "event/SocketMonitor.hxx" +#include "event/TimeoutMonitor.hxx" + +static unsigned +FromAvahiWatchEvent(AvahiWatchEvent e) +{ + return (e & AVAHI_WATCH_IN ? SocketMonitor::READ : 0) | + (e & AVAHI_WATCH_OUT ? SocketMonitor::WRITE : 0) | + (e & AVAHI_WATCH_ERR ? SocketMonitor::ERROR : 0) | + (e & AVAHI_WATCH_HUP ? SocketMonitor::HANGUP : 0); +} + +static AvahiWatchEvent +ToAvahiWatchEvent(unsigned e) +{ + return AvahiWatchEvent((e & SocketMonitor::READ ? AVAHI_WATCH_IN : 0) | + (e & SocketMonitor::WRITE ? AVAHI_WATCH_OUT : 0) | + (e & SocketMonitor::ERROR ? AVAHI_WATCH_ERR : 0) | + (e & SocketMonitor::HANGUP ? AVAHI_WATCH_HUP : 0)); +} + +struct AvahiWatch final : private SocketMonitor { +private: + const AvahiWatchCallback callback; + void *const userdata; + + AvahiWatchEvent received; + +public: + AvahiWatch(int _fd, AvahiWatchEvent _event, + AvahiWatchCallback _callback, void *_userdata, + EventLoop &_loop) + :SocketMonitor(_fd, _loop), + callback(_callback), userdata(_userdata), + received(AvahiWatchEvent(0)) { + Schedule(FromAvahiWatchEvent(_event)); + } + + static void WatchUpdate(AvahiWatch *w, AvahiWatchEvent event) { + w->Schedule(FromAvahiWatchEvent(event)); + } + + static AvahiWatchEvent WatchGetEvents(AvahiWatch *w) { + return w->received; + } + + static void WatchFree(AvahiWatch *w) { + delete w; + } + +protected: + virtual bool OnSocketReady(unsigned flags) { + received = ToAvahiWatchEvent(flags); + callback(this, Get(), received, userdata); + received = AvahiWatchEvent(0); + return true; + } +}; + +static constexpr unsigned +TimevalToMS(const timeval &tv) +{ + return tv.tv_sec * 1000 + (tv.tv_usec + 500) / 1000; +} + +struct AvahiTimeout final : private TimeoutMonitor { +private: + const AvahiTimeoutCallback callback; + void *const userdata; + +public: + AvahiTimeout(const struct timeval *tv, + AvahiTimeoutCallback _callback, void *_userdata, + EventLoop &_loop) + :TimeoutMonitor(_loop), + callback(_callback), userdata(_userdata) { + if (tv != nullptr) + Schedule(TimevalToMS(*tv)); + } + + static void TimeoutUpdate(AvahiTimeout *t, const struct timeval *tv) { + if (tv != nullptr) + t->Schedule(TimevalToMS(*tv)); + else + t->Cancel(); + } + + static void TimeoutFree(AvahiTimeout *t) { + delete t; + } + +protected: + virtual void OnTimeout() { + callback(this, userdata); + } +}; + +MyAvahiPoll::MyAvahiPoll(EventLoop &_loop):event_loop(_loop) +{ + watch_new = WatchNew; + watch_update = AvahiWatch::WatchUpdate; + watch_get_events = AvahiWatch::WatchGetEvents; + watch_free = AvahiWatch::WatchFree; + timeout_new = TimeoutNew; + timeout_update = AvahiTimeout::TimeoutUpdate; + timeout_free = AvahiTimeout::TimeoutFree; +} + +AvahiWatch * +MyAvahiPoll::WatchNew(const AvahiPoll *api, int fd, AvahiWatchEvent event, + AvahiWatchCallback callback, void *userdata) { + const MyAvahiPoll &poll = *(const MyAvahiPoll *)api; + + return new AvahiWatch(fd, event, callback, userdata, + poll.event_loop); +} + +AvahiTimeout * +MyAvahiPoll::TimeoutNew(const AvahiPoll *api, const struct timeval *tv, + AvahiTimeoutCallback callback, void *userdata) { + const MyAvahiPoll &poll = *(const MyAvahiPoll *)api; + + return new AvahiTimeout(tv, callback, userdata, + poll.event_loop); +} diff --git a/src/zeroconf/AvahiPoll.hxx b/src/zeroconf/AvahiPoll.hxx new file mode 100644 index 000000000..e194d3370 --- /dev/null +++ b/src/zeroconf/AvahiPoll.hxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_AVAHI_POLL_HXX +#define MPD_AVAHI_POLL_HXX + +#include "check.h" +#include "Compiler.h" + +#include <avahi-common/watch.h> + +class EventLoop; + +class MyAvahiPoll final : public AvahiPoll { + EventLoop &event_loop; + +public: + MyAvahiPoll(EventLoop &_loop); + +private: + static AvahiWatch *WatchNew(const AvahiPoll *api, int fd, + AvahiWatchEvent event, + AvahiWatchCallback callback, + void *userdata); + + static AvahiTimeout *TimeoutNew(const AvahiPoll *api, + const struct timeval *tv, + AvahiTimeoutCallback callback, + void *userdata); +}; + +#endif diff --git a/src/zeroconf/ZeroconfAvahi.cxx b/src/zeroconf/ZeroconfAvahi.cxx new file mode 100644 index 000000000..8cd09cbed --- /dev/null +++ b/src/zeroconf/ZeroconfAvahi.cxx @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "AvahiPoll.hxx" +#include "ZeroconfInternal.hxx" +#include "Listen.hxx" +#include "system/FatalError.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +#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> + +static constexpr Domain avahi_domain("avahi"); + +static char *avahiName; +static bool avahi_running; +static MyAvahiPoll *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 */ + FormatDefault(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; + + FormatDefault(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 */ + avahi_running = false; + 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, nullptr); + 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, nullptr, + nullptr, listen_port, nullptr); + 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: + avahi_running = false; +} + +/* 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) { + LogDefault(avahi_domain, + "Client Disconnected, will reconnect shortly"); + if (avahiGroup) { + avahi_entry_group_free(avahiGroup); + avahiGroup = nullptr; + } + if (avahiClient) + avahi_client_free(avahiClient); + avahiClient = + avahi_client_new(avahi_poll, + AVAHI_CLIENT_NO_FAIL, + avahiClientCallback, nullptr, + &reason); + if (!avahiClient) { + FormatWarning(avahi_domain, + "Could not reconnect: %s", + avahi_strerror(reason)); + avahi_running = false; + } + } else { + FormatWarning(avahi_domain, + "Client failure: %s (terminal)", + avahi_strerror(reason)); + avahi_running = false; + } + 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); + + avahi_running = true; + + avahi_poll = new MyAvahiPoll(loop); + + int error; + avahiClient = avahi_client_new(avahi_poll, AVAHI_CLIENT_NO_FAIL, + avahiClientCallback, nullptr, &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 = nullptr; + } + + if (avahiClient) { + avahi_client_free(avahiClient); + avahiClient = nullptr; + } + + delete avahi_poll; + avahi_poll = nullptr; + + avahi_free(avahiName); + avahiName = nullptr; +} diff --git a/src/zeroconf/ZeroconfAvahi.hxx b/src/zeroconf/ZeroconfAvahi.hxx new file mode 100644 index 000000000..09a199f55 --- /dev/null +++ b/src/zeroconf/ZeroconfAvahi.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/zeroconf/ZeroconfBonjour.cxx b/src/zeroconf/ZeroconfBonjour.cxx new file mode 100644 index 000000000..5b5de1247 --- /dev/null +++ b/src/zeroconf/ZeroconfBonjour.cxx @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "Compiler.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() { + 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, nullptr, nullptr, + g_htons(listen_port), 0, + nullptr, + dnsRegisterCallback, + nullptr); + + if (error != kDNSServiceErr_NoError) { + LogError(bonjour_domain, + "Failed to register zeroconf service"); + + if (dnsReference) { + DNSServiceRefDeallocate(dnsReference); + dnsReference = nullptr; + } + return; + } + + bonjour_monitor = new BonjourMonitor(loop, dnsReference); +} + +void +BonjourDeinit() +{ + delete bonjour_monitor; +} diff --git a/src/zeroconf/ZeroconfBonjour.hxx b/src/zeroconf/ZeroconfBonjour.hxx new file mode 100644 index 000000000..cff52815e --- /dev/null +++ b/src/zeroconf/ZeroconfBonjour.hxx @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/zeroconf/ZeroconfGlue.cxx b/src/zeroconf/ZeroconfGlue.cxx new file mode 100644 index 000000000..95797491b --- /dev/null +++ b/src/zeroconf/ZeroconfGlue.cxx @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" +#include "Listen.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" +#include "Compiler.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) { + LogWarning(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/zeroconf/ZeroconfGlue.hxx b/src/zeroconf/ZeroconfGlue.hxx new file mode 100644 index 000000000..5d2f29642 --- /dev/null +++ b/src/zeroconf/ZeroconfGlue.hxx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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/zeroconf/ZeroconfInternal.hxx b/src/zeroconf/ZeroconfInternal.hxx new file mode 100644 index 000000000..4d47d260a --- /dev/null +++ b/src/zeroconf/ZeroconfInternal.hxx @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License 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/mpd.service.in b/systemd/mpd.service.in index 65fffa7bb..65fffa7bb 100644 --- a/mpd.service.in +++ b/systemd/mpd.service.in diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 000000000..132ce5fcf --- /dev/null +++ b/test/.gitignore @@ -0,0 +1 @@ +/run_neighbor_explorer diff --git a/test/DumpDatabase.cxx b/test/DumpDatabase.cxx index 21a12d294..0be1191d2 100644 --- a/test/DumpDatabase.cxx +++ b/test/DumpDatabase.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,16 +18,18 @@ */ #include "config.h" -#include "DatabaseRegistry.hxx" -#include "DatabasePlugin.hxx" -#include "DatabaseSelection.hxx" -#include "Directory.hxx" -#include "Song.hxx" +#include "db/Registry.hxx" +#include "db/DatabasePlugin.hxx" +#include "db/Selection.hxx" +#include "db/DatabaseListener.hxx" +#include "db/LightDirectory.hxx" +#include "db/LightSong.hxx" #include "PlaylistVector.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigData.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigData.hxx" #include "tag/TagConfig.hxx" #include "fs/Path.hxx" +#include "event/Loop.hxx" #include "util/Error.hxx" #include <glib.h> @@ -39,35 +41,45 @@ 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) +#ifdef HAVE_LIBUPNP +#include "input/InputStream.hxx" +size_t +InputStream::LockRead(void *, size_t, Error &) { - if (log_domain != NULL) - g_printerr("%s: %s\n", log_domain, message); - else - g_printerr("%s\n", message); + return 0; } +#endif + +class MyDatabaseListener final : public DatabaseListener { +public: + virtual void OnDatabaseModified() override { + cout << "DatabaseModified" << endl; + } +}; static bool -DumpDirectory(const Directory &directory, Error &) +DumpDirectory(const LightDirectory &directory, Error &) { - cout << "D " << directory.path << endl; + cout << "D " << directory.GetPath() << endl; return true; } static bool -DumpSong(Song &song, Error &) +DumpSong(const LightSong &song, Error &) { - cout << "S " << song.parent->path << "/" << song.uri << endl; + cout << "S "; + if (song.directory != nullptr) + cout << song.directory << "/"; + cout << song.uri << endl; return true; } static bool DumpPlaylist(const PlaylistInfo &playlist, - const Directory &directory, Error &) + const LightDirectory &directory, Error &) { - cout << "P " << directory.path << "/" << playlist.name.c_str() << endl; + cout << "P " << directory.GetPath() + << "/" << playlist.name.c_str() << endl; return true; } @@ -94,8 +106,6 @@ main(int argc, char **argv) g_thread_init(nullptr); #endif - g_log_set_default_handler(my_log_func, nullptr); - /* initialize MPD */ config_global_init(); @@ -108,14 +118,18 @@ main(int argc, char **argv) TagLoadConfig(); + EventLoop event_loop; + MyDatabaseListener database_listener; + /* do it */ const struct config_param *path = config_get_param(CONF_DB_FILE); - config_param param("database", path->line); + config_param param("database", path != nullptr ? path->line : -1); if (path != nullptr) param.AddBlockParam("path", path->value.c_str(), path->line); - Database *db = plugin->create(param, error); + Database *db = plugin->create(event_loop, database_listener, + param, error); if (db == nullptr) { cerr << error.GetMessage() << endl; diff --git a/test/FakeDecoderAPI.cxx b/test/FakeDecoderAPI.cxx new file mode 100644 index 000000000..88db4a268 --- /dev/null +++ b/test/FakeDecoderAPI.cxx @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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/DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "util/Error.hxx" +#include "Compiler.h" + +#include <glib.h> + +#include <unistd.h> + +void +decoder_initialized(gcc_unused Decoder &decoder, + gcc_unused const AudioFormat audio_format, + gcc_unused bool seekable, + gcc_unused float total_time) +{ +} + +DecoderCommand +decoder_get_command(gcc_unused Decoder &decoder) +{ + return DecoderCommand::NONE; +} + +void +decoder_command_finished(gcc_unused Decoder &decoder) +{ +} + +double +decoder_seek_where(gcc_unused Decoder &decoder) +{ + return 1.0; +} + +void +decoder_seek_error(gcc_unused Decoder &decoder) +{ +} + +size_t +decoder_read(gcc_unused Decoder *decoder, + InputStream &is, + void *buffer, size_t length) +{ + return is.LockRead(buffer, length, IgnoreError()); +} + +bool +decoder_read_full(Decoder *decoder, InputStream &is, + void *_buffer, size_t size) +{ + uint8_t *buffer = (uint8_t *)_buffer; + + while (size > 0) { + size_t nbytes = decoder_read(decoder, is, buffer, size); + if (nbytes == 0) + return false; + + buffer += nbytes; + size -= nbytes; + } + + return true; +} + +bool +decoder_skip(Decoder *decoder, InputStream &is, size_t size) +{ + while (size > 0) { + char buffer[1024]; + size_t nbytes = decoder_read(decoder, is, buffer, + std::min(sizeof(buffer), size)); + if (nbytes == 0) + return false; + + size -= nbytes; + } + + return true; +} + +void +decoder_timestamp(gcc_unused Decoder &decoder, + gcc_unused double t) +{ +} + +DecoderCommand +decoder_data(gcc_unused Decoder &decoder, + gcc_unused InputStream *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 Decoder &decoder, + gcc_unused InputStream *is, + gcc_unused Tag &&tag) +{ + return DecoderCommand::NONE; +} + +void +decoder_replay_gain(gcc_unused Decoder &decoder, + const ReplayGainInfo *rgi) +{ + const ReplayGainTuple *tuple = &rgi->tuples[REPLAY_GAIN_ALBUM]; + if (tuple->IsDefined()) + fprintf(stderr, "replay_gain[album]: gain=%f peak=%f\n", + tuple->gain, tuple->peak); + + tuple = &rgi->tuples[REPLAY_GAIN_TRACK]; + if (tuple->IsDefined()) + fprintf(stderr, "replay_gain[track]: gain=%f peak=%f\n", + tuple->gain, tuple->peak); +} + +void +decoder_mixramp(gcc_unused Decoder &decoder, gcc_unused MixRampInfo &&mix_ramp) +{ +} diff --git a/test/FakeReplayGainConfig.cxx b/test/FakeReplayGainConfig.cxx index 3305b79a3..0cb282050 100644 --- a/test/FakeReplayGainConfig.cxx +++ b/test/FakeReplayGainConfig.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/FakeSong.cxx b/test/FakeSong.cxx deleted file mode 100644 index c0859d7b1..000000000 --- a/test/FakeSong.cxx +++ /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. - */ - -#include "config.h" -#include "Song.hxx" -#include "directory.h" -#include "Compiler.h" - -#include <stdlib.h> - -struct directory detached_root; - -Song * -song_dup_detached(gcc_unused const Song *src) -{ - abort(); -} diff --git a/test/ShutdownHandler.cxx b/test/ShutdownHandler.cxx index 341a92939..c04834444 100644 --- a/test/ShutdownHandler.cxx +++ b/test/ShutdownHandler.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/ShutdownHandler.hxx b/test/ShutdownHandler.hxx index 0a84ed63f..e4e900e5d 100644 --- a/test/ShutdownHandler.hxx +++ b/test/ShutdownHandler.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/dump_playlist.cxx b/test/dump_playlist.cxx index d11562930..d8a7c4bfa 100644 --- a/test/dump_playlist.cxx +++ b/test/dump_playlist.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,17 +19,15 @@ #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 "DetachedSong.hxx" +#include "playlist/SongEnumerator.hxx" +#include "input/InputStream.hxx" +#include "config/ConfigGlobal.hxx" +#include "decoder/DecoderList.hxx" +#include "input/Init.hxx" #include "IOThread.hxx" -#include "PlaylistRegistry.hxx" -#include "PlaylistPlugin.hxx" +#include "playlist/PlaylistRegistry.hxx" +#include "playlist/PlaylistPlugin.hxx" #include "fs/Path.hxx" #include "util/Error.hxx" #include "thread/Cond.hxx" @@ -40,110 +38,14 @@ #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 Decoder &decoder, - gcc_unused const AudioFormat audio_format, - gcc_unused bool seekable, - gcc_unused float total_time) -{ -} - -DecoderCommand -decoder_get_command(gcc_unused Decoder &decoder) -{ - return DecoderCommand::NONE; -} - -void -decoder_command_finished(gcc_unused Decoder &decoder) -{ -} - -double -decoder_seek_where(gcc_unused Decoder &decoder) -{ - return 1.0; -} - -void -decoder_seek_error(gcc_unused Decoder &decoder) -{ -} - -size_t -decoder_read(gcc_unused Decoder *decoder, - InputStream &is, - void *buffer, size_t length) -{ - return is.LockRead(buffer, length, IgnoreError()); -} - -void -decoder_timestamp(gcc_unused Decoder &decoder, - gcc_unused double t) -{ -} - -DecoderCommand -decoder_data(gcc_unused Decoder &decoder, - gcc_unused InputStream *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 Decoder &decoder, - gcc_unused InputStream *is, - gcc_unused Tag &&tag) -{ - return DecoderCommand::NONE; -} - -void -decoder_replay_gain(gcc_unused Decoder &decoder, - const ReplayGainInfo *rgi) -{ - const ReplayGainTuple *tuple = &rgi->tuples[REPLAY_GAIN_ALBUM]; - if (tuple->IsDefined()) - g_printerr("replay_gain[album]: gain=%f peak=%f\n", - tuple->gain, tuple->peak); - - tuple = &rgi->tuples[REPLAY_GAIN_TRACK]; - if (tuple->IsDefined()) - g_printerr("replay_gain[track]: gain=%f peak=%f\n", - tuple->gain, tuple->peak); -} - -void -decoder_mixramp(gcc_unused Decoder &decoder, gcc_unused MixRampInfo &&mix_ramp) -{ -} - int main(int argc, char **argv) { const char *uri; InputStream *is = NULL; - Song *song; if (argc != 3) { - g_printerr("Usage: dump_playlist CONFIG URI\n"); - return 1; + fprintf(stderr, "Usage: dump_playlist CONFIG URI\n"); + return EXIT_FAILURE; } const Path config_path = Path::FromFS(argv[1]); @@ -155,16 +57,14 @@ int main(int argc, char **argv) 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; + LogError(error); + return EXIT_FAILURE; } io_thread_init(); @@ -172,7 +72,7 @@ int main(int argc, char **argv) if (!input_stream_global_init(error)) { LogError(error); - return 2; + return EXIT_FAILURE; } playlist_list_global_init(); @@ -187,47 +87,49 @@ int main(int argc, char **argv) if (playlist == NULL) { /* open the stream and wait until it becomes ready */ - is = InputStream::Open(uri, mutex, cond, error); + is = InputStream::OpenReady(uri, mutex, cond, error); if (is == NULL) { if (error.IsDefined()) LogError(error); else - g_printerr("InputStream::Open() failed\n"); + fprintf(stderr, + "input/InputStream::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"); + fprintf(stderr, "Failed to open playlist\n"); return 2; } } /* dump the playlist */ + DetachedSong *song; 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(); + printf("%s\n", song->GetURI()); + + const unsigned start_ms = song->GetStartMS(); + const unsigned end_ms = song->GetEndMS(); + + if (end_ms > 0) + printf("range: %u:%02u..%u:%02u\n", + start_ms / 60000, + (start_ms / 1000) % 60, + end_ms / 60000, + (end_ms / 1000) % 60); + else if (start_ms > 0) + printf("range: %u:%02u..\n", + start_ms / 60000, + (start_ms / 1000) % 60); + + tag_save(stdout, song->GetTag()); + + delete song; } /* deinitialize everything */ diff --git a/test/dump_rva2.cxx b/test/dump_rva2.cxx index e1ba5336a..fd46ee36c 100644 --- a/test/dump_rva2.cxx +++ b/test/dump_rva2.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,19 +21,19 @@ #include "tag/TagId3.hxx" #include "tag/TagRva2.hxx" #include "ReplayGainInfo.hxx" -#include "ConfigGlobal.hxx" +#include "config/ConfigGlobal.hxx" #include "util/Error.hxx" #include "fs/Path.hxx" +#include "Log.hxx" #include <id3tag.h> -#include <glib.h> - #ifdef HAVE_LOCALE_H #include <locale.h> #endif #include <stdlib.h> +#include <stdio.h> const char * config_get_string(gcc_unused enum ConfigOption option, @@ -50,8 +50,8 @@ int main(int argc, char **argv) #endif if (argc != 2) { - g_printerr("Usage: read_rva2 FILE\n"); - return 1; + fprintf(stderr, "Usage: read_rva2 FILE\n"); + return EXIT_FAILURE; } const char *path = argv[1]; @@ -60,9 +60,9 @@ int main(int argc, char **argv) struct id3_tag *tag = tag_id3_load(Path::FromFS(path), error); if (tag == NULL) { if (error.IsDefined()) - g_printerr("%s\n", error.GetMessage()); + LogError(error); else - g_printerr("No ID3 tag found\n"); + fprintf(stderr, "No ID3 tag found\n"); return EXIT_FAILURE; } @@ -74,19 +74,19 @@ int main(int argc, char **argv) id3_tag_delete(tag); if (!success) { - g_printerr("No RVA2 tag found\n"); + fprintf(stderr, "No RVA2 tag found\n"); return EXIT_FAILURE; } const ReplayGainTuple *tuple = &replay_gain.tuples[REPLAY_GAIN_ALBUM]; if (tuple->IsDefined()) - g_printerr("replay_gain[album]: gain=%f peak=%f\n", - tuple->gain, tuple->peak); + fprintf(stderr, "replay_gain[album]: gain=%f peak=%f\n", + tuple->gain, tuple->peak); tuple = &replay_gain.tuples[REPLAY_GAIN_TRACK]; if (tuple->IsDefined()) - g_printerr("replay_gain[track]: gain=%f peak=%f\n", - tuple->gain, tuple->peak); + fprintf(stderr, "replay_gain[track]: gain=%f peak=%f\n", + tuple->gain, tuple->peak); return EXIT_SUCCESS; } diff --git a/test/dump_text_file.cxx b/test/dump_text_file.cxx index bb84f5cce..e664c5a1b 100644 --- a/test/dump_text_file.cxx +++ b/test/dump_text_file.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,17 +19,17 @@ #include "config.h" #include "IOThread.hxx" -#include "InputInit.hxx" -#include "InputStream.hxx" -#include "ConfigGlobal.hxx" +#include "input/Init.hxx" +#include "input/InputStream.hxx" +#include "input/TextInputStream.hxx" +#include "config/ConfigGlobal.hxx" #include "stdbin.h" -#include "TextInputStream.hxx" #include "util/Error.hxx" #include "thread/Cond.hxx" #include "Log.hxx" #ifdef ENABLE_ARCHIVE -#include "ArchiveList.hxx" +#include "archive/ArchiveList.hxx" #endif #include <glib.h> @@ -39,16 +39,6 @@ #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; @@ -59,23 +49,6 @@ dump_text_file(TextInputStream &is) static int dump_input_stream(InputStream &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); @@ -83,6 +56,7 @@ dump_input_stream(InputStream &is) is.Lock(); + Error error; if (!is.Check(error)) { LogError(error); is.Unlock(); @@ -99,8 +73,8 @@ int main(int argc, char **argv) int ret; if (argc != 2) { - g_printerr("Usage: run_input URI\n"); - return 1; + fprintf(stderr, "Usage: run_input URI\n"); + return EXIT_FAILURE; } /* initialize GLib */ @@ -109,8 +83,6 @@ int main(int argc, char **argv) g_thread_init(NULL); #endif - g_log_set_default_handler(my_log_func, NULL); - /* initialize MPD */ config_global_init(); @@ -133,7 +105,7 @@ int main(int argc, char **argv) Mutex mutex; Cond cond; - InputStream *is = InputStream::Open(argv[1], mutex, cond, error); + InputStream *is = InputStream::OpenReady(argv[1], mutex, cond, error); if (is != NULL) { ret = dump_input_stream(*is); is->Close(); @@ -141,8 +113,8 @@ int main(int argc, char **argv) if (error.IsDefined()) LogError(error); else - g_printerr("input_stream::Open() failed\n"); - ret = 2; + fprintf(stderr, "input_stream::Open() failed\n"); + ret = EXIT_FAILURE; } /* deinitialize everything */ diff --git a/test/read_conf.cxx b/test/read_conf.cxx index d5eacec67..42afdfb4b 100644 --- a/test/read_conf.cxx +++ b/test/read_conf.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,43 +18,31 @@ */ #include "config.h" -#include "ConfigGlobal.hxx" +#include "config/ConfigGlobal.hxx" #include "fs/Path.hxx" #include "util/Error.hxx" - -#include <glib.h> +#include "Log.hxx" #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); -} +#include <stdio.h> +#include <stdlib.h> int main(int argc, char **argv) { if (argc != 3) { - g_printerr("Usage: read_conf FILE SETTING\n"); - return 1; + fprintf(stderr, "Usage: read_conf FILE SETTING\n"); + return EXIT_FAILURE; } 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; + LogError(error); + return EXIT_FAILURE; } ConfigOption option = ParseConfigOptionName(name); @@ -63,11 +51,11 @@ int main(int argc, char **argv) : nullptr; int ret; if (value != NULL) { - g_print("%s\n", value); - ret = 0; + printf("%s\n", value); + ret = EXIT_SUCCESS; } else { - g_printerr("No such setting: %s\n", name); - ret = 2; + fprintf(stderr, "No such setting: %s\n", name); + ret = EXIT_FAILURE; } config_global_finish(); diff --git a/test/read_mixer.cxx b/test/read_mixer.cxx index 8426443ae..1401a6a1e 100644 --- a/test/read_mixer.cxx +++ b/test/read_mixer.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,15 +18,16 @@ */ #include "config.h" -#include "MixerControl.hxx" -#include "MixerList.hxx" -#include "FilterRegistry.hxx" -#include "pcm/PcmVolume.hxx" +#include "mixer/MixerControl.hxx" +#include "mixer/MixerList.hxx" +#include "filter/FilterRegistry.hxx" +#include "pcm/Volume.hxx" #include "GlobalEvents.hxx" #include "Main.hxx" #include "event/Loop.hxx" -#include "ConfigData.hxx" +#include "config/ConfigData.hxx" #include "util/Error.hxx" +#include "Log.hxx" #include <glib.h> @@ -37,7 +38,7 @@ EventLoop *main_loop; #ifdef HAVE_PULSE -#include "output/PulseOutputPlugin.hxx" +#include "output/plugins/PulseOutputPlugin.hxx" void pulse_output_lock(gcc_unused PulseOutput *po) @@ -72,7 +73,7 @@ pulse_output_set_volume(gcc_unused PulseOutput *po, #endif #ifdef HAVE_ROAR -#include "output/RoarOutputPlugin.hxx" +#include "output/plugins/RoarOutputPlugin.hxx" int roar_output_get_volume(gcc_unused RoarOutput *roar) @@ -101,22 +102,13 @@ filter_plugin_by_name(gcc_unused const char *name) 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; + fprintf(stderr, "Usage: read_mixer PLUGIN\n"); + return EXIT_FAILURE; } #if !GLIB_CHECK_VERSION(2,32,0) @@ -129,14 +121,14 @@ int main(int argc, gcc_unused char **argv) 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; + LogError(error, "mixer_new() failed"); + return EXIT_FAILURE; } if (!mixer_open(mixer, error)) { mixer_free(mixer); - g_printerr("failed to open the mixer: %s\n", error.GetMessage()); - return 2; + LogError(error, "failed to open the mixer"); + return EXIT_FAILURE; } volume = mixer_get_volume(mixer, error); @@ -149,13 +141,12 @@ int main(int argc, gcc_unused char **argv) if (volume < 0) { if (error.IsDefined()) { - g_printerr("failed to read volume: %s\n", - error.GetMessage()); + LogError(error, "failed to read volume"); } else - g_printerr("failed to read volume\n"); - return 2; + fprintf(stderr, "failed to read volume\n"); + return EXIT_FAILURE; } - g_print("%d\n", volume); + printf("%d\n", volume); return 0; } diff --git a/test/read_tags.cxx b/test/read_tags.cxx index 90f1424d9..b12aff11a 100644 --- a/test/read_tags.cxx +++ b/test/read_tags.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,10 +19,10 @@ #include "config.h" #include "IOThread.hxx" -#include "DecoderList.hxx" -#include "DecoderAPI.hxx" -#include "InputInit.hxx" -#include "InputStream.hxx" +#include "decoder/DecoderList.hxx" +#include "decoder/DecoderPlugin.hxx" +#include "input/Init.hxx" +#include "input/InputStream.hxx" #include "AudioFormat.hxx" #include "tag/TagHandler.hxx" #include "tag/TagId3.hxx" @@ -37,103 +37,31 @@ #include <assert.h> #include <unistd.h> #include <stdlib.h> +#include <stdio.h> #ifdef HAVE_LOCALE_H #include <locale.h> #endif -void -decoder_initialized(gcc_unused Decoder &decoder, - gcc_unused const AudioFormat audio_format, - gcc_unused bool seekable, - gcc_unused float total_time) -{ -} - -DecoderCommand -decoder_get_command(gcc_unused Decoder &decoder) -{ - return DecoderCommand::NONE; -} - -void -decoder_command_finished(gcc_unused Decoder &decoder) -{ -} - -double -decoder_seek_where(gcc_unused Decoder &decoder) -{ - return 1.0; -} - -void -decoder_seek_error(gcc_unused Decoder &decoder) -{ -} - -size_t -decoder_read(gcc_unused Decoder *decoder, - InputStream &is, - void *buffer, size_t length) -{ - return is.LockRead(buffer, length, IgnoreError()); -} - -void -decoder_timestamp(gcc_unused Decoder &decoder, - gcc_unused double t) -{ -} - -DecoderCommand -decoder_data(gcc_unused Decoder &decoder, - gcc_unused InputStream *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 Decoder &decoder, - gcc_unused InputStream *is, - gcc_unused Tag &&tag) -{ - return DecoderCommand::NONE; -} - -void -decoder_replay_gain(gcc_unused Decoder &decoder, - gcc_unused const ReplayGainInfo *replay_gain_info) -{ -} - -void -decoder_mixramp(gcc_unused Decoder &decoder, gcc_unused MixRampInfo &&mix_ramp) -{ -} - static bool empty = true; static void print_duration(unsigned seconds, gcc_unused void *ctx) { - g_print("duration=%d\n", seconds); + printf("duration=%d\n", seconds); } static void print_tag(TagType type, const char *value, gcc_unused void *ctx) { - g_print("[%s]=%s\n", tag_item_names[type], value); + printf("[%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); + printf("\"%s\"=%s\n", name, value); } static const struct tag_handler print_handler = { @@ -153,8 +81,8 @@ int main(int argc, char **argv) #endif if (argc != 3) { - g_printerr("Usage: read_tags DECODER FILE\n"); - return 1; + fprintf(stderr, "Usage: read_tags DECODER FILE\n"); + return EXIT_FAILURE; } decoder_name = argv[1]; @@ -177,8 +105,8 @@ int main(int argc, char **argv) plugin = decoder_plugin_from_name(decoder_name); if (plugin == NULL) { - g_printerr("No such decoder: %s\n", decoder_name); - return 1; + fprintf(stderr, "No such decoder: %s\n", decoder_name); + return EXIT_FAILURE; } bool success = plugin->ScanFile(path, print_handler, nullptr); @@ -186,28 +114,13 @@ int main(int argc, char **argv) Mutex mutex; Cond cond; - InputStream *is = InputStream::Open(path, mutex, cond, - error); + InputStream *is = InputStream::OpenReady(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()); + FormatError(error, "Failed to open %s", path); return EXIT_FAILURE; } - mutex.unlock(); - success = plugin->ScanStream(*is, print_handler, nullptr); is->Close(); } @@ -217,8 +130,8 @@ int main(int argc, char **argv) io_thread_deinit(); if (!success) { - g_printerr("Failed to read tags\n"); - return 1; + fprintf(stderr, "Failed to read tags\n"); + return EXIT_FAILURE; } if (empty) { diff --git a/test/run_avahi.cxx b/test/run_avahi.cxx index b392edb6d..d1c153cfd 100644 --- a/test/run_avahi.cxx +++ b/test/run_avahi.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,7 @@ #include "config.h" #include "event/Loop.hxx" #include "ShutdownHandler.hxx" -#include "ZeroconfAvahi.hxx" +#include "zeroconf/ZeroconfAvahi.hxx" #include <stdlib.h> diff --git a/test/run_convert.cxx b/test/run_convert.cxx index 0e873a3b3..3c75b2c19 100644 --- a/test/run_convert.cxx +++ b/test/run_convert.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,28 +27,17 @@ #include "AudioParser.hxx" #include "AudioFormat.hxx" #include "pcm/PcmConvert.hxx" -#include "ConfigGlobal.hxx" +#include "config/ConfigGlobal.hxx" #include "util/FifoBuffer.hxx" #include "util/Error.hxx" +#include "Log.hxx" #include "stdbin.h" -#include <glib.h> - #include <assert.h> #include <stddef.h> #include <stdlib.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) @@ -62,26 +51,23 @@ int main(int argc, char **argv) const void *output; if (argc != 3) { - g_printerr("Usage: run_convert IN_FORMAT OUT_FORMAT <IN >OUT\n"); + fprintf(stderr, + "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; + LogError(error, "Failed to parse audio format"); + return EXIT_FAILURE; } 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; + LogError(error, "Failed to parse audio format"); + return EXIT_FAILURE; } out_audio_format = in_audio_format; @@ -90,6 +76,10 @@ int main(int argc, char **argv) const size_t in_frame_size = in_audio_format.GetFrameSize(); PcmConvert state; + if (!state.Open(in_audio_format, out_audio_format_mask, error)) { + LogError(error, "Failed to open PcmConvert"); + return EXIT_FAILURE; + } FifoBuffer<uint8_t, 4096> buffer; @@ -115,15 +105,18 @@ int main(int argc, char **argv) buffer.Consume(src.size); size_t length; - output = state.Convert(in_audio_format, src.data, src.size, - out_audio_format, &length, error); + output = state.Convert(src.data, src.size, + &length, error); if (output == NULL) { - g_printerr("Failed to convert: %s\n", error.GetMessage()); - return 2; + state.Close(); + LogError(error, "Failed to convert"); + return EXIT_FAILURE; } gcc_unused ssize_t ignored = write(1, output, length); } + state.Close(); + return EXIT_SUCCESS; } diff --git a/test/run_decoder.cxx b/test/run_decoder.cxx index 2f7330a1d..3e5f61778 100644 --- a/test/run_decoder.cxx +++ b/test/run_decoder.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,31 +19,24 @@ #include "config.h" #include "IOThread.hxx" -#include "DecoderList.hxx" -#include "DecoderAPI.hxx" -#include "InputInit.hxx" -#include "InputStream.hxx" +#include "decoder/DecoderList.hxx" +#include "decoder/DecoderAPI.hxx" +#include "input/Init.hxx" +#include "input/InputStream.hxx" #include "AudioFormat.hxx" #include "util/Error.hxx" #include "thread/Cond.hxx" #include "Log.hxx" #include "stdbin.h" +#ifdef HAVE_GLIB #include <glib.h> +#endif #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); -} +#include <stdio.h> struct Decoder { const char *uri; @@ -64,9 +57,9 @@ decoder_initialized(Decoder &decoder, assert(!decoder.initialized); assert(audio_format.IsValid()); - g_printerr("audio_format=%s duration=%f\n", - audio_format_to_string(audio_format, &af_string), - duration); + fprintf(stderr, "audio_format=%s duration=%f\n", + audio_format_to_string(audio_format, &af_string), + duration); decoder.initialized = true; } @@ -101,6 +94,40 @@ decoder_read(gcc_unused Decoder *decoder, return is.LockRead(buffer, length, IgnoreError()); } +bool +decoder_read_full(Decoder *decoder, InputStream &is, + void *_buffer, size_t size) +{ + uint8_t *buffer = (uint8_t *)_buffer; + + while (size > 0) { + size_t nbytes = decoder_read(decoder, is, buffer, size); + if (nbytes == 0) + return false; + + buffer += nbytes; + size -= nbytes; + } + + return true; +} + +bool +decoder_skip(Decoder *decoder, InputStream &is, size_t size) +{ + while (size > 0) { + char buffer[1024]; + size_t nbytes = decoder_read(decoder, is, buffer, + std::min(sizeof(buffer), size)); + if (nbytes == 0) + return false; + + size -= nbytes; + } + + return true; +} + void decoder_timestamp(gcc_unused Decoder &decoder, gcc_unused double t) @@ -131,13 +158,13 @@ decoder_replay_gain(gcc_unused Decoder &decoder, { const ReplayGainTuple *tuple = &rgi->tuples[REPLAY_GAIN_ALBUM]; if (tuple->IsDefined()) - g_printerr("replay_gain[album]: gain=%f peak=%f\n", - tuple->gain, tuple->peak); + fprintf(stderr, "replay_gain[album]: gain=%f peak=%f\n", + tuple->gain, tuple->peak); tuple = &rgi->tuples[REPLAY_GAIN_TRACK]; if (tuple->IsDefined()) - g_printerr("replay_gain[track]: gain=%f peak=%f\n", - tuple->gain, tuple->peak); + fprintf(stderr, "replay_gain[track]: gain=%f peak=%f\n", + tuple->gain, tuple->peak); } void @@ -150,19 +177,19 @@ int main(int argc, char **argv) const char *decoder_name; if (argc != 3) { - g_printerr("Usage: run_decoder DECODER URI >OUT\n"); - return 1; + fprintf(stderr, "Usage: run_decoder DECODER URI >OUT\n"); + return EXIT_FAILURE; } Decoder decoder; decoder_name = argv[1]; decoder.uri = argv[2]; +#ifdef HAVE_GLIB #if !GLIB_CHECK_VERSION(2,32,0) g_thread_init(NULL); #endif - - g_log_set_default_handler(my_log_func, NULL); +#endif io_thread_init(); io_thread_start(); @@ -170,15 +197,15 @@ int main(int argc, char **argv) Error error; if (!input_stream_global_init(error)) { LogError(error); - return 2; + return EXIT_FAILURE; } 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; + fprintf(stderr, "No such decoder: %s\n", decoder_name); + return EXIT_FAILURE; } decoder.initialized = false; @@ -195,17 +222,17 @@ int main(int argc, char **argv) if (error.IsDefined()) LogError(error); else - g_printerr("InputStream::Open() failed\n"); + fprintf(stderr, "input/InputStream::Open() failed\n"); - return 1; + return EXIT_FAILURE; } decoder.plugin->StreamDecode(decoder, *is); is->Close(); } else { - g_printerr("Decoder plugin is not usable\n"); - return 1; + fprintf(stderr, "Decoder plugin is not usable\n"); + return EXIT_FAILURE; } decoder_plugin_deinit_all(); @@ -213,8 +240,8 @@ int main(int argc, char **argv) io_thread_deinit(); if (!decoder.initialized) { - g_printerr("Decoding failed\n"); - return 1; + fprintf(stderr, "Decoding failed\n"); + return EXIT_FAILURE; } return 0; diff --git a/test/run_encoder.cxx b/test/run_encoder.cxx index 838ee708e..f16d8cb0a 100644 --- a/test/run_encoder.cxx +++ b/test/run_encoder.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,16 +18,17 @@ */ #include "config.h" -#include "EncoderList.hxx" -#include "EncoderPlugin.hxx" +#include "encoder/EncoderList.hxx" +#include "encoder/EncoderPlugin.hxx" #include "AudioFormat.hxx" #include "AudioParser.hxx" -#include "ConfigData.hxx" +#include "config/ConfigData.hxx" #include "util/Error.hxx" +#include "Log.hxx" #include "stdbin.h" -#include <glib.h> - +#include <stdio.h> +#include <stdlib.h> #include <stddef.h> #include <unistd.h> @@ -50,8 +51,9 @@ int main(int argc, char **argv) /* parse command line */ if (argc > 3) { - g_printerr("Usage: run_encoder [ENCODER] [FORMAT] <IN >OUT\n"); - return 1; + fprintf(stderr, + "Usage: run_encoder [ENCODER] [FORMAT] <IN >OUT\n"); + return EXIT_FAILURE; } if (argc > 1) @@ -63,8 +65,8 @@ int main(int argc, char **argv) const auto plugin = encoder_plugin_get(encoder_name); if (plugin == NULL) { - g_printerr("No such encoder: %s\n", encoder_name); - return 1; + fprintf(stderr, "No such encoder: %s\n", encoder_name); + return EXIT_FAILURE; } config_param param; @@ -73,9 +75,8 @@ int main(int argc, char **argv) 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; + LogError(error, "Failed to initialize encoder"); + return EXIT_FAILURE; } /* open the encoder */ @@ -83,16 +84,14 @@ int main(int argc, char **argv) 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; + LogError(error, "Failed to parse audio format"); + return EXIT_FAILURE; } } if (!encoder_open(encoder, audio_format, error)) { - g_printerr("Failed to open encoder: %s\n", - error.GetMessage()); - return 1; + LogError(error, "Failed to open encoder"); + return EXIT_FAILURE; } encoder_to_stdout(*encoder); @@ -102,19 +101,20 @@ int main(int argc, char **argv) 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; + LogError(error, "encoder_write() failed"); + return EXIT_FAILURE; } encoder_to_stdout(*encoder); } if (!encoder_end(encoder, error)) { - g_printerr("encoder_flush() failed: %s\n", - error.GetMessage()); - return 1; + LogError(error, "encoder_flush() failed"); + return EXIT_FAILURE; } encoder_to_stdout(*encoder); + + encoder_close(encoder); + encoder_finish(encoder); } diff --git a/test/run_filter.cxx b/test/run_filter.cxx index 085fc256b..7816827d7 100644 --- a/test/run_filter.cxx +++ b/test/run_filter.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,23 +18,26 @@ */ #include "config.h" -#include "ConfigData.hxx" -#include "ConfigGlobal.hxx" +#include "config/ConfigData.hxx" +#include "config/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 "filter/FilterPlugin.hxx" +#include "filter/FilterInternal.hxx" +#include "pcm/Volume.hxx" +#include "mixer/MixerControl.hxx" #include "stdbin.h" #include "util/Error.hxx" #include "system/FatalError.hxx" +#include "Log.hxx" #include <glib.h> #include <assert.h> #include <string.h> +#include <stdlib.h> +#include <stdio.h> #include <errno.h> #include <unistd.h> @@ -45,16 +48,6 @@ mixer_set_volume(gcc_unused Mixer *mixer, 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) { @@ -76,14 +69,14 @@ load_filter(const char *name) param = find_named_config_block(CONF_AUDIO_FILTER, name); if (param == NULL) { - g_printerr("No such configured filter: %s\n", name); + fprintf(stderr, "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()); + LogError(error, "Failed to load filter"); return NULL; } @@ -97,8 +90,8 @@ int main(int argc, char **argv) char buffer[4096]; if (argc < 3 || argc > 4) { - g_printerr("Usage: run_filter CONFIG NAME [FORMAT] <IN\n"); - return 1; + fprintf(stderr, "Usage: run_filter CONFIG NAME [FORMAT] <IN\n"); + return EXIT_FAILURE; } const Path config_path = Path::FromFS(argv[1]); @@ -111,8 +104,6 @@ int main(int argc, char **argv) g_thread_init(NULL); #endif - g_log_set_default_handler(my_log_func, NULL); - /* read configuration file (mpd.conf) */ config_global_init(); @@ -124,9 +115,8 @@ int main(int argc, char **argv) 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; + LogError(error, "Failed to parse audio format"); + return EXIT_FAILURE; } } @@ -134,20 +124,20 @@ int main(int argc, char **argv) Filter *filter = load_filter(argv[2]); if (filter == NULL) - return 1; + return EXIT_FAILURE; /* 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()); + LogError(error, "Failed to open filter"); delete filter; - return 1; + return EXIT_FAILURE; } - g_printerr("audio_format=%s\n", - audio_format_to_string(out_audio_format, &af_string)); + fprintf(stderr, "audio_format=%s\n", + audio_format_to_string(out_audio_format, &af_string)); /* play */ @@ -163,15 +153,16 @@ int main(int argc, char **argv) dest = filter->FilterPCM(buffer, (size_t)nbytes, &length, error); if (dest == NULL) { - g_printerr("Filter failed: %s\n", error.GetMessage()); + LogError(error, "filter/Filter failed"); filter->Close(); delete filter; - return 1; + return EXIT_FAILURE; } nbytes = write(1, dest, length); if (nbytes < 0) { - g_printerr("Failed to write: %s\n", g_strerror(errno)); + fprintf(stderr, "Failed to write: %s\n", + strerror(errno)); filter->Close(); delete filter; return 1; diff --git a/test/run_inotify.cxx b/test/run_inotify.cxx index c57e6e9ef..7d77372f0 100644 --- a/test/run_inotify.cxx +++ b/test/run_inotify.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,13 +19,11 @@ #include "config.h" #include "ShutdownHandler.hxx" -#include "InotifySource.hxx" +#include "db/update/InotifySource.hxx" #include "event/Loop.hxx" #include "util/Error.hxx" #include "Log.hxx" -#include <glib.h> - #include <sys/inotify.h> static constexpr unsigned IN_MASK = @@ -39,7 +37,7 @@ 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); + printf("mask=0x%x name='%s'\n", mask, name); } int main(int argc, char **argv) @@ -47,8 +45,8 @@ int main(int argc, char **argv) const char *path; if (argc != 2) { - g_printerr("Usage: run_inotify PATH\n"); - return 1; + fprintf(stderr, "Usage: run_inotify PATH\n"); + return EXIT_FAILURE; } path = argv[1]; @@ -62,17 +60,18 @@ int main(int argc, char **argv) nullptr, error); if (source == NULL) { LogError(error); - return 2; + return EXIT_FAILURE; } int descriptor = source->Add(path, IN_MASK, error); if (descriptor < 0) { delete source; LogError(error); - return 2; + return EXIT_FAILURE; } event_loop.Run(); delete source; + return EXIT_SUCCESS; } diff --git a/test/run_input.cxx b/test/run_input.cxx index 3817ed418..539171ad8 100644 --- a/test/run_input.cxx +++ b/test/run_input.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,33 +21,25 @@ #include "TagSave.hxx" #include "stdbin.h" #include "tag/Tag.hxx" -#include "ConfigGlobal.hxx" -#include "InputStream.hxx" -#include "InputInit.hxx" +#include "config/ConfigGlobal.hxx" +#include "input/InputStream.hxx" +#include "input/Init.hxx" #include "IOThread.hxx" #include "util/Error.hxx" #include "thread/Cond.hxx" #include "Log.hxx" #ifdef ENABLE_ARCHIVE -#include "ArchiveList.hxx" +#include "archive/ArchiveList.hxx" #endif +#ifdef HAVE_GLIB #include <glib.h> +#endif #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(InputStream *is) { @@ -58,27 +50,17 @@ dump_input_stream(InputStream *is) 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()); + fprintf(stderr, "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"); + fprintf(stderr, "Received a tag:\n"); tag_save(stderr, *tag); delete tag; } @@ -114,17 +96,17 @@ int main(int argc, char **argv) int ret; if (argc != 2) { - g_printerr("Usage: run_input URI\n"); - return 1; + fprintf(stderr, "Usage: run_input URI\n"); + return EXIT_FAILURE; } /* initialize GLib */ +#ifdef HAVE_GLIB #if !GLIB_CHECK_VERSION(2,32,0) g_thread_init(NULL); #endif - - g_log_set_default_handler(my_log_func, NULL); +#endif /* initialize MPD */ @@ -147,7 +129,7 @@ int main(int argc, char **argv) Mutex mutex; Cond cond; - is = InputStream::Open(argv[1], mutex, cond, error); + is = InputStream::OpenReady(argv[1], mutex, cond, error); if (is != NULL) { ret = dump_input_stream(is); is->Close(); @@ -155,8 +137,8 @@ int main(int argc, char **argv) if (error.IsDefined()) LogError(error); else - g_printerr("input_stream::Open() failed\n"); - ret = 2; + fprintf(stderr, "input_stream::Open() failed\n"); + ret = EXIT_FAILURE; } /* deinitialize everything */ diff --git a/test/run_neighbor_explorer.cxx b/test/run_neighbor_explorer.cxx new file mode 100644 index 000000000..374114b11 --- /dev/null +++ b/test/run_neighbor_explorer.cxx @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "config/ConfigGlobal.hxx" +#include "neighbor/Listener.hxx" +#include "neighbor/Info.hxx" +#include "neighbor/Glue.hxx" +#include "fs/Path.hxx" +#include "event/Loop.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <stdio.h> +#include <stdlib.h> + +class MyNeighborListener final : public NeighborListener { + public: + /* virtual methods from class NeighborListener */ + virtual void FoundNeighbor(const NeighborInfo &info) override { + printf("found '%s' (%s)\n", + info.display_name.c_str(), info.uri.c_str()); + } + + virtual void LostNeighbor(const NeighborInfo &info) override { + printf("lost '%s' (%s)\n", + info.display_name.c_str(), info.uri.c_str()); + } +}; + +int +main(int argc, char **argv) +{ + if (argc != 2) { + fprintf(stderr, "Usage: run_neighbor_explorer CONFIG\n"); + return EXIT_FAILURE; + } + + const Path config_path = Path::FromFS(argv[1]); + + /* read configuration file (mpd.conf) */ + + Error error; + + config_global_init(); + if (!ReadConfigFile(config_path, error)) { + LogError(error); + return EXIT_FAILURE; + } + + /* initialize the core */ + + EventLoop loop((EventLoop::Default())); + + /* initialize neighbor plugins */ + + MyNeighborListener listener; + NeighborGlue neighbor; + if (!neighbor.Init(loop, listener, error) || !neighbor.Open(error)) { + LogError(error); + return EXIT_FAILURE; + } + + /* run */ + + loop.Run(); + neighbor.Close(); + return EXIT_SUCCESS; +} diff --git a/test/run_normalize.cxx b/test/run_normalize.cxx index 3193fefd2..300dae1d3 100644 --- a/test/run_normalize.cxx +++ b/test/run_normalize.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -33,6 +33,7 @@ #include <glib.h> #include <stddef.h> +#include <stdio.h> #include <unistd.h> #include <string.h> @@ -43,7 +44,7 @@ int main(int argc, char **argv) ssize_t nbytes; if (argc > 2) { - g_printerr("Usage: run_normalize [FORMAT] <IN >OUT\n"); + fprintf(stderr, "Usage: run_normalize [FORMAT] <IN >OUT\n"); return 1; } @@ -51,7 +52,7 @@ int main(int argc, char **argv) if (argc > 1) { Error error; if (!audio_format_parse(audio_format, argv[1], false, error)) { - g_printerr("Failed to parse audio format: %s\n", + fprintf(stderr, "Failed to parse audio format: %s\n", error.GetMessage()); return 1; } diff --git a/test/run_output.cxx b/test/run_output.cxx index 7982bd7de..522db5e1e 100644 --- a/test/run_output.cxx +++ b/test/run_output.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,12 +18,12 @@ */ #include "config.h" -#include "OutputControl.hxx" -#include "OutputInternal.hxx" -#include "OutputPlugin.hxx" -#include "ConfigData.hxx" -#include "ConfigGlobal.hxx" -#include "ConfigOption.hxx" +#include "output/OutputControl.hxx" +#include "output/OutputInternal.hxx" +#include "output/OutputPlugin.hxx" +#include "config/ConfigData.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" #include "Idle.hxx" #include "Main.hxx" #include "event/Loop.hxx" @@ -32,10 +32,11 @@ #include "fs/Path.hxx" #include "AudioParser.hxx" #include "pcm/PcmConvert.hxx" -#include "FilterRegistry.hxx" +#include "filter/FilterRegistry.hxx" #include "PlayerControl.hxx" #include "stdbin.h" #include "util/Error.hxx" +#include "Log.hxx" #include <glib.h> @@ -43,6 +44,7 @@ #include <string.h> #include <unistd.h> #include <stdlib.h> +#include <stdio.h> EventLoop *main_loop; @@ -83,7 +85,7 @@ load_audio_output(const char *name) param = find_named_config_block(CONF_AUDIO_OUTPUT, name); if (param == NULL) { - g_printerr("No such configured audio output: %s\n", name); + fprintf(stderr, "No such configured audio output: %s\n", name); return nullptr; } @@ -93,7 +95,7 @@ load_audio_output(const char *name) struct audio_output *ao = audio_output_new(*param, dummy_player_control, error); if (ao == nullptr) - g_printerr("%s\n", error.GetMessage()); + LogError(error); return ao; } @@ -105,21 +107,19 @@ run_output(struct audio_output *ao, AudioFormat audio_format) Error error; if (!ao_plugin_enable(ao, error)) { - g_printerr("Failed to enable audio output: %s\n", - error.GetMessage()); + LogError(error, "Failed to enable audio output"); 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()); + LogError(error, "Failed to open audio output"); return false; } struct audio_format_string af_string; - g_printerr("audio_format=%s\n", - audio_format_to_string(audio_format, &af_string)); + fprintf(stderr, "audio_format=%s\n", + audio_format_to_string(audio_format, &af_string)); size_t frame_size = audio_format.GetFrameSize(); @@ -145,8 +145,7 @@ run_output(struct audio_output *ao, AudioFormat audio_format) if (consumed == 0) { ao_plugin_close(ao); ao_plugin_disable(ao); - g_printerr("Failed to play: %s\n", - error.GetMessage()); + LogError(error, "Failed to play"); return false; } @@ -168,8 +167,8 @@ 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; + fprintf(stderr, "Usage: run_output CONFIG NAME [FORMAT] <IN\n"); + return EXIT_FAILURE; } const Path config_path = Path::FromFS(argv[1]); @@ -184,8 +183,8 @@ int main(int argc, char **argv) config_global_init(); if (!ReadConfigFile(config_path, error)) { - g_printerr("%s\n", error.GetMessage()); - return 1; + LogError(error); + return EXIT_FAILURE; } main_loop = new EventLoop(EventLoop::Default()); @@ -203,9 +202,8 @@ int main(int argc, char **argv) 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; + LogError(error, "Failed to parse audio format"); + return EXIT_FAILURE; } } diff --git a/test/run_resolver.cxx b/test/run_resolver.cxx index 7da2fd5b2..71cadbeec 100644 --- a/test/run_resolver.cxx +++ b/test/run_resolver.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,8 +22,6 @@ #include "util/Error.hxx" #include "Log.hxx" -#include <glib.h> - #ifdef WIN32 #include <ws2tcpip.h> #include <winsock.h> @@ -32,12 +30,13 @@ #include <netdb.h> #endif +#include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { if (argc != 2) { - g_printerr("Usage: run_resolver HOST\n"); + fprintf(stderr, "Usage: run_resolver HOST\n"); return EXIT_FAILURE; } @@ -51,16 +50,8 @@ int main(int argc, char **argv) } 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); + const auto s = sockaddr_to_string(i->ai_addr, i->ai_addrlen); + printf("%s\n", s.c_str()); } freeaddrinfo(ai); diff --git a/test/software_volume.cxx b/test/software_volume.cxx index 19a0be88c..1e41f95fd 100644 --- a/test/software_volume.cxx +++ b/test/software_volume.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,15 +24,17 @@ */ #include "config.h" -#include "pcm/PcmVolume.hxx" +#include "pcm/Volume.hxx" #include "AudioParser.hxx" #include "AudioFormat.hxx" +#include "util/ConstBuffer.hxx" #include "util/Error.hxx" #include "stdbin.h" +#include "Log.hxx" -#include <glib.h> - +#include <stdio.h> #include <stddef.h> +#include <stdlib.h> #include <unistd.h> int main(int argc, char **argv) @@ -41,28 +43,29 @@ int main(int argc, char **argv) ssize_t nbytes; if (argc > 2) { - g_printerr("Usage: software_volume [FORMAT] <IN >OUT\n"); - return 1; + fprintf(stderr, "Usage: software_volume [FORMAT] <IN >OUT\n"); + return EXIT_FAILURE; } 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; + LogError(error, "Failed to parse audio format"); + return EXIT_FAILURE; } } - 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; - } + PcmVolume pv; + if (!pv.Open(audio_format.format, error)) { + fprintf(stderr, "%s\n", error.GetMessage()); + return EXIT_FAILURE; + } - gcc_unused ssize_t ignored = write(1, buffer, nbytes); + while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { + auto dest = pv.Apply({buffer, size_t(nbytes)}); + gcc_unused ssize_t ignored = write(1, dest.data, dest.size); } + + pv.Close(); } diff --git a/test/stdbin.h b/test/stdbin.h index 48cac7338..8b5502e6f 100644 --- a/test/stdbin.h +++ b/test/stdbin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/test_archive.cxx b/test/test_archive.cxx index dbb41fe42..353699574 100644 --- a/test/test_archive.cxx +++ b/test/test_archive.cxx @@ -1,5 +1,5 @@ #include "config.h" -#include "ArchiveLookup.hxx" +#include "archive/ArchiveLookup.hxx" #include "Compiler.h" #include <cppunit/TestFixture.h> diff --git a/test/test_byte_reverse.cxx b/test/test_byte_reverse.cxx index 58673e4f8..0ab97e4d1 100644 --- a/test/test_byte_reverse.cxx +++ b/test/test_byte_reverse.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/test_icy_parser.cxx b/test/test_icy_parser.cxx index 2abf60f9e..a996df134 100644 --- a/test/test_icy_parser.cxx +++ b/test/test_icy_parser.cxx @@ -28,7 +28,7 @@ icy_parse_tag(const char *p) static void CompareTagTitle(const Tag &tag, const std::string &title) { - CPPUNIT_ASSERT_EQUAL(1u, tag.num_items); + CPPUNIT_ASSERT_EQUAL(uint16_t(1), tag.num_items); const TagItem &item = *tag.items[0]; CPPUNIT_ASSERT_EQUAL(TAG_TITLE, item.type); @@ -47,7 +47,7 @@ static void TestIcyParserEmpty(const char *input) { Tag *tag = icy_parse_tag(input); - CPPUNIT_ASSERT_EQUAL(0u, tag->num_items); + CPPUNIT_ASSERT_EQUAL(uint16_t(0), tag->num_items); delete tag; } diff --git a/test/test_pcm_all.hxx b/test/test_pcm_all.hxx index 2a0aa8628..09f25f70c 100644 --- a/test/test_pcm_all.hxx +++ b/test/test_pcm_all.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/test_pcm_channels.cxx b/test/test_pcm_channels.cxx index 85c872674..85ad0c248 100644 --- a/test/test_pcm_channels.cxx +++ b/test/test_pcm_channels.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,67 +22,60 @@ #include "test_pcm_util.hxx" #include "pcm/PcmChannels.hxx" #include "pcm/PcmBuffer.hxx" +#include "util/ConstBuffer.hxx" void PcmChannelsTest::TestChannels16() { - constexpr unsigned N = 256; + constexpr size_t 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); - CPPUNIT_ASSERT(dest != NULL); - CPPUNIT_ASSERT_EQUAL(sizeof(src) / 2, dest_size); + auto dest = pcm_convert_channels_16(buffer, 1, 2, { src, N * 2 }); + CPPUNIT_ASSERT(!dest.IsNull()); + CPPUNIT_ASSERT_EQUAL(N, dest.size); for (unsigned i = 0; i < N; ++i) CPPUNIT_ASSERT_EQUAL(int16_t((src[i * 2] + src[i * 2 + 1]) / 2), - dest[i]); + dest.data[i]); /* mono to stereo */ - dest = pcm_convert_channels_16(buffer, 2, 1, src, sizeof(src), - &dest_size); - CPPUNIT_ASSERT(dest != NULL); - CPPUNIT_ASSERT_EQUAL(sizeof(src) * 2, dest_size); + dest = pcm_convert_channels_16(buffer, 2, 1, { src, N * 2 }); + CPPUNIT_ASSERT(!dest.IsNull()); + CPPUNIT_ASSERT_EQUAL(N * 4, dest.size); for (unsigned i = 0; i < N; ++i) { - CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2]); - CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2 + 1]); + CPPUNIT_ASSERT_EQUAL(src[i], dest.data[i * 2]); + CPPUNIT_ASSERT_EQUAL(src[i], dest.data[i * 2 + 1]); } } void PcmChannelsTest::TestChannels32() { - constexpr unsigned N = 256; + constexpr size_t 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); - CPPUNIT_ASSERT(dest != NULL); - CPPUNIT_ASSERT_EQUAL(sizeof(src) / 2, dest_size); + auto dest = pcm_convert_channels_32(buffer, 1, 2, { src, N * 2 }); + CPPUNIT_ASSERT(!dest.IsNull()); + CPPUNIT_ASSERT_EQUAL(N, dest.size); for (unsigned i = 0; i < N; ++i) CPPUNIT_ASSERT_EQUAL(int32_t(((int64_t)src[i * 2] + (int64_t)src[i * 2 + 1]) / 2), - dest[i]); + dest.data[i]); /* mono to stereo */ - dest = pcm_convert_channels_32(buffer, 2, 1, src, sizeof(src), - &dest_size); - CPPUNIT_ASSERT(dest != NULL); - CPPUNIT_ASSERT_EQUAL(sizeof(src) * 2, dest_size); + dest = pcm_convert_channels_32(buffer, 2, 1, { src, N * 2 }); + CPPUNIT_ASSERT(!dest.IsNull()); + CPPUNIT_ASSERT_EQUAL(N * 4, dest.size); for (unsigned i = 0; i < N; ++i) { - CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2]); - CPPUNIT_ASSERT_EQUAL(src[i], dest[i * 2 + 1]); + CPPUNIT_ASSERT_EQUAL(src[i], dest.data[i * 2]); + CPPUNIT_ASSERT_EQUAL(src[i], dest.data[i * 2 + 1]); } } diff --git a/test/test_pcm_dither.cxx b/test/test_pcm_dither.cxx index 710deffcc..6751d94fe 100644 --- a/test/test_pcm_dither.cxx +++ b/test/test_pcm_dither.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,7 +19,7 @@ #include "test_pcm_all.hxx" #include "test_pcm_util.hxx" -#include "pcm/PcmDither.hxx" +#include "pcm/PcmDither.cxx" void PcmDitherTest::TestDither24() diff --git a/test/test_pcm_format.cxx b/test/test_pcm_format.cxx index 49f4ccd4b..6393d484b 100644 --- a/test/test_pcm_format.cxx +++ b/test/test_pcm_format.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -29,86 +29,72 @@ void PcmFormatTest::TestFormat8to16() { - constexpr unsigned N = 256; + constexpr size_t 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); - CPPUNIT_ASSERT_EQUAL(N, unsigned(d_end - d)); + auto d = pcm_convert_to_16(buffer, dither, SampleFormat::S8, src); + CPPUNIT_ASSERT_EQUAL(N, d.size); for (size_t i = 0; i < N; ++i) - CPPUNIT_ASSERT_EQUAL(int(src[i]), d[i] >> 8); + CPPUNIT_ASSERT_EQUAL(int(src[i]), d.data[i] >> 8); } void PcmFormatTest::TestFormat16to24() { - constexpr unsigned N = 256; + constexpr size_t 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); - CPPUNIT_ASSERT_EQUAL(N, unsigned(d_end - d)); + auto d = pcm_convert_to_24(buffer, SampleFormat::S16, src); + CPPUNIT_ASSERT_EQUAL(N, d.size); for (size_t i = 0; i < N; ++i) - CPPUNIT_ASSERT_EQUAL(int(src[i]), d[i] >> 8); + CPPUNIT_ASSERT_EQUAL(int(src[i]), d.data[i] >> 8); } void PcmFormatTest::TestFormat16to32() { - constexpr unsigned N = 256; + constexpr size_t 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); - CPPUNIT_ASSERT_EQUAL(N, unsigned(d_end - d)); + auto d = pcm_convert_to_32(buffer, SampleFormat::S16, src); + CPPUNIT_ASSERT_EQUAL(N, d.size); for (size_t i = 0; i < N; ++i) - CPPUNIT_ASSERT_EQUAL(int(src[i]), d[i] >> 16); + CPPUNIT_ASSERT_EQUAL(int(src[i]), d.data[i] >> 16); } void PcmFormatTest::TestFormatFloat() { - constexpr unsigned N = 256; + constexpr size_t 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); - CPPUNIT_ASSERT_EQUAL(N, unsigned(f_end - f)); + auto f = pcm_convert_to_float(buffer1, SampleFormat::S16, src); + CPPUNIT_ASSERT_EQUAL(N, f.size); - for (auto i = f; i != f_end; ++i) { - CPPUNIT_ASSERT(*i >= -1.); - CPPUNIT_ASSERT(*i <= 1.); + for (size_t i = 0; i != f.size; ++i) { + CPPUNIT_ASSERT(f.data[i] >= -1.); + CPPUNIT_ASSERT(f.data[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); - CPPUNIT_ASSERT_EQUAL(N, unsigned(d_end - d)); + f.ToVoid()); + CPPUNIT_ASSERT_EQUAL(N, d.size); for (size_t i = 0; i < N; ++i) - CPPUNIT_ASSERT_EQUAL(src[i], d[i]); + CPPUNIT_ASSERT_EQUAL(src[i], d.data[i]); } diff --git a/test/test_pcm_main.cxx b/test/test_pcm_main.cxx index c034181d9..0d6d2fef3 100644 --- a/test/test_pcm_main.cxx +++ b/test/test_pcm_main.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/test_pcm_mix.cxx b/test/test_pcm_mix.cxx index 2a8a11388..65d009050 100644 --- a/test/test_pcm_mix.cxx +++ b/test/test_pcm_mix.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,6 +21,7 @@ #include "test_pcm_all.hxx" #include "test_pcm_util.hxx" #include "pcm/PcmMix.hxx" +#include "pcm/PcmDither.hxx" template<typename T, SampleFormat format, typename G=RandomInt<T>> static void @@ -30,23 +31,26 @@ TestPcmMix(G g=G()) const auto src1 = TestDataBuffer<T, N>(g); const auto src2 = TestDataBuffer<T, N>(g); + PcmDither dither; + /* portion1=1.0: result must be equal to src1 */ auto result = src1; - bool success = pcm_mix(result.begin(), src2.begin(), sizeof(result), + bool success = pcm_mix(dither, + result.begin(), src2.begin(), sizeof(result), format, 1.0); CPPUNIT_ASSERT(success); - AssertEqualWithTolerance(result, src1, 1); + AssertEqualWithTolerance(result, src1, 3); /* portion1=0.0: result must be equal to src2 */ result = src1; - success = pcm_mix(result.begin(), src2.begin(), sizeof(result), + success = pcm_mix(dither, result.begin(), src2.begin(), sizeof(result), format, 0.0); CPPUNIT_ASSERT(success); - AssertEqualWithTolerance(result, src2, 1); + AssertEqualWithTolerance(result, src2, 3); /* portion1=0.5 */ result = src1; - success = pcm_mix(result.begin(), src2.begin(), sizeof(result), + success = pcm_mix(dither, result.begin(), src2.begin(), sizeof(result), format, 0.5); CPPUNIT_ASSERT(success); @@ -54,7 +58,7 @@ TestPcmMix(G g=G()) for (unsigned i = 0; i < N; ++i) expected[i] = (int64_t(src1[i]) + int64_t(src2[i])) / 2; - AssertEqualWithTolerance(result, expected, 1); + AssertEqualWithTolerance(result, expected, 3); } void diff --git a/test/test_pcm_pack.cxx b/test/test_pcm_pack.cxx index cab78c499..161e13145 100644 --- a/test/test_pcm_pack.cxx +++ b/test/test_pcm_pack.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/test/test_pcm_util.hxx b/test/test_pcm_util.hxx index b378c75a7..f1efbc666 100644 --- a/test/test_pcm_util.hxx +++ b/test/test_pcm_util.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "util/ConstBuffer.hxx" + #include <array> #include <random> @@ -76,6 +78,14 @@ public: operator typename std::array<T, N>::const_pointer() const { return begin(); } + + operator ConstBuffer<T>() const { + return { begin(), size() }; + } + + operator ConstBuffer<void>() const { + return { begin(), size() * sizeof(T) }; + } }; template<typename T> diff --git a/test/test_pcm_volume.cxx b/test/test_pcm_volume.cxx index 764d8b127..d08c7efb0 100644 --- a/test/test_pcm_volume.cxx +++ b/test/test_pcm_volume.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,169 +17,109 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "test_pcm_all.hxx" -#include "pcm/PcmVolume.hxx" +#include "pcm/Volume.hxx" +#include "pcm/Traits.hxx" +#include "util/ConstBuffer.hxx" +#include "util/Error.hxx" #include "test_pcm_util.hxx" #include <algorithm> #include <string.h> -void -PcmVolumeTest::TestVolume8() +template<SampleFormat F, class Traits=SampleTraits<F>, + typename G=RandomInt<typename Traits::value_type>> +static void +TestVolume(G g=G()) { - constexpr unsigned N = 256; - static int8_t zero[N]; - const auto src = TestDataBuffer<int8_t, N>(); + typedef typename Traits::value_type value_type; + + PcmVolume pv; + CPPUNIT_ASSERT(pv.Open(F, IgnoreError())); - int8_t dest[N]; + constexpr size_t N = 256; + static value_type zero[N]; + const auto _src = TestDataBuffer<value_type, N>(g); + const ConstBuffer<void> src(_src, sizeof(_src)); - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S8, 0)); - CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, zero, sizeof(zero))); + pv.SetVolume(0); + auto dest = pv.Apply(src); + CPPUNIT_ASSERT_EQUAL(src.size, dest.size); + CPPUNIT_ASSERT_EQUAL(0, memcmp(dest.data, zero, sizeof(zero))); - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S8, PCM_VOLUME_1)); - CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, src, sizeof(src))); + pv.SetVolume(PCM_VOLUME_1); + dest = pv.Apply(src); + CPPUNIT_ASSERT_EQUAL(src.size, dest.size); + CPPUNIT_ASSERT_EQUAL(0, memcmp(dest.data, src.data, src.size)); - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S8, PCM_VOLUME_1 / 2)); + pv.SetVolume(PCM_VOLUME_1 / 2); + dest = pv.Apply(src); + CPPUNIT_ASSERT_EQUAL(src.size, dest.size); + const auto _dest = ConstBuffer<value_type>::FromVoid(dest); for (unsigned i = 0; i < N; ++i) { - CPPUNIT_ASSERT(dest[i] >= (src[i] - 1) / 2); - CPPUNIT_ASSERT(dest[i] <= src[i] / 2 + 1); + const auto expected = (_src[i] + 1) / 2; + CPPUNIT_ASSERT(_dest.data[i] >= expected - 4); + CPPUNIT_ASSERT(_dest.data[i] <= expected + 4); } + + pv.Close(); } void -PcmVolumeTest::TestVolume16() +PcmVolumeTest::TestVolume8() { - 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); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S16, 0)); - CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, zero, sizeof(zero))); - - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S16, PCM_VOLUME_1)); - CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, src, sizeof(src))); - - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S16, PCM_VOLUME_1 / 2)); + TestVolume<SampleFormat::S8>(); +} - for (unsigned i = 0; i < N; ++i) { - CPPUNIT_ASSERT(dest[i] >= (src[i] - 1) / 2); - CPPUNIT_ASSERT(dest[i] <= src[i] / 2 + 1); - } +void +PcmVolumeTest::TestVolume16() +{ + TestVolume<SampleFormat::S16>(); } void PcmVolumeTest::TestVolume24() { - constexpr unsigned N = 256; - static int32_t zero[N]; - const auto src = TestDataBuffer<int32_t, N>(RandomInt24()); - - int32_t dest[N]; - - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S24_P32, 0)); - CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, zero, sizeof(zero))); - - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S24_P32, PCM_VOLUME_1)); - CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, src, sizeof(src))); - - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S24_P32, PCM_VOLUME_1 / 2)); - - for (unsigned i = 0; i < N; ++i) { - CPPUNIT_ASSERT(dest[i] >= (src[i] - 1) / 2); - CPPUNIT_ASSERT(dest[i] <= src[i] / 2 + 1); - } + TestVolume<SampleFormat::S24_P32>(RandomInt24()); } void PcmVolumeTest::TestVolume32() { - 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); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S32, 0)); - CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, zero, sizeof(zero))); - - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S32, PCM_VOLUME_1)); - CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, src, sizeof(src))); - - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::S32, PCM_VOLUME_1 / 2)); - - for (unsigned i = 0; i < N; ++i) { - CPPUNIT_ASSERT(dest[i] >= (src[i] - 1) / 2); - CPPUNIT_ASSERT(dest[i] <= src[i] / 2 + 1); - } + TestVolume<SampleFormat::S32>(); } void PcmVolumeTest::TestVolumeFloat() { - constexpr unsigned N = 256; - static float zero[N]; - const auto src = TestDataBuffer<float, N>(RandomFloat()); + PcmVolume pv; + CPPUNIT_ASSERT(pv.Open(SampleFormat::FLOAT, IgnoreError())); - float dest[N]; + constexpr size_t N = 256; + static float zero[N]; + const auto _src = TestDataBuffer<float, N>(RandomFloat()); + const ConstBuffer<void> src(_src, sizeof(_src)); - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::FLOAT, 0)); - CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, zero, sizeof(zero))); + pv.SetVolume(0); + auto dest = pv.Apply(src); + CPPUNIT_ASSERT_EQUAL(src.size, dest.size); + CPPUNIT_ASSERT_EQUAL(0, memcmp(dest.data, zero, sizeof(zero))); - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::FLOAT, PCM_VOLUME_1)); - CPPUNIT_ASSERT_EQUAL(0, memcmp(dest, src, sizeof(src))); + pv.SetVolume(PCM_VOLUME_1); + dest = pv.Apply(src); + CPPUNIT_ASSERT_EQUAL(src.size, dest.size); + CPPUNIT_ASSERT_EQUAL(0, memcmp(dest.data, src.data, src.size)); - std::copy(src.begin(), src.end(), dest); - CPPUNIT_ASSERT_EQUAL(true, - pcm_volume(dest, sizeof(dest), - SampleFormat::FLOAT, - PCM_VOLUME_1 / 2)); + pv.SetVolume(PCM_VOLUME_1 / 2); + dest = pv.Apply(src); + CPPUNIT_ASSERT_EQUAL(src.size, dest.size); + const auto _dest = ConstBuffer<float>::FromVoid(dest); for (unsigned i = 0; i < N; ++i) - CPPUNIT_ASSERT_DOUBLES_EQUAL(src[i] / 2, dest[i], 1); + CPPUNIT_ASSERT_DOUBLES_EQUAL(_src[i] / 2, _dest.data[i], 1); + + pv.Close(); } diff --git a/test/test_queue_priority.cxx b/test/test_queue_priority.cxx index fca18fc2d..517cb028c 100644 --- a/test/test_queue_priority.cxx +++ b/test/test_queue_priority.cxx @@ -1,7 +1,6 @@ #include "config.h" -#include "Queue.hxx" -#include "Song.hxx" -#include "Directory.hxx" +#include "queue/Queue.hxx" +#include "DetachedSong.hxx" #include "util/Macros.hxx" #include <cppunit/TestFixture.h> @@ -9,21 +8,8 @@ #include <cppunit/ui/text/TestRunner.h> #include <cppunit/extensions/HelperMacros.h> -Directory detached_root; - -Directory::Directory() {} -Directory::~Directory() {} - -Song * -Song::DupDetached() const -{ - return const_cast<Song *>(this); -} - -void -Song::Free() -{ -} +Tag::Tag(const Tag &) {} +void Tag::Clear() {} static void check_descending_priority(const Queue *queue, @@ -53,12 +39,29 @@ public: void QueuePriorityTest::TestPriority() { - static Song songs[16]; + DetachedSong songs[16] = { + DetachedSong("0.ogg"), + DetachedSong("1.ogg"), + DetachedSong("2.ogg"), + DetachedSong("3.ogg"), + DetachedSong("4.ogg"), + DetachedSong("5.ogg"), + DetachedSong("6.ogg"), + DetachedSong("7.ogg"), + DetachedSong("8.ogg"), + DetachedSong("9.ogg"), + DetachedSong("a.ogg"), + DetachedSong("b.ogg"), + DetachedSong("c.ogg"), + DetachedSong("d.ogg"), + DetachedSong("e.ogg"), + DetachedSong("f.ogg"), + }; Queue queue(32); for (unsigned i = 0; i < ARRAY_SIZE(songs); ++i) - queue.Append(&songs[i], 0); + queue.Append(DetachedSong(songs[i]), 0); CPPUNIT_ASSERT_EQUAL(unsigned(ARRAY_SIZE(songs)), queue.GetLength()); diff --git a/test/test_translate_song.cxx b/test/test_translate_song.cxx new file mode 100644 index 000000000..006bd56aa --- /dev/null +++ b/test/test_translate_song.cxx @@ -0,0 +1,273 @@ +/* + * Unit tests for playlist_check_translate_song(). + */ + +#include "config.h" +#include "playlist/PlaylistSong.hxx" +#include "DetachedSong.hxx" +#include "tag/TagBuilder.hxx" +#include "tag/Tag.hxx" +#include "util/Domain.hxx" +#include "fs/AllocatedPath.hxx" +#include "ls.hxx" +#include "Log.hxx" +#include "db/DatabaseSong.hxx" +#include "Mapper.hxx" + +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <cppunit/ui/text/TestRunner.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <string.h> +#include <stdio.h> + +void +Log(const Domain &domain, gcc_unused LogLevel level, const char *msg) +{ + fprintf(stderr, "[%s] %s\n", domain.GetName(), msg); +} + +bool +uri_supported_scheme(const char *uri) +{ + return memcmp(uri, "http://", 7) == 0; +} + +const char *const music_directory = "/music"; + +const char * +map_to_relative_path(const char *path_utf8) +{ + size_t length = strlen(music_directory); + if (memcmp(path_utf8, music_directory, length) == 0 && + path_utf8[length] == '/') + path_utf8 += length + 1; + return path_utf8; +} + +static void +BuildTag(gcc_unused TagBuilder &tag) +{ +} + +template<typename... Args> +static void +BuildTag(TagBuilder &tag, TagType type, const char *value, Args&&... args) +{ + tag.AddItem(type, value); + BuildTag(tag, std::forward<Args>(args)...); +} + +template<typename... Args> +static Tag +MakeTag(Args&&... args) +{ + TagBuilder tag; + BuildTag(tag, std::forward<Args>(args)...); + return tag.Commit(); +} + +static Tag +MakeTag1a() +{ + return MakeTag(TAG_ARTIST, "artist_a1", TAG_TITLE, "title_a1", + TAG_ALBUM, "album_a1"); +} + +static Tag +MakeTag1b() +{ + return MakeTag(TAG_ARTIST, "artist_b1", TAG_TITLE, "title_b1", + TAG_COMMENT, "comment_b1"); +} + +static Tag +MakeTag1c() +{ + return MakeTag(TAG_ARTIST, "artist_b1", TAG_TITLE, "title_b1", + TAG_COMMENT, "comment_b1", TAG_ALBUM, "album_a1"); +} + +static Tag +MakeTag2a() +{ + return MakeTag(TAG_ARTIST, "artist_a2", TAG_TITLE, "title_a2", + TAG_ALBUM, "album_a2"); +} + +static Tag +MakeTag2b() +{ + return MakeTag(TAG_ARTIST, "artist_b2", TAG_TITLE, "title_b2", + TAG_COMMENT, "comment_b2"); +} + +static Tag +MakeTag2c() +{ + return MakeTag(TAG_ARTIST, "artist_b2", TAG_TITLE, "title_b2", + TAG_COMMENT, "comment_b2", TAG_ALBUM, "album_a2"); +} + +static const char *uri1 = "/foo/bar.ogg"; +static const char *uri2 = "foo/bar.ogg"; + +DetachedSong * +DatabaseDetachSong(const char *uri, gcc_unused Error &error) +{ + if (strcmp(uri, uri2) == 0) + return new DetachedSong(uri, MakeTag2a()); + + return nullptr; +} + +bool +DetachedSong::Update() +{ + if (strcmp(GetURI(), uri1) == 0) { + SetTag(MakeTag1a()); + return true; + } + + return false; +} + +static std::string +ToString(const Tag &tag) +{ + char buffer[64]; + sprintf(buffer, "%d", tag.time); + + std::string result = buffer; + + for (unsigned i = 0, n = tag.num_items; i != n; ++i) { + const TagItem &item = *tag.items[i]; + result.push_back('|'); + result.append(tag_item_names[item.type]); + result.push_back('='); + result.append(item.value); + } + + return result; +} + +static std::string +ToString(const DetachedSong &song) +{ + std::string result = song.GetURI(); + result.push_back('|'); + + char buffer[64]; + + if (song.GetLastModified() > 0) { + sprintf(buffer, "%lu", (unsigned long)song.GetLastModified()); + result.append(buffer); + } + + result.push_back('|'); + + if (song.GetStartMS() > 0) { + sprintf(buffer, "%u", song.GetStartMS()); + result.append(buffer); + } + + result.push_back('-'); + + if (song.GetEndMS() > 0) { + sprintf(buffer, "%u", song.GetEndMS()); + result.append(buffer); + } + + result.push_back('|'); + + result.append(ToString(song.GetTag())); + + return result; +} + +class TranslateSongTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(TranslateSongTest); + CPPUNIT_TEST(TestAbsoluteURI); + CPPUNIT_TEST(TestInsecure); + CPPUNIT_TEST(TestSecure); + CPPUNIT_TEST(TestInDatabase); + CPPUNIT_TEST(TestRelative); + CPPUNIT_TEST_SUITE_END(); + + void TestAbsoluteURI() { + DetachedSong song1("http://example.com/foo.ogg"); + auto se = ToString(song1); + CPPUNIT_ASSERT(playlist_check_translate_song(song1, "/ignored", false)); + CPPUNIT_ASSERT_EQUAL(se, ToString(song1)); + } + + void TestInsecure() { + /* illegal because secure=false */ + DetachedSong song1 (uri1); + CPPUNIT_ASSERT(!playlist_check_translate_song(song1, nullptr, false)); + } + + void TestSecure() { + DetachedSong song1(uri1, MakeTag1b()); + auto s1 = ToString(song1); + auto se = ToString(DetachedSong(uri1, MakeTag1c())); + CPPUNIT_ASSERT(playlist_check_translate_song(song1, "/ignored", true)); + CPPUNIT_ASSERT_EQUAL(se, ToString(song1)); + } + + void TestInDatabase() { + DetachedSong song1("doesntexist"); + CPPUNIT_ASSERT(!playlist_check_translate_song(song1, nullptr, false)); + + DetachedSong song2(uri2, MakeTag2b()); + auto s1 = ToString(song2); + auto se = ToString(DetachedSong(uri2, MakeTag2c())); + CPPUNIT_ASSERT(playlist_check_translate_song(song2, nullptr, false)); + CPPUNIT_ASSERT_EQUAL(se, ToString(song2)); + + DetachedSong song3("/music/foo/bar.ogg", MakeTag2b()); + s1 = ToString(song3); + se = ToString(DetachedSong(uri2, MakeTag2c())); + CPPUNIT_ASSERT(playlist_check_translate_song(song3, nullptr, false)); + CPPUNIT_ASSERT_EQUAL(se, ToString(song3)); + } + + void TestRelative() { + /* map to music_directory */ + DetachedSong song1("bar.ogg", MakeTag2b()); + auto s1 = ToString(song1); + auto se = ToString(DetachedSong(uri2, MakeTag2c())); + CPPUNIT_ASSERT(playlist_check_translate_song(song1, "/music/foo", false)); + CPPUNIT_ASSERT_EQUAL(se, ToString(song1)); + + /* illegal because secure=false */ + DetachedSong song2("bar.ogg", MakeTag2b()); + CPPUNIT_ASSERT(!playlist_check_translate_song(song1, "/foo", false)); + + /* legal because secure=true */ + DetachedSong song3("bar.ogg", MakeTag1b()); + s1 = ToString(song3); + se = ToString(DetachedSong(uri1, MakeTag1c())); + CPPUNIT_ASSERT(playlist_check_translate_song(song3, "/foo", true)); + CPPUNIT_ASSERT_EQUAL(se, ToString(song3)); + + /* relative to http:// */ + DetachedSong song4("bar.ogg", MakeTag2a()); + s1 = ToString(song4); + se = ToString(DetachedSong("http://example.com/foo/bar.ogg", MakeTag2a())); + CPPUNIT_ASSERT(playlist_check_translate_song(song4, "http://example.com/foo", false)); + CPPUNIT_ASSERT_EQUAL(se, ToString(song4)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TranslateSongTest); + +int +main(gcc_unused int argc, gcc_unused char **argv) +{ + CppUnit::TextUi::TestRunner runner; + auto ®istry = CppUnit::TestFactoryRegistry::getRegistry(); + runner.addTest(registry.makeTest()); + return runner.run() ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/test/test_vorbis_encoder.cxx b/test/test_vorbis_encoder.cxx index 1d95f6deb..59b901da2 100644 --- a/test/test_vorbis_encoder.cxx +++ b/test/test_vorbis_encoder.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,12 +18,13 @@ */ #include "config.h" -#include "EncoderList.hxx" -#include "EncoderPlugin.hxx" +#include "encoder/EncoderList.hxx" +#include "encoder/EncoderPlugin.hxx" #include "AudioFormat.hxx" -#include "ConfigData.hxx" +#include "config/ConfigData.hxx" #include "stdbin.h" #include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" #include "util/Error.hxx" #include <stddef.h> @@ -81,8 +82,13 @@ main(gcc_unused int argc, gcc_unused char **argv) encoder_to_stdout(*encoder); Tag tag; - tag.AddItem(TAG_ARTIST, "Foo"); - tag.AddItem(TAG_TITLE, "Bar"); + + { + TagBuilder tag_builder; + tag_builder.AddItem(TAG_ARTIST, "Foo"); + tag_builder.AddItem(TAG_TITLE, "Bar"); + tag_builder.Commit(tag); + } success = encoder_tag(encoder, &tag, IgnoreError()); assert(success); diff --git a/test/visit_archive.cxx b/test/visit_archive.cxx index 6e66c4696..0ff5706f2 100644 --- a/test/visit_archive.cxx +++ b/test/visit_archive.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,13 +20,13 @@ #include "config.h" #include "stdbin.h" #include "tag/Tag.hxx" -#include "ConfigGlobal.hxx" +#include "config/ConfigGlobal.hxx" #include "IOThread.hxx" -#include "InputInit.hxx" -#include "ArchiveList.hxx" -#include "ArchivePlugin.hxx" -#include "ArchiveFile.hxx" -#include "ArchiveVisitor.hxx" +#include "input/Init.hxx" +#include "archive/ArchiveList.hxx" +#include "archive/ArchivePlugin.hxx" +#include "archive/ArchiveFile.hxx" +#include "archive/ArchiveVisitor.hxx" #include "fs/Path.hxx" #include "util/Error.hxx" @@ -35,16 +35,6 @@ #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 { @@ -71,8 +61,6 @@ main(int argc, char **argv) g_thread_init(NULL); #endif - g_log_set_default_handler(my_log_func, NULL); - /* initialize MPD */ config_global_init(); diff --git a/valgrind.suppressions b/valgrind.suppressions index 03f2025ee..8d1d86c41 100644 --- a/valgrind.suppressions +++ b/valgrind.suppressions @@ -485,3 +485,25 @@ fun:call_init fun:_dl_init } + +# +# libsmbclient +# + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:*alloc + ... + fun:smbc_*_context + fun:smbc_init +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:*alloc + ... + fun:smbc_setDebug + fun:smbc_init +} |