diff options
Diffstat (limited to '')
464 files changed, 15172 insertions, 7953 deletions
diff --git a/Makefile.am b/Makefile.am index 7240cb3f1..c367ca455 100644 --- a/Makefile.am +++ b/Makefile.am @@ -82,6 +82,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 \ @@ -113,6 +114,7 @@ src_mpd_SOURCES = \ src/DatabaseLock.cxx src/DatabaseLock.hxx \ src/DatabaseSave.cxx src/DatabaseSave.hxx \ src/DatabasePlugin.hxx \ + src/DatabaseListener.hxx \ src/DatabaseVisitor.hxx \ src/DatabaseSelection.cxx src/DatabaseSelection.hxx \ src/ExcludeList.cxx src/ExcludeList.hxx \ @@ -146,7 +148,9 @@ src_mpd_SOURCES = \ src/ClientFile.cxx src/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 \ @@ -170,6 +174,7 @@ 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 \ @@ -188,6 +193,7 @@ src_mpd_SOURCES = \ src/ReplayGainConfig.cxx src/ReplayGainConfig.hxx \ src/ReplayGainInfo.cxx src/ReplayGainInfo.hxx \ src/SignalHandlers.cxx src/SignalHandlers.hxx \ + src/DetachedSong.cxx src/DetachedSong.hxx \ src/Song.cxx src/Song.hxx \ src/SongUpdate.cxx \ src/SongPrint.cxx src/SongPrint.hxx \ @@ -198,11 +204,10 @@ src_mpd_SOURCES = \ 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/TagStream.cxx src/TagStream.hxx \ src/TextInputStream.cxx \ src/Volume.cxx src/Volume.hxx \ src/SongFilter.cxx src/SongFilter.hxx \ - src/SongPointer.hxx \ src/PlaylistFile.cxx src/PlaylistFile.hxx \ src/Timer.cxx @@ -245,6 +250,10 @@ endif 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 +261,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 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 \ @@ -297,12 +309,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 +336,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,7 +387,9 @@ 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 # database plugins @@ -364,6 +397,7 @@ libfs_a_SOURCES = \ libdb_plugins_a_SOURCES = \ src/DatabaseRegistry.cxx src/DatabaseRegistry.hxx \ src/DatabaseHelpers.cxx src/DatabaseHelpers.hxx \ + src/db/LazyDatabase.cxx src/db/LazyDatabase.hxx \ src/db/SimpleDatabasePlugin.cxx src/db/SimpleDatabasePlugin.hxx if HAVE_LIBMPDCLIENT @@ -375,6 +409,24 @@ DB_LIBS = \ libdb_plugins.a \ $(LIBMPDCLIENT_LIBS) +if HAVE_LIBUPNP +libdb_plugins_a_SOURCES += \ + src/db/UpnpDatabasePlugin.cxx src/db/UpnpDatabasePlugin.hxx \ + src/db/upnp/ContentDirectoryService.cxx src/db/upnp/ContentDirectoryService.hxx \ + src/db/upnp/Device.cxx src/db/upnp/Device.hxx \ + src/db/upnp/Directory.cxx src/db/upnp/Directory.hxx \ + src/db/upnp/Discovery.cxx src/db/upnp/Discovery.hxx \ + src/db/upnp/Domain.cxx src/db/upnp/Domain.hxx \ + src/db/upnp/ixmlwrap.cxx src/db/upnp/ixmlwrap.hxx \ + src/db/upnp/upnpplib.cxx src/db/upnp/upnpplib.hxx \ + src/db/upnp/Util.cxx src/db/upnp/Util.hxx \ + src/db/upnp/WorkQueue.hxx \ + src/db/upnp/Object.hxx +DB_LIBS += \ + $(EXPAT_LIBS) \ + $(UPNP_LIBS) +endif + # archive plugins if ENABLE_ARCHIVE @@ -670,6 +722,7 @@ libencoder_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(TWOLAME_CFLAGS) \ $(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \ $(OPUS_CFLAGS) \ + $(SHINE_CFLAGS) \ $(VORBISENC_CFLAGS) ENCODER_LIBS = \ @@ -678,6 +731,7 @@ ENCODER_LIBS = \ $(TWOLAME_LIBS) \ $(FLAC_LIBS) \ $(OPUS_LIBS) \ + $(SHINE_LIBS) \ $(VORBISENC_LIBS) libencoder_plugins_a_SOURCES = \ @@ -728,6 +782,12 @@ libencoder_plugins_a_SOURCES += \ src/encoder/FlacEncoderPlugin.cxx src/encoder/FlacEncoderPlugin.hxx endif +if ENABLE_SHINE_ENCODER +libencoder_plugins_a_SOURCES += \ + src/encoder/ShineEncoderPlugin.cxx \ + src/encoder/ShineEncoderPlugin.hxx +endif + else ENCODER_LIBS = endif @@ -763,6 +823,7 @@ libinput_a_SOURCES = \ libinput_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(CURL_CFLAGS) \ + $(SMBCLIENT_CFLAGS) \ $(CDIO_PARANOIA_CFLAGS) \ $(FFMPEG_CFLAGS) \ $(DESPOTIFY_CFLAGS) \ @@ -771,17 +832,31 @@ 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/AlsaInputPlugin.cxx \ + src/input/AlsaInputPlugin.hxx +INPUT_LIBS += $(ALSA_LIBS) +endif + + if ENABLE_CURL libinput_a_SOURCES += \ src/input/CurlInputPlugin.cxx src/input/CurlInputPlugin.hxx \ src/IcyMetaDataParser.cxx src/IcyMetaDataParser.hxx endif +if ENABLE_SMBCLIENT +libinput_a_SOURCES += \ + src/input/SmbclientInputPlugin.cxx src/input/SmbclientInputPlugin.hxx +endif + if ENABLE_CDIO_PARANOIA libinput_a_SOURCES += \ src/input/CdioParanoiaInputPlugin.cxx \ @@ -963,25 +1038,19 @@ libplaylist_plugins_a_SOURCES = \ src/playlist/ExtM3uPlaylistPlugin.hxx \ src/playlist/M3uPlaylistPlugin.cxx \ src/playlist/M3uPlaylistPlugin.hxx \ - src/playlist/PlsPlaylistPlugin.cxx \ - src/playlist/PlsPlaylistPlugin.hxx \ - src/playlist/XspfPlaylistPlugin.cxx \ - src/playlist/XspfPlaylistPlugin.hxx \ - src/playlist/AsxPlaylistPlugin.cxx \ - src/playlist/AsxPlaylistPlugin.hxx \ - src/playlist/RssPlaylistPlugin.cxx \ - src/playlist/RssPlaylistPlugin.hxx \ src/playlist/CuePlaylistPlugin.cxx \ src/playlist/CuePlaylistPlugin.hxx \ src/playlist/EmbeddedCuePlaylistPlugin.cxx \ src/playlist/EmbeddedCuePlaylistPlugin.hxx \ src/PlaylistRegistry.cxx src/PlaylistRegistry.hxx libplaylist_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ + $(EXPAT_CFLAGS) \ $(YAJL_CFLAGS) \ $(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) PLAYLIST_LIBS = \ libplaylist_plugins.a \ + $(EXPAT_LIBS) \ $(FLAC_LIBS) if ENABLE_DESPOTIFY @@ -997,6 +1066,23 @@ libplaylist_plugins_a_SOURCES += \ PLAYLIST_LIBS += $(YAJL_LIBS) endif +if HAVE_EXPAT +libplaylist_plugins_a_SOURCES += \ + src/Expat.cxx src/Expat.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 +endif + +if HAVE_GLIB +libplaylist_plugins_a_SOURCES += \ + src/playlist/PlsPlaylistPlugin.cxx \ + src/playlist/PlsPlaylistPlugin.hxx +endif + # # Filter plugins # @@ -1110,7 +1196,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 = \ @@ -1118,7 +1204,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 = \ @@ -1126,12 +1212,13 @@ 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/Log.cxx src/LogBackend.cxx \ src/DatabaseError.cxx \ src/DatabaseRegistry.cxx \ src/DatabaseSelection.cxx \ @@ -1140,8 +1227,11 @@ test_DumpDatabase_SOURCES = test/DumpDatabase.cxx \ src/DatabaseLock.cxx src/DatabaseSave.cxx \ src/Song.cxx src/SongSave.cxx src/SongSort.cxx \ src/TagSave.cxx \ - src/SongFilter.cxx \ - src/TextFile.cxx + src/SongFilter.cxx + +if HAVE_LIBUPNP +test_DumpDatabase_SOURCES += src/Expat.cxx +endif test_run_input_LDADD = \ $(INPUT_LIBS) \ @@ -1156,7 +1246,7 @@ 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 @@ -1174,7 +1264,7 @@ 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 @@ -1197,7 +1287,7 @@ test_dump_text_file_LDADD = \ $(GLIB_LIBS) test_dump_text_file_SOURCES = test/dump_text_file.cxx \ test/stdbin.h \ - src/Log.cxx \ + src/Log.cxx src/LogBackend.cxx \ src/IOThread.cxx \ src/TextInputStream.cxx @@ -1217,10 +1307,11 @@ 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 \ @@ -1247,7 +1338,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 \ @@ -1270,7 +1361,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 \ @@ -1282,7 +1374,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 @@ -1296,7 +1388,7 @@ test_run_filter_LDADD = \ test_run_filter_SOURCES = test/run_filter.cxx \ test/FakeReplayGainConfig.cxx \ test/stdbin.h \ - src/Log.cxx \ + src/Log.cxx src/LogBackend.cxx \ src/FilterPlugin.cxx src/FilterRegistry.cxx \ src/CheckAudioFormat.cxx \ src/AudioFormat.cxx \ @@ -1316,7 +1408,7 @@ 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 @@ -1336,7 +1428,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 \ @@ -1356,7 +1448,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) \ @@ -1364,7 +1457,7 @@ test_software_volume_LDADD = \ $(GLIB_LIBS) test_run_avahi_SOURCES = \ - src/Log.cxx \ + src/Log.cxx src/LogBackend.cxx \ src/ZeroconfAvahi.cxx src/AvahiPoll.cxx \ test/ShutdownHandler.cxx test/ShutdownHandler.hxx \ test/run_avahi.cxx @@ -1386,7 +1479,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 @@ -1412,7 +1505,7 @@ 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 \ @@ -1440,9 +1533,10 @@ test_read_mixer_LDADD = \ libfs.a \ $(GLIB_LIBS) test_read_mixer_SOURCES = test/read_mixer.cxx \ - src/Log.cxx \ + src/Log.cxx src/LogBackend.cxx \ src/MixerControl.cxx \ src/FilterPlugin.cxx \ + src/AudioFormat.cxx \ src/filter/VolumeFilterPlugin.cxx if ENABLE_BZIP2_TEST @@ -1461,7 +1555,7 @@ 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/Log.cxx src/LogBackend.cxx \ src/InotifyDomain.cxx \ src/InotifySource.cxx test_run_inotify_LDADD = \ @@ -1488,7 +1582,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 @@ -1497,6 +1591,7 @@ test_test_mixramp_LDADD = \ $(CPPUNIT_LIBS) test_test_pcm_SOURCES = \ + src/AudioFormat.cxx \ test/test_pcm_util.hxx \ test/test_pcm_dither.cxx \ test/test_pcm_pack.cxx \ @@ -1515,7 +1610,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 @@ -1,3 +1,23 @@ +ver 0.19 (not yet released) +* protocol + - new commands "addtagid", "cleartagid" + - "lsinfo" and "readcomments" allowed for remote files +* database + - proxy: forward "idle" events + - 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 + ver 0.18.7 (2013/01/13) * playlist - pls: fix crash after parser error diff --git a/configure.ac b/configure.ac index 2cd0c7720..34fb4b1c1 100644 --- a/configure.ac +++ b/configure.ac @@ -1,9 +1,9 @@ AC_PREREQ(2.60) -AC_INIT(mpd, 0.18.7, musicpd-dev-team@lists.sourceforge.net) +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 @@ -13,7 +13,7 @@ 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]) dnl --------------------------------------------------------------------------- @@ -65,9 +65,20 @@ 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 @@ -76,14 +87,19 @@ mingw32* | windows*) 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= @@ -145,10 +161,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])]) @@ -157,6 +179,48 @@ AC_CHECK_HEADERS(locale.h) AC_CHECK_HEADERS(valgrind/memcheck.h) 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 +229,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 +246,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 +278,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 +374,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 +449,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 +467,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 +477,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 +502,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 +558,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 +613,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 +653,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 +794,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 +823,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]) @@ -798,6 +909,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 +1256,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 +1268,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 +1322,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 +1518,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 +1526,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 +1700,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 +1734,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 +1744,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' 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..a49f025b2 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,165 @@ 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</parameter>" + </entry> + <entry> + Use libsoxr. + </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 +906,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 +1067,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 +1370,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 +2395,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/ArchiveFile.hxx b/src/ArchiveFile.hxx index 4bdba62ab..154b4b297 100644 --- a/src/ArchiveFile.hxx +++ b/src/ArchiveFile.hxx @@ -23,6 +23,8 @@ class Mutex; class Cond; class Error; +class ArchiveVisitor; +struct InputStream; class ArchiveFile { public: diff --git a/src/ArchiveLookup.cxx b/src/ArchiveLookup.cxx index 7a93c136a..65925a6e3 100644 --- a/src/ArchiveLookup.cxx +++ b/src/ArchiveLookup.cxx @@ -24,7 +24,6 @@ #include <string.h> #include <sys/stat.h> -#include <unistd.h> #include <errno.h> gcc_pure diff --git a/src/ArchivePlugin.hxx b/src/ArchivePlugin.hxx index 6439c7242..c23826540 100644 --- a/src/ArchivePlugin.hxx +++ b/src/ArchivePlugin.hxx @@ -20,9 +20,7 @@ #ifndef MPD_ARCHIVE_PLUGIN_HXX #define MPD_ARCHIVE_PLUGIN_HXX -struct InputStream; class ArchiveFile; -class ArchiveVisitor; class Error; struct archive_plugin { diff --git a/src/AvahiPoll.cxx b/src/AvahiPoll.cxx index 0d5a43dad..de0f2b1e3 100644 --- a/src/AvahiPoll.cxx +++ b/src/AvahiPoll.cxx @@ -19,7 +19,6 @@ #include "config.h" #include "AvahiPoll.hxx" -#include "event/Loop.hxx" #include "event/SocketMonitor.hxx" #include "event/TimeoutMonitor.hxx" @@ -58,10 +57,6 @@ public: Schedule(FromAvahiWatchEvent(_event)); } - ~AvahiWatch() { - Steal(); - } - static void WatchUpdate(AvahiWatch *w, AvahiWatchEvent event) { w->Schedule(FromAvahiWatchEvent(event)); } diff --git a/src/Client.hxx b/src/Client.hxx index f0bc6b0f7..202cff4da 100644 --- a/src/Client.hxx +++ b/src/Client.hxx @@ -82,6 +82,11 @@ public: Client(EventLoop &loop, Partition &partition, int fd, int uid, int num); + ~Client() { + if (FullyBufferedSocket::IsDefined()) + FullyBufferedSocket::Close(); + } + bool IsConnected() const { return FullyBufferedSocket::IsDefined(); } @@ -165,7 +170,7 @@ void client_manager_init(void); void client_new(EventLoop &loop, Partition &partition, - int fd, const struct sockaddr *sa, size_t sa_length, int uid); + int fd, const sockaddr *sa, size_t sa_length, int uid); /** * Write a C string to the client. diff --git a/src/ClientFile.cxx b/src/ClientFile.cxx index 382b76083..d1b00ebbc 100644 --- a/src/ClientFile.cxx +++ b/src/ClientFile.cxx @@ -24,11 +24,8 @@ #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 diff --git a/src/ClientGlobal.cxx b/src/ClientGlobal.cxx index e79f3430b..8fb1f8e49 100644 --- a/src/ClientGlobal.cxx +++ b/src/ClientGlobal.cxx @@ -19,7 +19,6 @@ #include "config.h" #include "ClientInternal.hxx" -#include "ClientList.hxx" #include "ConfigGlobal.hxx" #define CLIENT_TIMEOUT_DEFAULT (60) diff --git a/src/ClientList.cxx b/src/ClientList.cxx index 37e6f1289..c530ff54a 100644 --- a/src/ClientList.cxx +++ b/src/ClientList.cxx @@ -40,8 +40,14 @@ ClientList::Remove(Client &client) void ClientList::CloseAll() { - while (!list.empty()) - list.front()->Close(); + while (!list.empty()) { + delete list.front(); + list.pop_front(); + +#ifndef NDEBUG + --size; +#endif + } assert(size == 0); } diff --git a/src/ClientNew.cxx b/src/ClientNew.cxx index e84887072..5618e9ece 100644 --- a/src/ClientNew.cxx +++ b/src/ClientNew.cxx @@ -28,16 +28,12 @@ #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> @@ -65,15 +61,14 @@ 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; + const auto remote = sockaddr_to_string(sa, sa_length); 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(); + // 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); @@ -84,14 +79,11 @@ client_new(EventLoop &loop, Partition &partition, /* tcp wrappers says no */ FormatWarning(client_domain, "libwrap refused connection (libwrap=%s) from %s", - progname, hostaddr); + progname, remote.c_str()); - g_free(hostaddr); close_socket(fd); return; } - - g_free(hostaddr); } #endif /* HAVE_WRAP */ @@ -109,9 +101,8 @@ client_new(EventLoop &loop, Partition &partition, 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); + FormatInfo(client_domain, "[%u] opened from %s", + client->num, remote.c_str()); } void diff --git a/src/ClientRead.cxx b/src/ClientRead.cxx index 22edefe60..205884815 100644 --- a/src/ClientRead.cxx +++ b/src/ClientRead.cxx @@ -23,7 +23,6 @@ #include "event/Loop.hxx" #include "util/CharUtil.hxx" -#include <assert.h> #include <string.h> BufferedSocket::InputResult diff --git a/src/ClientSubscribe.cxx b/src/ClientSubscribe.cxx index 3a9f1b19c..f4d9ad316 100644 --- a/src/ClientSubscribe.cxx +++ b/src/ClientSubscribe.cxx @@ -22,11 +22,6 @@ #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) diff --git a/src/CommandLine.cxx b/src/CommandLine.cxx index 05f0a358c..58d07ddb7 100644 --- a/src/CommandLine.cxx +++ b/src/CommandLine.cxx @@ -34,9 +34,11 @@ #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_ENCODER #include "EncoderList.hxx" @@ -48,19 +50,37 @@ #include "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 @@ -132,122 +152,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_pure -static AllocatedPath -PathBuildChecked(const AllocatedPath &a, PathTraits::const_pointer b) +gcc_noreturn +static void help(void) { - if (a.IsNull()) - return AllocatedPath::Null(); + puts("Usage:\n" + " mpd [OPTION...] [path/to/mpd.conf]\n" + "\n" + "Music Player Daemon - a daemon for playing music.\n" + "\n" + "Options:"); - return AllocatedPath::Build(a, b); + 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); +} + +class ConfigLoader +{ + Error &error; + bool result; +public: + ConfigLoader(Error &_error) : error(_error), result(false) { } + + 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..3b24a0d77 100644 --- a/src/CommandLine.hxx +++ b/src/CommandLine.hxx @@ -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/ConfigData.cxx b/src/ConfigData.cxx index c6cabee6e..cd4e34a94 100644 --- a/src/ConfigData.cxx +++ b/src/ConfigData.cxx @@ -26,7 +26,6 @@ #include "system/FatalError.hxx" #include <assert.h> -#include <string.h> #include <stdlib.h> int @@ -64,7 +63,7 @@ block_param::GetBoolValue() const } config_param::config_param(const char *_value, int _line) - :next(nullptr), value(_value), line(_line) {} + :next(nullptr), value(_value), line(_line), used(false) {} config_param::~config_param() { diff --git a/src/ConfigFile.cxx b/src/ConfigFile.cxx index 90859a89a..16b23bc0d 100644 --- a/src/ConfigFile.cxx +++ b/src/ConfigFile.cxx @@ -19,7 +19,6 @@ #include "config.h" #include "ConfigFile.hxx" -#include "ConfigError.hxx" #include "ConfigData.hxx" #include "ConfigTemplates.hxx" #include "util/Tokenizer.hxx" @@ -32,9 +31,7 @@ #include "Log.hxx" #include <assert.h> -#include <string.h> #include <stdio.h> -#include <errno.h> #define MAX_STRING_SIZE MPD_PATH_MAX+80 diff --git a/src/ConfigGlobal.cxx b/src/ConfigGlobal.cxx index 49b9c08fb..93929ccb4 100644 --- a/src/ConfigGlobal.cxx +++ b/src/ConfigGlobal.cxx @@ -30,8 +30,6 @@ #include "system/FatalError.hxx" #include "Log.hxx" -#include <assert.h> -#include <string.h> #include <stdlib.h> static ConfigData config_data; diff --git a/src/ConfigPath.cxx b/src/ConfigPath.cxx index b88de3934..a2d7c2bb4 100644 --- a/src/ConfigPath.cxx +++ b/src/ConfigPath.cxx @@ -22,11 +22,10 @@ #include "fs/AllocatedPath.hxx" #include "fs/Traits.hxx" #include "fs/Domain.hxx" +#include "fs/StandardDirectory.hxx" #include "util/Error.hxx" #include "ConfigGlobal.hxx" -#include <glib.h> - #include <assert.h> #include <string.h> @@ -39,14 +38,14 @@ static AllocatedPath GetHome(const char *user, Error &error) { - passwd *pw = getpwnam(user); - if (pw == nullptr) { + AllocatedPath result = GetHomeDir(user); + if (result.IsNull()) { error.Format(path_domain, "no such user: %s", user); return AllocatedPath::Null(); } - return AllocatedPath::FromFS(pw->pw_dir); + return result; } /** @@ -55,14 +54,14 @@ GetHome(const char *user, Error &error) static AllocatedPath GetHome(Error &error) { - const char *home = g_get_home_dir(); - if (home == nullptr) { + AllocatedPath result = GetHomeDir(); + if (result.IsNull()) { error.Set(path_domain, "problems getting home for current user"); return AllocatedPath::Null(); } - return AllocatedPath::FromUTF8(home, error); + return result; } /** @@ -119,7 +118,7 @@ ParsePath(const char *path, Error &error) return AllocatedPath::Null(); return AllocatedPath::Build(home, path2); - } else if (!PathTraits::IsAbsoluteUTF8(path)) { + } else if (!PathTraitsUTF8::IsAbsolute(path)) { error.Format(path_domain, "not an absolute path: %s", path); return AllocatedPath::Null(); diff --git a/src/ConfigTemplates.hxx b/src/ConfigTemplates.hxx index 4f5460460..1fbe3b737 100644 --- a/src/ConfigTemplates.hxx +++ b/src/ConfigTemplates.hxx @@ -20,8 +20,6 @@ #ifndef MPD_CONFIG_TEMPLATES_HXX #define MPD_CONFIG_TEMPLATES_HXX -#include "ConfigOption.hxx" - struct ConfigTemplate { const char *const name; const bool repeatable; diff --git a/src/CrossFade.cxx b/src/CrossFade.cxx index 601d74dc2..c1dafd951 100644 --- a/src/CrossFade.cxx +++ b/src/CrossFade.cxx @@ -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/Daemon.cxx b/src/Daemon.cxx index 557c47777..0a4eea841 100644 --- a/src/Daemon.cxx +++ b/src/Daemon.cxx @@ -25,14 +25,9 @@ #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 @@ -215,10 +210,10 @@ daemonize_init(const char *user, const char *group, AllocatedPath &&_pidfile) user_uid = pwd->pw_uid; user_gid = pwd->pw_gid; - user_name = g_strdup(user); + user_name = strdup(user); /* this is needed by libs such as arts */ - g_setenv("HOME", pwd->pw_dir, true); + setenv("HOME", pwd->pw_dir, true); } if (group) { @@ -241,7 +236,7 @@ daemonize_finish(void) pidfile = AllocatedPath::Null(); } - g_free(user_name); + free(user_name); } #endif diff --git a/src/DatabaseGlue.cxx b/src/DatabaseGlue.cxx index 013a3e329..d5aad0522 100644 --- a/src/DatabaseGlue.cxx +++ b/src/DatabaseGlue.cxx @@ -21,7 +21,6 @@ #include "DatabaseGlue.hxx" #include "DatabaseSimple.hxx" #include "DatabaseRegistry.hxx" -#include "DatabaseSave.hxx" #include "DatabaseError.hxx" #include "Directory.hxx" #include "util/Error.hxx" @@ -30,20 +29,16 @@ #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) +DatabaseGlobalInit(EventLoop &loop, DatabaseListener &listener, + const config_param ¶m, Error &error) { assert(db == nullptr); assert(!db_is_open); @@ -59,7 +54,7 @@ DatabaseGlobalInit(const config_param ¶m, Error &error) return false; } - db = plugin->create(param, error); + db = plugin->create(loop, listener, param, error); return db != nullptr; } @@ -143,8 +138,6 @@ DatabaseGlobalOpen(Error &error) db_is_open = true; - stats_update(); - return true; } diff --git a/src/DatabaseGlue.hxx b/src/DatabaseGlue.hxx index bc05942e0..bf4699ff6 100644 --- a/src/DatabaseGlue.hxx +++ b/src/DatabaseGlue.hxx @@ -23,6 +23,8 @@ #include "Compiler.h" struct config_param; +class EventLoop; +class DatabaseListener; class Database; class Error; @@ -32,7 +34,8 @@ class Error; * @param param the database configuration block */ bool -DatabaseGlobalInit(const config_param ¶m, Error &error); +DatabaseGlobalInit(EventLoop &loop, DatabaseListener &listener, + const config_param ¶m, Error &error); void DatabaseGlobalDeinit(void); @@ -44,7 +47,7 @@ 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 +gcc_const const Database * GetDatabase(); diff --git a/src/DatabaseHelpers.hxx b/src/DatabaseHelpers.hxx index d8806bc69..9dc82bcda 100644 --- a/src/DatabaseHelpers.hxx +++ b/src/DatabaseHelpers.hxx @@ -22,7 +22,6 @@ #include "DatabaseVisitor.hxx" #include "tag/TagType.h" -#include "Compiler.h" class Error; class Database; diff --git a/src/DatabaseListener.hxx b/src/DatabaseListener.hxx new file mode 100644 index 000000000..4da458866 --- /dev/null +++ b/src/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/DatabaseLock.cxx b/src/DatabaseLock.cxx index d85f72d3b..660515d4e 100644 --- a/src/DatabaseLock.cxx +++ b/src/DatabaseLock.cxx @@ -19,7 +19,6 @@ #include "config.h" #include "DatabaseLock.hxx" -#include "Compiler.h" Mutex db_mutex; diff --git a/src/DatabasePlaylist.cxx b/src/DatabasePlaylist.cxx index b0cb19589..a29c9f2bc 100644 --- a/src/DatabasePlaylist.cxx +++ b/src/DatabasePlaylist.cxx @@ -23,6 +23,7 @@ #include "PlaylistFile.hxx" #include "DatabaseGlue.hxx" #include "DatabasePlugin.hxx" +#include "DetachedSong.hxx" #include <functional> @@ -30,7 +31,7 @@ static bool AddSong(const char *playlist_path_utf8, Song &song, Error &error) { - return spl_append_song(playlist_path_utf8, song, error); + return spl_append_song(playlist_path_utf8, DetachedSong(song), error); } bool diff --git a/src/DatabasePlugin.hxx b/src/DatabasePlugin.hxx index ccf899389..e42c3bbd3 100644 --- a/src/DatabasePlugin.hxx +++ b/src/DatabasePlugin.hxx @@ -37,6 +37,8 @@ struct DatabaseSelection; struct db_visitor; struct Song; class Error; +class EventLoop; +class DatabaseListener; struct DatabaseStats { /** @@ -149,7 +151,8 @@ struct DatabasePlugin { /** * Allocates and configures a database. */ - Database *(*create)(const config_param ¶m, + Database *(*create)(EventLoop &loop, DatabaseListener &listener, + const config_param ¶m, Error &error); }; diff --git a/src/DatabasePrint.cxx b/src/DatabasePrint.cxx index 3732e98f3..feb58a263 100644 --- a/src/DatabasePrint.cxx +++ b/src/DatabasePrint.cxx @@ -21,7 +21,6 @@ #include "DatabasePrint.hxx" #include "DatabaseSelection.hxx" #include "SongFilter.hxx" -#include "PlaylistVector.hxx" #include "SongPrint.hxx" #include "TimePrint.hxx" #include "Directory.hxx" @@ -56,26 +55,24 @@ PrintDirectoryFull(Client &client, const Directory &directory) static void print_playlist_in_directory(Client &client, - const Directory &directory, + const Directory *directory, const char *name_utf8) { - if (directory.IsRoot()) + 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); + 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); + print_playlist_in_directory(client, song.parent, song.uri); return true; } @@ -83,13 +80,11 @@ PrintSongBrief(Client &client, const Song &song) 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); + print_playlist_in_directory(client, song.parent, song.uri); return true; } @@ -99,7 +94,7 @@ PrintPlaylistBrief(Client &client, const PlaylistInfo &playlist, const Directory &directory) { - print_playlist_in_directory(client, directory, playlist.name.c_str()); + print_playlist_in_directory(client, &directory, playlist.name.c_str()); return true; } @@ -108,7 +103,7 @@ PrintPlaylistFull(Client &client, const PlaylistInfo &playlist, const Directory &directory) { - print_playlist_in_directory(client, directory, playlist.name.c_str()); + print_playlist_in_directory(client, &directory, playlist.name.c_str()); if (playlist.mtime > 0) time_print(client, "Last-Modified", playlist.mtime); diff --git a/src/DatabasePrint.hxx b/src/DatabasePrint.hxx index 36a68d87b..0ed7ff1e7 100644 --- a/src/DatabasePrint.hxx +++ b/src/DatabasePrint.hxx @@ -24,7 +24,6 @@ class SongFilter; struct DatabaseSelection; -struct db_visitor; class Client; class Error; diff --git a/src/DatabaseQueue.cxx b/src/DatabaseQueue.cxx index 98f80c5b6..3dac54630 100644 --- a/src/DatabaseQueue.cxx +++ b/src/DatabaseQueue.cxx @@ -19,19 +19,20 @@ #include "config.h" #include "DatabaseQueue.hxx" -#include "DatabaseSelection.hxx" #include "DatabaseGlue.hxx" #include "DatabasePlugin.hxx" #include "Partition.hxx" #include "util/Error.hxx" +#include "DetachedSong.hxx" #include <functional> static bool -AddToQueue(Partition &partition, Song &song, Error &error) +AddToQueue(Partition &partition, const Song &song, Error &error) { PlaylistResult result = - partition.playlist.AppendSong(partition.pc, &song, nullptr); + partition.playlist.AppendSong(partition.pc, DetachedSong(song), + nullptr); if (result != PlaylistResult::SUCCESS) { error.Set(playlist_domain, int(result), "Playlist error"); return false; diff --git a/src/DatabaseRegistry.cxx b/src/DatabaseRegistry.cxx index 4ce4a62cb..6937d51c1 100644 --- a/src/DatabaseRegistry.cxx +++ b/src/DatabaseRegistry.cxx @@ -21,6 +21,7 @@ #include "DatabaseRegistry.hxx" #include "db/SimpleDatabasePlugin.hxx" #include "db/ProxyDatabasePlugin.hxx" +#include "db/UpnpDatabasePlugin.hxx" #include <string.h> @@ -29,6 +30,9 @@ const DatabasePlugin *const database_plugins[] = { #ifdef HAVE_LIBMPDCLIENT &proxy_db_plugin, #endif +#ifdef HAVE_LIBUPNP + &upnp_db_plugin, +#endif nullptr }; diff --git a/src/DatabaseSave.cxx b/src/DatabaseSave.cxx index abfd4a34f..f41edefda 100644 --- a/src/DatabaseSave.cxx +++ b/src/DatabaseSave.cxx @@ -23,17 +23,14 @@ #include "DatabaseError.hxx" #include "Directory.hxx" #include "DirectorySave.hxx" -#include "Song.hxx" -#include "TextFile.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 <glib.h> - -#include <assert.h> #include <string.h> #include <stdlib.h> @@ -83,16 +80,16 @@ db_load_internal(TextFile &file, Directory &music_root, Error &error) while ((line = file.ReadLine()) != nullptr && strcmp(line, DIRECTORY_INFO_END) != 0) { - if (g_str_has_prefix(line, DB_FORMAT_PREFIX)) { + if (StringStartsWith(line, DB_FORMAT_PREFIX)) { format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1); - } else if (g_str_has_prefix(line, DIRECTORY_MPD_VERSION)) { + } else if (StringStartsWith(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)) { + } else if (StringStartsWith(line, DIRECTORY_FS_CHARSET)) { const char *new_charset; if (found_charset) { @@ -113,7 +110,7 @@ db_load_internal(TextFile &file, Directory &music_root, Error &error) new_charset, old_charset); return false; } - } else if (g_str_has_prefix(line, DB_TAG_PREFIX)) { + } 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) { diff --git a/src/DecoderAPI.cxx b/src/DecoderAPI.cxx index 4fea02bef..148d20005 100644 --- a/src/DecoderAPI.cxx +++ b/src/DecoderAPI.cxx @@ -20,6 +20,7 @@ #include "config.h" #include "DecoderAPI.hxx" #include "DecoderError.hxx" +#include "pcm/PcmConvert.hxx" #include "AudioConfig.hxx" #include "ReplayGainConfig.hxx" #include "MusicChunk.hxx" @@ -27,13 +28,12 @@ #include "MusicPipe.hxx" #include "DecoderControl.hxx" #include "DecoderInternal.hxx" -#include "Song.hxx" +#include "DetachedSong.hxx" #include "InputStream.hxx" #include "util/Error.hxx" #include "Log.hxx" #include <assert.h> -#include <stdlib.h> #include <string.h> #include <math.h> @@ -47,6 +47,7 @@ decoder_initialized(Decoder &decoder, 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); @@ -59,19 +60,28 @@ decoder_initialized(Decoder &decoder, 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) + 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(); } /** @@ -129,6 +139,10 @@ 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); @@ -292,6 +306,40 @@ decoder_read(Decoder *decoder, 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) { @@ -312,12 +360,12 @@ do_send_tag(Decoder &decoder, const Tag &tag) if (decoder.chunk != nullptr) { /* there is a partial chunk - flush it, we want the tag in a new chunk */ - decoder_flush_chunk(decoder); + decoder.FlushChunk(); } assert(decoder.chunk == nullptr); - chunk = decoder_get_chunk(decoder); + chunk = decoder.GetChunk(); if (chunk == nullptr) { assert(decoder.dc.command != DecoderCommand::NONE); return decoder.dc.command; @@ -388,13 +436,13 @@ decoder_data(Decoder &decoder, return cmd; } - if (dc.in_audio_format != dc.out_audio_format) { + if (decoder.convert != nullptr) { + assert(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); + 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 @@ -402,13 +450,15 @@ decoder_data(Decoder &decoder, 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_get_chunk(decoder); + chunk = decoder.GetChunk(); if (chunk == nullptr) { assert(dc.command != DecoderCommand::NONE); return dc.command; @@ -417,11 +467,11 @@ decoder_data(Decoder &decoder, const auto dest = chunk->Write(dc.out_audio_format, decoder.timestamp - - dc.song->start_ms / 1000.0, + dc.song->GetStartMS() / 1000.0, kbit_rate); if (dest.IsNull()) { /* the chunk is full, flush it */ - decoder_flush_chunk(decoder); + decoder.FlushChunk(); continue; } @@ -440,7 +490,7 @@ decoder_data(Decoder &decoder, full = chunk->Expand(dc.out_audio_format, nbytes); if (full) { /* the chunk is full, flush it */ - decoder_flush_chunk(decoder); + decoder.FlushChunk(); } data = (const uint8_t *)data + nbytes; @@ -532,7 +582,7 @@ decoder_replay_gain(Decoder &decoder, /* flush the current chunk because the new replay gain values affect the following samples */ - decoder_flush_chunk(decoder); + decoder.FlushChunk(); } } else decoder.replay_gain_serial = 0; diff --git a/src/DecoderAPI.hxx b/src/DecoderAPI.hxx index 2ee42483c..bb693dbc4 100644 --- a/src/DecoderAPI.hxx +++ b/src/DecoderAPI.hxx @@ -27,6 +27,8 @@ #ifndef MPD_DECODER_API_HXX #define MPD_DECODER_API_HXX +// IWYU pragma: begin_exports + #include "check.h" #include "DecoderCommand.hxx" #include "DecoderPlugin.hxx" @@ -36,6 +38,8 @@ #include "MixRampInfo.hxx" #include "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. @@ -113,6 +117,25 @@ decoder_read(Decoder &decoder, InputStream &is, } /** + * 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 diff --git a/src/DecoderBuffer.cxx b/src/DecoderBuffer.cxx index 6aad53cb2..18fd3aa5c 100644 --- a/src/DecoderBuffer.cxx +++ b/src/DecoderBuffer.cxx @@ -20,11 +20,12 @@ #include "config.h" #include "DecoderBuffer.hxx" #include "DecoderAPI.hxx" - -#include <glib.h> +#include "util/ConstBuffer.hxx" +#include "util/VarSize.hxx" #include <assert.h> #include <string.h> +#include <stdlib.h> struct DecoderBuffer { Decoder *decoder; @@ -42,24 +43,22 @@ struct DecoderBuffer { /** 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) { - 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; + return NewVarSize<DecoderBuffer>(sizeof(DecoderBuffer::data), + size, + decoder, is, size); } void @@ -67,7 +66,7 @@ decoder_buffer_free(DecoderBuffer *buffer) { assert(buffer != nullptr); - g_free(buffer); + DeleteVarSize(buffer); } bool @@ -82,6 +81,12 @@ 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) { @@ -118,15 +123,13 @@ decoder_buffer_fill(DecoderBuffer *buffer) return true; } -const void * -decoder_buffer_read(const DecoderBuffer *buffer, size_t *length_r) +ConstBuffer<void> +decoder_buffer_read(const DecoderBuffer *buffer) { - if (buffer->consumed >= buffer->length) - /* buffer is empty */ - return nullptr; - - *length_r = buffer->length - buffer->consumed; - return buffer->data + buffer->consumed; + return { + buffer->data + buffer->consumed, + buffer->length - buffer->consumed + }; } void @@ -143,19 +146,17 @@ decoder_buffer_consume(DecoderBuffer *buffer, size_t nbytes) 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; + 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; } diff --git a/src/DecoderBuffer.hxx b/src/DecoderBuffer.hxx index 92cc31aa4..f298d4429 100644 --- a/src/DecoderBuffer.hxx +++ b/src/DecoderBuffer.hxx @@ -20,6 +20,8 @@ #ifndef MPD_DECODER_BUFFER_HXX #define MPD_DECODER_BUFFER_HXX +#include "Compiler.h" + #include <stddef.h> /** @@ -32,6 +34,8 @@ struct DecoderBuffer; struct Decoder; struct InputStream; +template<typename T> struct ConstBuffer; + /** * Creates a new buffer. * @@ -50,12 +54,17 @@ decoder_buffer_new(Decoder *decoder, InputStream &is, 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. * @@ -73,13 +82,10 @@ decoder_buffer_fill(DecoderBuffer *buffer); * 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); +gcc_pure +ConstBuffer<void> +decoder_buffer_read(const DecoderBuffer *buffer); /** * Consume (delete, invalidate) a part of the buffer. The "nbytes" diff --git a/src/DecoderControl.cxx b/src/DecoderControl.cxx index ab460ced0..4e5e894e3 100644 --- a/src/DecoderControl.cxx +++ b/src/DecoderControl.cxx @@ -20,9 +20,7 @@ #include "config.h" #include "DecoderControl.hxx" #include "MusicPipe.hxx" -#include "Song.hxx" - -#include <glib.h> +#include "DetachedSong.hxx" #include <assert.h> @@ -38,8 +36,7 @@ DecoderControl::~DecoderControl() { ClearError(); - if (song != nullptr) - song->Free(); + delete song; } void @@ -55,7 +52,7 @@ DecoderControl::WaitForDecoder() } bool -DecoderControl::IsCurrentSong(const Song &_song) const +DecoderControl::IsCurrentSong(const DetachedSong &_song) const { switch (state) { case DecoderState::STOP: @@ -64,7 +61,7 @@ DecoderControl::IsCurrentSong(const Song &_song) const case DecoderState::START: case DecoderState::DECODE: - return SongEquals(*song, _song); + return song->IsSame(_song); } assert(false); @@ -72,16 +69,14 @@ DecoderControl::IsCurrentSong(const Song &_song) const } void -DecoderControl::Start(Song *_song, +DecoderControl::Start(DetachedSong *_song, unsigned _start_ms, unsigned _end_ms, MusicBuffer &_buffer, MusicPipe &_pipe) { assert(_song != nullptr); assert(_pipe.IsEmpty()); - if (song != nullptr) - song->Free(); - + delete song; song = _song; start_ms = _start_ms; end_ms = _end_ms; diff --git a/src/DecoderControl.hxx b/src/DecoderControl.hxx index 863398dca..bacdad347 100644 --- a/src/DecoderControl.hxx +++ b/src/DecoderControl.hxx @@ -36,7 +36,7 @@ #undef ERROR #endif -struct Song; +class DetachedSong; class MusicBuffer; class MusicPipe; @@ -123,7 +123,7 @@ struct DecoderControl { * This is a duplicate, and must be freed when this attribute * is cleared. */ - Song *song; + DetachedSong *song; /** * The initial seek position (in milliseconds), e.g. to the @@ -293,10 +293,10 @@ struct DecoderControl { * Caller must lock the object. */ gcc_pure - bool IsCurrentSong(const Song &_song) const; + bool IsCurrentSong(const DetachedSong &_song) const; gcc_pure - bool LockIsCurrentSong(const Song &_song) const { + bool LockIsCurrentSong(const DetachedSong &_song) const { Lock(); const bool result = IsCurrentSong(_song); Unlock(); @@ -360,7 +360,7 @@ public: * @param pipe the pipe which receives the decoded chunks (owned by * the caller) */ - void Start(Song *song, unsigned start_ms, unsigned end_ms, + void Start(DetachedSong *song, unsigned start_ms, unsigned end_ms, MusicBuffer &buffer, MusicPipe &pipe); void Stop(); diff --git a/src/DecoderInternal.cxx b/src/DecoderInternal.cxx index d5f40ad48..38e54a36c 100644 --- a/src/DecoderInternal.cxx +++ b/src/DecoderInternal.cxx @@ -20,6 +20,7 @@ #include "config.h" #include "DecoderInternal.hxx" #include "DecoderControl.hxx" +#include "pcm/PcmConvert.hxx" #include "MusicPipe.hxx" #include "MusicBuffer.hxx" #include "MusicChunk.hxx" @@ -32,6 +33,11 @@ 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; @@ -51,24 +57,21 @@ need_chunks(DecoderControl &dc) } struct music_chunk * -decoder_get_chunk(Decoder &decoder) +Decoder::GetChunk() { - DecoderControl &dc = decoder.dc; DecoderCommand cmd; - if (decoder.chunk != nullptr) - return decoder.chunk; + if (chunk != nullptr) + return 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; + 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(); @@ -80,18 +83,16 @@ decoder_get_chunk(Decoder &decoder) } void -decoder_flush_chunk(Decoder &decoder) +Decoder::FlushChunk() { - DecoderControl &dc = decoder.dc; - - assert(decoder.chunk != nullptr); + assert(chunk != nullptr); - if (decoder.chunk->IsEmpty()) - dc.buffer->Return(decoder.chunk); + if (chunk->IsEmpty()) + dc.buffer->Return(chunk); else - dc.pipe->Push(decoder.chunk); + dc.pipe->Push(chunk); - decoder.chunk = nullptr; + chunk = nullptr; dc.Lock(); if (dc.client_is_waiting) diff --git a/src/DecoderInternal.hxx b/src/DecoderInternal.hxx index 46069a561..fbd613a36 100644 --- a/src/DecoderInternal.hxx +++ b/src/DecoderInternal.hxx @@ -20,18 +20,21 @@ #ifndef MPD_DECODER_INTERNAL_HXX #define MPD_DECODER_INTERNAL_HXX -#include "DecoderCommand.hxx" -#include "pcm/PcmConvert.hxx" #include "ReplayGainInfo.hxx" +#include "util/Error.hxx" +class PcmConvert; struct DecoderControl; -struct InputStream; struct Tag; struct Decoder { DecoderControl &dc; - PcmConvert conv_state; + /** + * 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. @@ -83,8 +86,15 @@ struct Decoder { */ 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), @@ -95,23 +105,21 @@ struct Decoder { } ~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); + /** + * 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 -decoder_flush_chunk(Decoder &decoder); + /** + * Flushes the current chunk. + * + * Caller must not lock the #DecoderControl object. + */ + void FlushChunk(); +}; #endif diff --git a/src/DecoderList.cxx b/src/DecoderList.cxx index 834178260..b077da035 100644 --- a/src/DecoderList.cxx +++ b/src/DecoderList.cxx @@ -118,65 +118,6 @@ static constexpr unsigned num_decoder_plugins = /** 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) { @@ -235,3 +176,11 @@ void decoder_plugin_deinit_all(void) 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/DecoderList.hxx b/src/DecoderList.hxx index fd4b22c63..50848ad4e 100644 --- a/src/DecoderList.hxx +++ b/src/DecoderList.hxx @@ -20,6 +20,8 @@ #ifndef MPD_DECODER_LIST_HXX #define MPD_DECODER_LIST_HXX +#include "Compiler.h" + struct DecoderPlugin; extern const struct DecoderPlugin *const decoder_plugins[]; @@ -27,20 +29,7 @@ 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); - +gcc_pure const struct DecoderPlugin * decoder_plugin_from_name(const char *name); @@ -89,4 +78,12 @@ decoder_plugins_for_each_enabled(F f) 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/DecoderPlugin.hxx b/src/DecoderPlugin.hxx index 6b0340123..41e009bec 100644 --- a/src/DecoderPlugin.hxx +++ b/src/DecoderPlugin.hxx @@ -24,7 +24,6 @@ struct config_param; struct InputStream; -struct Tag; struct tag_handler; /** diff --git a/src/DecoderThread.cxx b/src/DecoderThread.cxx index 72fc3cfb4..7099b3bd4 100644 --- a/src/DecoderThread.cxx +++ b/src/DecoderThread.cxx @@ -23,13 +23,12 @@ #include "DecoderInternal.hxx" #include "DecoderError.hxx" #include "DecoderPlugin.hxx" -#include "Song.hxx" +#include "DetachedSong.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" @@ -146,7 +145,7 @@ decoder_file_decode(const DecoderPlugin &plugin, assert(decoder.stream_tag == nullptr); assert(decoder.decoder_tag == nullptr); assert(path != nullptr); - assert(PathTraits::IsAbsoluteFS(path)); + assert(PathTraitsFS::IsAbsolute(path)); assert(decoder.dc.state == DecoderState::START); FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name); @@ -281,54 +280,66 @@ decoder_load_replay_gain(Decoder &decoder, const char *path_fs) decoder_replay_gain(decoder, &info); } -/** - * Try decoding a file. - */ static bool -decoder_run_file(Decoder &decoder, const char *path_fs) +TryDecoderFile(Decoder &decoder, const char *path_fs, const char *suffix, + const DecoderPlugin &plugin) { + if (!plugin.SupportsSuffix(suffix)) + return false; + DecoderControl &dc = decoder.dc; - const char *suffix = uri_get_suffix(path_fs); - const struct DecoderPlugin *plugin = nullptr; - if (suffix == nullptr) - return false; + if (plugin.file_decode != nullptr) { + dc.Lock(); - dc.Unlock(); + if (decoder_file_decode(plugin, decoder, path_fs)) + return true; - decoder_load_replay_gain(decoder, path_fs); + dc.Unlock(); + } else if (plugin.stream_decode != nullptr) { + InputStream *input_stream = + decoder_input_stream_open(dc, path_fs); + if (input_stream == nullptr) + return false; - while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != nullptr) { - if (plugin->file_decode != nullptr) { - dc.Lock(); + dc.Lock(); - if (decoder_file_decode(*plugin, decoder, path_fs)) - return true; + bool success = decoder_stream_decode(plugin, decoder, + *input_stream); - dc.Unlock(); - } else if (plugin->stream_decode != nullptr) { - InputStream *input_stream; - bool success; + dc.Unlock(); - input_stream = decoder_input_stream_open(dc, path_fs); - if (input_stream == nullptr) - continue; + input_stream->Close(); + if (success) { dc.Lock(); + return true; + } + } - success = decoder_stream_decode(*plugin, decoder, - *input_stream); + 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; - dc.Unlock(); + DecoderControl &dc = decoder.dc; + dc.Unlock(); - input_stream->Close(); + decoder_load_replay_gain(decoder, path_fs); - if (success) { - dc.Lock(); - return true; - } - } - } + if (decoder_plugins_try([&decoder, path_fs, suffix](const DecoderPlugin &plugin){ + return TryDecoderFile(decoder, path_fs, suffix, + plugin); + })) + return true; dc.Lock(); return false; @@ -336,18 +347,17 @@ decoder_run_file(Decoder &decoder, const char *path_fs) static void decoder_run_song(DecoderControl &dc, - const Song *song, const char *uri) + const DetachedSong &song, const char *uri) { Decoder decoder(dc, dc.start_ms > 0, - song->tag != nullptr && song->IsFile() - ? new Tag(*song->tag) : nullptr); + new Tag(song.GetTag())); int ret; dc.state = DecoderState::START; decoder_command_finished_locked(dc); - ret = song->IsFile() + ret = song.IsFile() ? decoder_run_file(decoder, uri) : decoder_run_stream(decoder, uri); @@ -356,16 +366,21 @@ decoder_run_song(DecoderControl &dc, /* flush the last chunk */ if (decoder.chunk != nullptr) - decoder_flush_chunk(decoder); + decoder.FlushChunk(); dc.Lock(); - if (ret) + 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->uri; + const char *error_uri = song.GetURI(); const std::string allocated = uri_remove_auth(error_uri); if (!allocated.empty()) error_uri = allocated.c_str(); @@ -382,12 +397,12 @@ decoder_run(DecoderControl &dc) { dc.ClearError(); - const Song *song = dc.song; - assert(song != nullptr); + assert(dc.song != nullptr); + const DetachedSong &song = *dc.song; - const std::string uri = song->IsFile() - ? std::string(map_song_fs(*song).c_str()) - : song->GetURI(); + const std::string uri = song.IsFile() + ? map_song_fs(song).c_str() + : song.GetURI(); if (uri.empty()) { dc.state = DecoderState::ERROR; diff --git a/src/DespotifyUtils.cxx b/src/DespotifyUtils.cxx index e91587a7f..8a499d75d 100644 --- a/src/DespotifyUtils.cxx +++ b/src/DespotifyUtils.cxx @@ -19,6 +19,7 @@ #include "DespotifyUtils.hxx" #include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" #include "ConfigGlobal.hxx" #include "ConfigOption.hxx" #include "util/Domain.hxx" @@ -82,33 +83,31 @@ void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, in } } - -Tag * -mpd_despotify_tag_from_track(struct ds_track *track) +Tag +mpd_despotify_tag_from_track(const ds_track &track) { char tracknum[20]; char comment[80]; char date[20]; - Tag *tag = new Tag(); - - if (!track->has_meta_data) - return tag; + if (!track.has_meta_data) + return Tag(); - snprintf(tracknum, sizeof(tracknum), "%d", track->tracknumber); - snprintf(date, sizeof(date), "%d", track->year); + 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->time = track->length / 1000; - - return tag; + 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) diff --git a/src/DespotifyUtils.hxx b/src/DespotifyUtils.hxx index c0d4af47c..c8d90afa4 100644 --- a/src/DespotifyUtils.hxx +++ b/src/DespotifyUtils.hxx @@ -42,10 +42,10 @@ struct despotify_session *mpd_despotify_get_session(void); * * @param track the track to convert * - * @return a pointer to the filled in tags structure + * @return filled in #Tag structure */ -Tag * -mpd_despotify_tag_from_track(struct ds_track *track); +Tag +mpd_despotify_tag_from_track(const ds_track &track); /** * Register a despotify callback. diff --git a/src/DetachedSong.cxx b/src/DetachedSong.cxx new file mode 100644 index 000000000..4b1d51a41 --- /dev/null +++ b/src/DetachedSong.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 "DetachedSong.hxx" +#include "Song.hxx" +#include "util/UriUtil.hxx" +#include "fs/Traits.hxx" + +DetachedSong::DetachedSong(const Song &other) + :uri(other.GetURI().c_str()), + tag(other.tag != nullptr ? *other.tag : Tag()), + mtime(other.mtime), + start_ms(other.start_ms), end_ms(other.end_ms) {} + +bool +DetachedSong::IsRemote() const +{ + return uri_has_scheme(uri.c_str()); +} + +bool +DetachedSong::IsAbsoluteFile() const +{ + return PathTraitsUTF8::IsAbsolute(uri.c_str()); +} + +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..bee6a73a4 --- /dev/null +++ b/src/DetachedSong.hxx @@ -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. + */ + +#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 Song; + +class DetachedSong { + /** + * 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; + + 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; + +public: + explicit DetachedSong(const DetachedSong &other) + :uri(other.uri), + tag(other.tag), + mtime(other.mtime), + start_ms(other.start_ms), end_ms(other.end_ms) {} + + explicit DetachedSong(const Song &other); + + explicit DetachedSong(const char *_uri) + :uri(_uri), + mtime(0), start_ms(0), end_ms(0) {} + + explicit DetachedSong(const std::string &_uri) + :uri(std::move(_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 &&other) + :uri(std::move(other.uri)), + tag(std::move(other.tag)), + mtime(other.mtime), + start_ms(other.start_ms), end_ms(other.end_ms) {} + + gcc_pure + const char *GetURI() const { + return uri.c_str(); + } + + template<typename T> + void SetURI(T &&_uri) { + 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 { + return IsFile() && !IsAbsoluteFile(); + } + + 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; +}; + +#endif diff --git a/src/Directory.cxx b/src/Directory.cxx index b2942588e..28c2c47e1 100644 --- a/src/Directory.cxx +++ b/src/Directory.cxx @@ -25,6 +25,7 @@ #include "SongSort.hxx" #include "Song.hxx" #include "fs/Traits.hxx" +#include "util/Alloc.hxx" #include "util/Error.hxx" extern "C" { @@ -37,35 +38,11 @@ extern "C" { #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) +Directory::Directory(const char *_path_utf8, Directory *_parent) + :parent(_parent), path(_path_utf8) { INIT_LIST_HEAD(&children); INIT_LIST_HEAD(&songs); - - strcpy(path, _path); } Directory::~Directory() @@ -76,27 +53,7 @@ Directory::~Directory() 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); + delete child; } void @@ -106,7 +63,7 @@ Directory::Delete() assert(parent != nullptr); list_del(&siblings); - Free(); + delete this; } const char * @@ -114,7 +71,7 @@ Directory::GetName() const { assert(!IsRoot()); - return PathTraits::GetBaseUTF8(path); + return PathTraitsUTF8::GetBase(path.c_str()); } Directory * @@ -135,7 +92,7 @@ Directory::CreateChild(const char *name_utf8) path_utf8 = allocated; } - Directory *child = NewGeneric(path_utf8, this); + Directory *child = new Directory(path_utf8, this); g_free(allocated); list_add_tail(&child->siblings, &children); @@ -178,7 +135,7 @@ Directory::LookupDirectory(const char *uri) if (isRootDirectory(uri)) return this; - char *duplicated = g_strdup(uri), *name = duplicated; + char *duplicated = xstrdup(uri), *name = duplicated; Directory *d = this; while (1) { @@ -198,7 +155,7 @@ Directory::LookupDirectory(const char *uri) name = slash + 1; } - g_free(duplicated); + free(duplicated); return d; } @@ -248,7 +205,7 @@ Directory::LookupSong(const char *uri) assert(holding_db_lock()); assert(uri != nullptr); - duplicated = g_strdup(uri); + duplicated = xstrdup(uri); base = strrchr(duplicated, '/'); Directory *d = this; @@ -256,7 +213,7 @@ Directory::LookupSong(const char *uri) *base++ = 0; d = d->LookupDirectory(duplicated); if (d == nullptr) { - g_free(duplicated); + free(duplicated); return nullptr; } } else @@ -265,7 +222,7 @@ Directory::LookupSong(const char *uri) Song *song = d->FindSong(base); assert(song == nullptr || song->parent == d); - g_free(duplicated); + free(duplicated); return song; } @@ -276,7 +233,7 @@ directory_cmp(gcc_unused void *priv, { const Directory *a = (const Directory *)_a; const Directory *b = (const Directory *)_b; - return g_utf8_collate(a->path, b->path); + return g_utf8_collate(a->path.c_str(), b->path.c_str()); } void diff --git a/src/Directory.hxx b/src/Directory.hxx index 380a6b790..031cca877 100644 --- a/src/Directory.hxx +++ b/src/Directory.hxx @@ -26,6 +26,8 @@ #include "DatabaseVisitor.hxx" #include "PlaylistVector.hxx" +#include <string> + #include <sys/types.h> #define DEVICE_INARCHIVE (dev_t)(-1) @@ -82,42 +84,22 @@ struct Directory { 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); + std::string path; public: - /** - * Default constructor, needed for #detached_root. - */ - Directory(); + Directory(const char *_path_utf8, Directory *_parent); ~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); + return new Directory("", 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. * @@ -178,7 +160,7 @@ public: gcc_pure const char *GetPath() const { - return path; + return path.c_str(); } /** diff --git a/src/DirectorySave.cxx b/src/DirectorySave.cxx index fa330d126..f8ee5b0bd 100644 --- a/src/DirectorySave.cxx +++ b/src/DirectorySave.cxx @@ -22,16 +22,15 @@ #include "Directory.hxx" #include "Song.hxx" #include "SongSave.hxx" +#include "DetachedSong.hxx" #include "PlaylistDatabase.hxx" -#include "TextFile.hxx" +#include "fs/TextFile.hxx" +#include "util/StringUtil.hxx" #include "util/NumberParser.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" -#include <glib.h> - -#include <assert.h> -#include <string.h> +#include <stddef.h> #define DIRECTORY_DIR "directory: " #define DIRECTORY_MTIME "mtime: " @@ -91,7 +90,7 @@ directory_load_subdir(TextFile &file, Directory &parent, const char *name, return nullptr; } - if (g_str_has_prefix(line, DIRECTORY_MTIME)) { + if (StringStartsWith(line, DIRECTORY_MTIME)) { directory->mtime = ParseUint64(line + sizeof(DIRECTORY_MTIME) - 1); @@ -103,7 +102,7 @@ directory_load_subdir(TextFile &file, Directory &parent, const char *name, } } - if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) { + if (!StringStartsWith(line, DIRECTORY_BEGIN)) { error.Format(directory_domain, "Malformed line: %s", line); directory->Delete(); return nullptr; @@ -124,17 +123,16 @@ 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)) { + !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 (g_str_has_prefix(line, SONG_BEGIN)) { + } else if (StringStartsWith(line, SONG_BEGIN)) { const char *name = line + sizeof(SONG_BEGIN) - 1; - Song *song; if (directory.FindSong(name) != nullptr) { error.Format(directory_domain, @@ -142,24 +140,18 @@ directory_load(TextFile &file, Directory &directory, Error &error) return false; } - song = song_load(file, &directory, name, error); + DetachedSong *song = song_load(file, 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); - + 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)) { - g_free(name); + name, error)) return false; - } - - g_free(name); } else { error.Format(directory_domain, "Malformed line: %s", line); diff --git a/src/EncoderAPI.hxx b/src/EncoderAPI.hxx index b3397f25c..3ae1879cb 100644 --- a/src/EncoderAPI.hxx +++ b/src/EncoderAPI.hxx @@ -25,9 +25,13 @@ #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 "ConfigData.hxx" +// IWYU pragma: end_exports + #endif diff --git a/src/EncoderList.cxx b/src/EncoderList.cxx index 7760a9582..22b83ffa1 100644 --- a/src/EncoderList.cxx +++ b/src/EncoderList.cxx @@ -25,6 +25,7 @@ #include "encoder/VorbisEncoderPlugin.hxx" #include "encoder/OpusEncoderPlugin.hxx" #include "encoder/FlacEncoderPlugin.hxx" +#include "encoder/ShineEncoderPlugin.hxx" #include "encoder/LameEncoderPlugin.hxx" #include "encoder/TwolameEncoderPlugin.hxx" @@ -50,6 +51,9 @@ const EncoderPlugin *const encoder_plugins[] = { #ifdef ENABLE_FLAC_ENCODER &flac_encoder_plugin, #endif +#ifdef ENABLE_SHINE_ENCODER + &shine_encoder_plugin, +#endif nullptr }; diff --git a/src/Expat.cxx b/src/Expat.cxx new file mode 100644 index 000000000..5cee45912 --- /dev/null +++ b/src/Expat.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 "Expat.hxx" +#include "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/Expat.hxx b/src/Expat.hxx new file mode 100644 index 000000000..d57a85533 --- /dev/null +++ b/src/Expat.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/FilterConfig.cxx b/src/FilterConfig.cxx index cfac1c756..423b84086 100644 --- a/src/FilterConfig.cxx +++ b/src/FilterConfig.cxx @@ -21,8 +21,6 @@ #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" diff --git a/src/FilterPlugin.cxx b/src/FilterPlugin.cxx index 608542f92..d5e3b664a 100644 --- a/src/FilterPlugin.cxx +++ b/src/FilterPlugin.cxx @@ -19,7 +19,6 @@ #include "config.h" #include "FilterPlugin.hxx" -#include "FilterInternal.hxx" #include "FilterRegistry.hxx" #include "ConfigData.hxx" #include "ConfigError.hxx" diff --git a/src/FilterRegistry.cxx b/src/FilterRegistry.cxx index b3b08505e..427f4e875 100644 --- a/src/FilterRegistry.cxx +++ b/src/FilterRegistry.cxx @@ -21,7 +21,6 @@ #include "FilterRegistry.hxx" #include "FilterPlugin.hxx" -#include <stddef.h> #include <string.h> const struct filter_plugin *const filter_plugins[] = { diff --git a/src/GlobalEvents.cxx b/src/GlobalEvents.cxx index 86bfb3e2a..b7ba85286 100644 --- a/src/GlobalEvents.cxx +++ b/src/GlobalEvents.cxx @@ -21,7 +21,6 @@ #include "GlobalEvents.hxx" #include "util/Manual.hxx" #include "event/DeferredMonitor.hxx" -#include "Compiler.h" #include <atomic> diff --git a/src/IcyMetaDataParser.cxx b/src/IcyMetaDataParser.cxx index 8861efb2e..02c0fdc1e 100644 --- a/src/IcyMetaDataParser.cxx +++ b/src/IcyMetaDataParser.cxx @@ -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 *item) +icy_parse_tag_item(TagBuilder &tag, const char *item) { gchar **p = g_strsplit(item, "=", 0); @@ -99,15 +100,16 @@ icy_parse_tag_item(Tag &tag, const char *item) static Tag * icy_parse_tag(const char *p) { - Tag *tag = new Tag(); + TagBuilder tag; + gchar **items = g_strsplit(p, ";", 0); for (unsigned i = 0; items[i] != nullptr; ++i) - icy_parse_tag_item(*tag, items[i]); + icy_parse_tag_item(tag, items[i]); g_strfreev(items); - return tag; + return tag.CommitNew(); } size_t @@ -136,7 +138,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); @@ -161,7 +163,7 @@ IcyMetaDataParser::Meta(const void *data, size_t length) delete tag; tag = icy_parse_tag(meta_data); - g_free(meta_data); + delete[] meta_data; /* change back to normal data mode */ diff --git a/src/IcyMetaDataServer.cxx b/src/IcyMetaDataServer.cxx index f5e981c2f..eebe8d0d4 100644 --- a/src/IcyMetaDataServer.cxx +++ b/src/IcyMetaDataServer.cxx @@ -25,7 +25,6 @@ #include <glib.h> -#include <assert.h> #include <string.h> char* diff --git a/src/InotifyQueue.cxx b/src/InotifyQueue.cxx index c24816241..7a652ef01 100644 --- a/src/InotifyQueue.cxx +++ b/src/InotifyQueue.cxx @@ -21,7 +21,6 @@ #include "InotifyQueue.hxx" #include "InotifyDomain.hxx" #include "UpdateGlue.hxx" -#include "event/Loop.hxx" #include "Log.hxx" #include <string.h> diff --git a/src/InotifySource.hxx b/src/InotifySource.hxx index f6ddea966..e2ce9301e 100644 --- a/src/InotifySource.hxx +++ b/src/InotifySource.hxx @@ -22,7 +22,6 @@ #include "event/SocketMonitor.hxx" #include "util/FifoBuffer.hxx" -#include "Compiler.h" class Error; @@ -39,6 +38,10 @@ class InotifySource final : private SocketMonitor { 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. diff --git a/src/InputRegistry.cxx b/src/InputRegistry.cxx index aa6c06ed1..6e46d3cd7 100644 --- a/src/InputRegistry.cxx +++ b/src/InputRegistry.cxx @@ -22,6 +22,10 @@ #include "util/Macros.hxx" #include "input/FileInputPlugin.hxx" +#ifdef HAVE_ALSA +#include "input/AlsaInputPlugin.hxx" +#endif + #ifdef ENABLE_ARCHIVE #include "input/ArchiveInputPlugin.hxx" #endif @@ -34,6 +38,10 @@ #include "input/FfmpegInputPlugin.hxx" #endif +#ifdef ENABLE_SMBCLIENT +#include "input/SmbclientInputPlugin.hxx" +#endif + #ifdef ENABLE_MMS #include "input/MmsInputPlugin.hxx" #endif @@ -48,6 +56,9 @@ const InputPlugin *const input_plugins[] = { &input_plugin_file, +#ifdef HAVE_ALSA + &input_plugin_alsa, +#endif #ifdef ENABLE_ARCHIVE &input_plugin_archive, #endif @@ -57,6 +68,9 @@ const InputPlugin *const input_plugins[] = { #ifdef HAVE_FFMPEG &input_plugin_ffmpeg, #endif +#ifdef ENABLE_SMBCLIENT + &input_plugin_smbclient, +#endif #ifdef ENABLE_MMS &input_plugin_mms, #endif diff --git a/src/InputStream.cxx b/src/InputStream.cxx index 28a0aad1a..7d3ebe615 100644 --- a/src/InputStream.cxx +++ b/src/InputStream.cxx @@ -57,6 +57,28 @@ InputStream::Open(const char *url, 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) { diff --git a/src/InputStream.hxx b/src/InputStream.hxx index b1bc9c4ab..5ac3bdcae 100644 --- a/src/InputStream.hxx +++ b/src/InputStream.hxx @@ -119,6 +119,15 @@ struct InputStream { 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. diff --git a/src/Instance.cxx b/src/Instance.cxx index daad94212..e9a308848 100644 --- a/src/Instance.cxx +++ b/src/Instance.cxx @@ -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,9 @@ Instance::SyncWithPlayer() { partition->SyncWithPlayer(); } + +void +Instance::OnDatabaseModified() +{ + DatabaseModified(); +} diff --git a/src/Instance.hxx b/src/Instance.hxx index a0dfd1b94..160e713b0 100644 --- a/src/Instance.hxx +++ b/src/Instance.hxx @@ -21,17 +21,18 @@ #define MPD_INSTANCE_HXX #include "check.h" +#include "DatabaseListener.hxx" +#include "Compiler.h" class ClientList; struct Partition; -struct Song; -struct Instance { +struct Instance final : public DatabaseListener { 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 +50,9 @@ struct Instance { * Synchronize the player with the play queue. */ void SyncWithPlayer(); + +private: + virtual void OnDatabaseModified(); }; #endif diff --git a/src/Log.cxx b/src/Log.cxx index a46c0ced8..c58790841 100644 --- a/src/Log.cxx +++ b/src/Log.cxx @@ -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..c8e166056 100644 --- a/src/Log.hxx +++ b/src/Log.hxx @@ -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..bddf8db16 --- /dev/null +++ b/src/LogBackend.cxx @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "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..0d4808e48 --- /dev/null +++ b/src/LogBackend.hxx @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_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..bd858a32c 100644 --- a/src/LogInit.cxx +++ b/src/LogInit.cxx @@ -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 "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/LogLevel.hxx b/src/LogLevel.hxx new file mode 100644 index 000000000..dd24cc384 --- /dev/null +++ b/src/LogLevel.hxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_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..9f3687ba0 100644 --- a/src/LogV.hxx +++ b/src/LogV.hxx @@ -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..624c6216d 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -50,11 +50,12 @@ #include "IOThread.hxx" #include "fs/AllocatedPath.hxx" #include "fs/Config.hxx" +#include "fs/StandardDirectory.hxx" #include "PlaylistRegistry.hxx" #include "ZeroconfGlue.hxx" #include "DecoderList.hxx" #include "AudioConfig.hxx" -#include "pcm/PcmResample.hxx" +#include "pcm/PcmConvert.hxx" #include "Daemon.hxx" #include "system/FatalError.hxx" #include "util/Error.hxx" @@ -78,12 +79,11 @@ #include "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 +94,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 +137,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 false; } mapper_init(std::move(music_dir), std::move(playlist_dir)); @@ -188,7 +186,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; @@ -365,15 +363,17 @@ 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); @@ -431,7 +431,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; } @@ -479,7 +479,7 @@ int mpd_main(int argc, char *argv[]) } if (!glue_state_file_init(error)) { - g_printerr("%s\n", error.GetMessage()); + LogError(error); return EXIT_FAILURE; } @@ -544,7 +544,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 +553,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/Mapper.cxx b/src/Mapper.cxx index cbe45daa0..6910b4983 100644 --- a/src/Mapper.cxx +++ b/src/Mapper.cxx @@ -25,6 +25,7 @@ #include "Mapper.hxx" #include "Directory.hxx" #include "Song.hxx" +#include "DetachedSong.hxx" #include "fs/AllocatedPath.hxx" #include "fs/Traits.hxx" #include "fs/Charset.hxx" @@ -36,9 +37,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 +149,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; } @@ -221,20 +220,24 @@ map_detached_song_fs(const char *uri_utf8) 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.GetURI()); 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..be61ab43a 100644 --- a/src/Mapper.hxx +++ b/src/Mapper.hxx @@ -30,10 +30,10 @@ #define PLAYLIST_FILE_SUFFIX ".m3u" -class Path; class AllocatedPath; struct Directory; struct Song; +class DetachedSong; void mapper_init(AllocatedPath &&music_dir, AllocatedPath &&playlist_dir); @@ -117,6 +117,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 +142,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 index 7c9d05daa..3bb17083c 100644 --- a/src/MemorySongEnumerator.cxx +++ b/src/MemorySongEnumerator.cxx @@ -20,13 +20,13 @@ #include "config.h" #include "MemorySongEnumerator.hxx" -Song * +DetachedSong * MemorySongEnumerator::NextSong() { if (songs.empty()) return nullptr; - auto result = songs.front().Steal(); + auto result = new DetachedSong(std::move(songs.front())); songs.pop_front(); return result; } diff --git a/src/MemorySongEnumerator.hxx b/src/MemorySongEnumerator.hxx index 46086a064..085e16bc6 100644 --- a/src/MemorySongEnumerator.hxx +++ b/src/MemorySongEnumerator.hxx @@ -21,18 +21,18 @@ #define MPD_MEMORY_PLAYLIST_PROVIDER_HXX #include "SongEnumerator.hxx" -#include "SongPointer.hxx" +#include "DetachedSong.hxx" #include <forward_list> class MemorySongEnumerator final : public SongEnumerator { - std::forward_list<SongPointer> songs; + std::forward_list<DetachedSong> songs; public: - MemorySongEnumerator(std::forward_list<SongPointer> &&_songs) + MemorySongEnumerator(std::forward_list<DetachedSong> &&_songs) :songs(std::move(_songs)) {} - virtual Song *NextSong() override; + virtual DetachedSong *NextSong() override; }; #endif diff --git a/src/MixerAll.cxx b/src/MixerAll.cxx index 37225fd25..2eb54c232 100644 --- a/src/MixerAll.cxx +++ b/src/MixerAll.cxx @@ -23,7 +23,7 @@ #include "MixerInternal.hxx" #include "MixerList.hxx" #include "OutputAll.hxx" -#include "pcm/PcmVolume.hxx" +#include "pcm/Volume.hxx" #include "OutputInternal.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" diff --git a/src/MixerControl.cxx b/src/MixerControl.cxx index dd4f03543..08973a698 100644 --- a/src/MixerControl.cxx +++ b/src/MixerControl.cxx @@ -23,7 +23,6 @@ #include "util/Error.hxx" #include <assert.h> -#include <stddef.h> Mixer * mixer_new(const struct mixer_plugin *plugin, void *ao, diff --git a/src/MixerControl.hxx b/src/MixerControl.hxx index c1ee01eec..e7e825e7c 100644 --- a/src/MixerControl.hxx +++ b/src/MixerControl.hxx @@ -31,7 +31,7 @@ struct mixer_plugin; struct config_param; Mixer * -mixer_new(const struct mixer_plugin *plugin, void *ao, +mixer_new(const mixer_plugin *plugin, void *ao, const config_param ¶m, Error &error); diff --git a/src/OutputAPI.hxx b/src/OutputAPI.hxx index 73cbaf183..437141c5c 100644 --- a/src/OutputAPI.hxx +++ b/src/OutputAPI.hxx @@ -20,10 +20,14 @@ #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 "ConfigData.hxx" +// IWYU pragma: end_exports + #endif diff --git a/src/OutputAll.cxx b/src/OutputAll.cxx index 36d41184a..8cbfb6e23 100644 --- a/src/OutputAll.cxx +++ b/src/OutputAll.cxx @@ -33,8 +33,6 @@ #include "ConfigOption.hxx" #include "notify.hxx" -#include <glib.h> - #include <assert.h> #include <string.h> @@ -110,7 +108,7 @@ audio_output_all_init(PlayerControl &pc) Error error; num_audio_outputs = audio_output_config_count(); - audio_outputs = g_new(struct audio_output *, num_audio_outputs); + audio_outputs = new audio_output *[num_audio_outputs]; const config_param empty; @@ -160,7 +158,7 @@ audio_output_all_finish(void) audio_output_finish(audio_outputs[i]); } - g_free(audio_outputs); + delete[] audio_outputs; audio_outputs = nullptr; num_audio_outputs = 0; } @@ -225,10 +223,7 @@ 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; - } + ao->fail_timer.Reset(); } /** diff --git a/src/OutputAll.hxx b/src/OutputAll.hxx index 98061c345..2d9b1477f 100644 --- a/src/OutputAll.hxx +++ b/src/OutputAll.hxx @@ -115,7 +115,7 @@ audio_output_all_set_replay_gain_mode(ReplayGainMode mode); * (all closed then) */ bool -audio_output_all_play(struct music_chunk *chunk, Error &error); +audio_output_all_play(music_chunk *chunk, Error &error); /** * Checks if the output devices have drained their music pipe, and diff --git a/src/OutputCommand.cxx b/src/OutputCommand.cxx index 10b5bb322..efc8081e0 100644 --- a/src/OutputCommand.cxx +++ b/src/OutputCommand.cxx @@ -28,7 +28,6 @@ #include "OutputCommand.hxx" #include "OutputAll.hxx" #include "OutputInternal.hxx" -#include "OutputPlugin.hxx" #include "PlayerControl.hxx" #include "MixerControl.hxx" #include "Idle.hxx" diff --git a/src/OutputControl.cxx b/src/OutputControl.cxx index 27f280231..bcfd6a8c1 100644 --- a/src/OutputControl.cxx +++ b/src/OutputControl.cxx @@ -24,18 +24,13 @@ #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 */ @@ -154,10 +149,7 @@ audio_output_open(struct audio_output *ao, assert(ao->allow_play); assert(audio_format.IsValid()); - if (ao->fail_timer != nullptr) { - g_timer_destroy(ao->fail_timer); - ao->fail_timer = nullptr; - } + ao->fail_timer.Reset(); if (ao->open && audio_format == ao->in_audio_format) { assert(ao->pipe == &mp || @@ -215,14 +207,12 @@ audio_output_close_locked(struct audio_output *ao) if (ao->mixer != nullptr) mixer_auto_close(ao->mixer); - assert(!ao->open || ao->fail_timer == nullptr); + assert(!ao->open || !ao->fail_timer.IsDefined()); 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; - } + else + ao->fail_timer.Reset(); } bool @@ -233,8 +223,7 @@ audio_output_update(struct audio_output *ao, 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) { + if (ao->fail_timer.Check(REOPEN_AFTER * 1000)) { return audio_output_open(ao, audio_format, mp); } } else if (audio_output_is_open(ao)) @@ -314,7 +303,7 @@ audio_output_release(struct audio_output *ao) void audio_output_close(struct audio_output *ao) { assert(ao != nullptr); - assert(!ao->open || ao->fail_timer == nullptr); + assert(!ao->open || !ao->fail_timer.IsDefined()); const ScopeLock protect(ao->mutex); audio_output_close_locked(ao); @@ -324,7 +313,7 @@ void audio_output_finish(struct audio_output *ao) { audio_output_close(ao); - assert(ao->fail_timer == nullptr); + assert(!ao->fail_timer.IsDefined()); if (ao->thread.IsDefined()) { assert(ao->allow_play); diff --git a/src/OutputControl.hxx b/src/OutputControl.hxx index d8f0b432d..c63383293 100644 --- a/src/OutputControl.hxx +++ b/src/OutputControl.hxx @@ -30,20 +30,20 @@ struct config_param; class MusicPipe; void -audio_output_set_replay_gain_mode(struct audio_output *ao, +audio_output_set_replay_gain_mode(audio_output *ao, ReplayGainMode mode); /** * Enables the device. */ void -audio_output_enable(struct audio_output *ao); +audio_output_enable(audio_output *ao); /** * Disables the device. */ void -audio_output_disable(struct audio_output *ao); +audio_output_disable(audio_output *ao); /** * Opens or closes the device, depending on the "enabled" flag. @@ -51,40 +51,44 @@ audio_output_disable(struct audio_output *ao); * @return true if the device is open */ bool -audio_output_update(struct audio_output *ao, +audio_output_update(audio_output *ao, AudioFormat audio_format, const MusicPipe &mp); void -audio_output_play(struct audio_output *ao); +audio_output_play(audio_output *ao); -void audio_output_pause(struct audio_output *ao); +void +audio_output_pause(audio_output *ao); void -audio_output_drain_async(struct audio_output *ao); +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(struct audio_output *ao); +void +audio_output_cancel(audio_output *ao); /** * Set the "allow_play" and signal the thread. */ void -audio_output_allow_play(struct audio_output *ao); +audio_output_allow_play(audio_output *ao); -void audio_output_close(struct 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(struct audio_output *ao); +audio_output_release(audio_output *ao); -void audio_output_finish(struct audio_output *ao); +void +audio_output_finish(audio_output *ao); #endif diff --git a/src/OutputFinish.cxx b/src/OutputFinish.cxx index db6599b53..b19f4c402 100644 --- a/src/OutputFinish.cxx +++ b/src/OutputFinish.cxx @@ -29,7 +29,7 @@ void ao_base_finish(struct audio_output *ao) { assert(!ao->open); - assert(ao->fail_timer == nullptr); + assert(!ao->fail_timer.IsDefined()); assert(!ao->thread.IsDefined()); if (ao->mixer != nullptr) @@ -44,7 +44,7 @@ void audio_output_free(struct audio_output *ao) { assert(!ao->open); - assert(ao->fail_timer == nullptr); + assert(!ao->fail_timer.IsDefined()); assert(!ao->thread.IsDefined()); ao_plugin_finish(ao); diff --git a/src/OutputInit.cxx b/src/OutputInit.cxx index 28eba1ab2..c9e03e0f2 100644 --- a/src/OutputInit.cxx +++ b/src/OutputInit.cxx @@ -19,7 +19,6 @@ #include "config.h" #include "OutputInternal.hxx" -#include "OutputControl.hxx" #include "OutputList.hxx" #include "OutputError.hxx" #include "OutputAPI.hxx" @@ -173,7 +172,6 @@ ao_base_init(struct audio_output *ao, ao->allow_play = true; ao->in_playback_loop = false; ao->woken_for_play = false; - ao->fail_timer = nullptr; /* set up the filter chain */ diff --git a/src/OutputInternal.hxx b/src/OutputInternal.hxx index c07cdf856..ab1c1242e 100644 --- a/src/OutputInternal.hxx +++ b/src/OutputInternal.hxx @@ -22,18 +22,17 @@ #include "AudioFormat.hxx" #include "pcm/PcmBuffer.hxx" +#include "pcm/PcmDither.hxx" #include "thread/Mutex.hxx" #include "thread/Cond.hxx" #include "thread/Thread.hxx" - -#include <time.h> +#include "system/PeriodClock.hxx" class Error; class Filter; class MusicPipe; struct config_param; struct PlayerControl; -typedef struct _GTimer GTimer; enum audio_output_command { AO_COMMAND_NONE = 0, @@ -147,7 +146,7 @@ struct audio_output { * to estimate how long it should stay disabled (unless * explicitly reopened with "play"). */ - GTimer *fail_timer; + PeriodClock fail_timer; /** * The configured audio format. @@ -174,6 +173,11 @@ struct audio_output { 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. */ diff --git a/src/OutputState.cxx b/src/OutputState.cxx index a3650413c..0124e1e33 100644 --- a/src/OutputState.cxx +++ b/src/OutputState.cxx @@ -28,12 +28,10 @@ #include "OutputInternal.hxx" #include "OutputError.hxx" #include "Log.hxx" - -#include <glib.h> +#include "util/StringUtil.hxx" #include <assert.h> #include <stdlib.h> -#include <string.h> #define AUDIO_DEVICE_STATE "audio_device_state:" @@ -62,7 +60,7 @@ audio_output_state_read(const char *line) const char *name; struct audio_output *ao; - if (!g_str_has_prefix(line, AUDIO_DEVICE_STATE)) + if (!StringStartsWith(line, AUDIO_DEVICE_STATE)) return false; line += sizeof(AUDIO_DEVICE_STATE) - 1; diff --git a/src/OutputThread.cxx b/src/OutputThread.cxx index 30d3ba30f..62833e360 100644 --- a/src/OutputThread.cxx +++ b/src/OutputThread.cxx @@ -35,8 +35,6 @@ #include "Log.hxx" #include "Compiler.h" -#include <glib.h> - #include <assert.h> #include <string.h> @@ -98,10 +96,16 @@ ao_filter_open(struct audio_output *ao, AudioFormat &format, 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); + 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()) { @@ -137,14 +141,7 @@ ao_open(struct audio_output *ao) 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; - } + ao->fail_timer.Reset(); /* enable the device (just in case the last enable has failed) */ @@ -160,7 +157,7 @@ ao_open(struct audio_output *ao) FormatError(error, "Failed to open filter for \"%s\" [%s]", ao->name, ao->plugin->name); - ao->fail_timer = g_timer_new(); + ao->fail_timer.Update(); return; } @@ -180,11 +177,19 @@ ao_open(struct audio_output *ao) ao->name, ao->plugin->name); ao_filter_close(ao); - ao->fail_timer = g_timer_new(); + ao->fail_timer.Update(); return; } - convert_filter_set(ao->convert_filter, ao->out_audio_format); + 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; @@ -233,7 +238,9 @@ ao_reopen_filter(struct audio_output *ao) ao_filter_close(ao); const AudioFormat filter_audio_format = ao_filter_open(ao, ao->in_audio_format, error); - if (!filter_audio_format.IsDefined()) { + 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); @@ -246,7 +253,7 @@ ao_reopen_filter(struct audio_output *ao) ao->chunk = nullptr; ao->open = false; - ao->fail_timer = g_timer_new(); + ao->fail_timer.Update(); ao->mutex.unlock(); ao_plugin_close(ao); @@ -254,8 +261,6 @@ ao_reopen_filter(struct audio_output *ao) return; } - - convert_filter_set(ao->convert_filter, ao->out_audio_format); } static void @@ -387,7 +392,7 @@ ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, void *dest = ao->cross_fade_buffer.Get(other_length); memcpy(dest, other_data, other_length); - if (!pcm_mix(dest, data, length, + if (!pcm_mix(ao->cross_fade_dither, dest, data, length, ao->in_audio_format.format, 1.0 - chunk->mix_ratio)) { FormatError(output_domain, @@ -437,7 +442,7 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) /* don't automatically reopen this device for 10 seconds */ - ao->fail_timer = g_timer_new(); + ao->fail_timer.Update(); return false; } @@ -461,8 +466,8 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) /* don't automatically reopen this device for 10 seconds */ - assert(ao->fail_timer == nullptr); - ao->fail_timer = g_timer_new(); + assert(!ao->fail_timer.IsDefined()); + ao->fail_timer.Update(); return false; } diff --git a/src/OutputThread.hxx b/src/OutputThread.hxx index 1a7932162..980d203fe 100644 --- a/src/OutputThread.hxx +++ b/src/OutputThread.hxx @@ -22,6 +22,7 @@ struct audio_output; -void audio_output_thread_start(struct audio_output *ao); +void +audio_output_thread_start(audio_output *ao); #endif diff --git a/src/Page.cxx b/src/Page.cxx index 91033a1ec..c46d743ca 100644 --- a/src/Page.cxx +++ b/src/Page.cxx @@ -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..4e1302ba5 100644 --- a/src/Page.hxx +++ b/src/Page.hxx @@ -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..9c30ce0a7 100644 --- a/src/Partition.cxx +++ b/src/Partition.cxx @@ -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..69c6f0175 100644 --- a/src/Partition.hxx +++ b/src/Partition.hxx @@ -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/PlayerControl.cxx b/src/PlayerControl.cxx index f180874b0..baaf7bc3b 100644 --- a/src/PlayerControl.cxx +++ b/src/PlayerControl.cxx @@ -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..6919d2cb1 100644 --- a/src/PlayerControl.hxx +++ b/src/PlayerControl.hxx @@ -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..814269b50 100644 --- a/src/PlayerThread.cxx +++ b/src/PlayerThread.cxx @@ -24,8 +24,7 @@ #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" @@ -93,7 +92,7 @@ class Player { /** * the song currently being played */ - Song *song; + DetachedSong *song; /** * is cross fading enabled? @@ -291,12 +290,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 +329,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 +339,7 @@ Player::WaitForDecoder() pc.ClearTaggedSong(); - if (song != nullptr) - song->Free(); - + delete song; song = pc.next_song; elapsed_time = 0.0; @@ -371,19 +368,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 +449,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 +459,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 +517,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 +543,7 @@ Player::SeekDecoder() ClearAndReplacePipe(dc.pipe); } - pc.next_song->Free(); + delete pc.next_song; pc.next_song = nullptr; queued = false; } @@ -661,7 +659,7 @@ Player::ProcessCommand() pc.Lock(); } - pc.next_song->Free(); + delete pc.next_song; pc.next_song = nullptr; queued = false; pc.CommandFinished(); @@ -684,19 +682,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 +711,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 +834,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 +878,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 +1074,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 +1084,7 @@ Player::Run() if (queued) { assert(pc.next_song != nullptr); - pc.next_song->Free(); + delete pc.next_song; pc.next_song = nullptr; } @@ -1142,10 +1133,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 +1169,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/Playlist.cxx b/src/Playlist.cxx index 8d9ab24a3..0720e490d 100644 --- a/src/Playlist.cxx +++ b/src/Playlist.cxx @@ -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 5875ff4d8..e578bfcee 100644 --- a/src/Playlist.hxx +++ b/src/Playlist.hxx @@ -23,8 +23,10 @@ #include "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 index 52304700f..083b9a230 100644 --- a/src/PlaylistAny.cxx +++ b/src/PlaylistAny.cxx @@ -21,7 +21,6 @@ #include "PlaylistAny.hxx" #include "PlaylistMapper.hxx" #include "PlaylistRegistry.hxx" -#include "PlaylistError.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" #include "InputStream.hxx" @@ -42,7 +41,7 @@ playlist_open_remote(const char *uri, Mutex &mutex, Cond &cond, } Error error; - InputStream *is = InputStream::Open(uri, mutex, cond, error); + InputStream *is = InputStream::OpenReady(uri, mutex, cond, error); if (is == nullptr) { if (error.IsDefined()) FormatError(error, "Failed to open %s", uri); diff --git a/src/PlaylistControl.cxx b/src/PlaylistControl.cxx index 2dbd75d6e..9ba7f7485 100644 --- a/src/PlaylistControl.cxx +++ b/src/PlaylistControl.cxx @@ -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..e6cdb922b 100644 --- a/src/PlaylistDatabase.cxx +++ b/src/PlaylistDatabase.cxx @@ -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/PlaylistEdit.cxx b/src/PlaylistEdit.cxx index 668612a1a..a55f9b151 100644 --- a/src/PlaylistEdit.cxx +++ b/src/PlaylistEdit.cxx @@ -30,6 +30,7 @@ #include "util/UriUtil.hxx" #include "util/Error.hxx" #include "Song.hxx" +#include "DetachedSong.hxx" #include "Idle.hxx" #include "DatabaseGlue.hxx" #include "DatabasePlugin.hxx" @@ -64,23 +65,23 @@ playlist::AppendFile(PlayerControl &pc, if (song == nullptr) return PlaylistResult::NO_SUCH_SONG; - const auto result = AppendSong(pc, song, added_id); + const auto result = AppendSong(pc, DetachedSong(*song), added_id); song->Free(); return result; } 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 +111,24 @@ 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(); + const Database *db = GetDatabase(); if (db == nullptr) return PlaylistResult::NO_SUCH_SONG; - song = db->GetSong(uri, IgnoreError()); - if (song == nullptr) + Song *tmp = db->GetSong(uri, IgnoreError()); + if (tmp == nullptr) return PlaylistResult::NO_SUCH_SONG; + + song = new DetachedSong(*tmp); + db->ReturnSong(tmp); } - 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 +139,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 +193,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 +225,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 +275,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 +297,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 +320,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 +341,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 +401,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/PlaylistFile.cxx b/src/PlaylistFile.cxx index e7dae6258..0f84cc4eb 100644 --- a/src/PlaylistFile.cxx +++ b/src/PlaylistFile.cxx @@ -24,9 +24,9 @@ #include "PlaylistVector.hxx" #include "DatabasePlugin.hxx" #include "DatabaseGlue.hxx" -#include "Song.hxx" +#include "DetachedSong.hxx" #include "Mapper.hxx" -#include "TextFile.hxx" +#include "fs/TextFile.hxx" #include "ConfigGlobal.hxx" #include "ConfigOption.hxx" #include "ConfigDefaults.hxx" @@ -43,10 +43,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 +156,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 +246,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 +361,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,22 +402,21 @@ 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); - if (song == nullptr) + Song *tmp = db->GetSong(url, error); + if (tmp == nullptr) return false; - bool success = spl_append_song(utf8file, *song, error); - db->ReturnSong(song); - return success; + const DetachedSong song(*tmp); + db->ReturnSong(tmp); + + return spl_append_song(utf8file, song, error); } } diff --git a/src/PlaylistFile.hxx b/src/PlaylistFile.hxx index f04530bcc..33fe00eb7 100644 --- a/src/PlaylistFile.hxx +++ b/src/PlaylistFile.hxx @@ -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..0e8493687 100644 --- a/src/PlaylistGlobal.cxx +++ b/src/PlaylistGlobal.cxx @@ -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/PlaylistPrint.cxx b/src/PlaylistPrint.cxx index 8e3beaa47..cee64f859 100644 --- a/src/PlaylistPrint.cxx +++ b/src/PlaylistPrint.cxx @@ -23,8 +23,6 @@ #include "PlaylistAny.hxx" #include "PlaylistSong.hxx" #include "Playlist.hxx" -#include "PlaylistRegistry.hxx" -#include "PlaylistPlugin.hxx" #include "QueuePrint.hxx" #include "SongEnumerator.hxx" #include "SongPrint.hxx" @@ -33,6 +31,7 @@ #include "Client.hxx" #include "InputStream.hxx" #include "Song.hxx" +#include "DetachedSong.hxx" #include "fs/Traits.hxx" #include "util/Error.hxx" #include "thread/Cond.hxx" @@ -150,10 +149,10 @@ playlist_provider_print(Client &client, const char *uri, SongEnumerator &e, bool detail) { const std::string base_uri = uri != nullptr - ? PathTraits::GetParentUTF8(uri) + ? PathTraitsUTF8::GetParent(uri) : std::string("."); - Song *song; + DetachedSong *song; while ((song = e.NextSong()) != nullptr) { song = playlist_check_translate_song(song, base_uri.c_str(), false); @@ -165,7 +164,7 @@ playlist_provider_print(Client &client, const char *uri, else song_print_uri(client, *song); - song->Free(); + delete song; } } diff --git a/src/PlaylistQueue.cxx b/src/PlaylistQueue.cxx index a8359d427..d2dce977a 100644 --- a/src/PlaylistQueue.cxx +++ b/src/PlaylistQueue.cxx @@ -19,13 +19,12 @@ #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 "DetachedSong.hxx" #include "thread/Cond.hxx" #include "fs/Traits.hxx" @@ -36,16 +35,16 @@ playlist_load_into_queue(const char *uri, SongEnumerator &e, bool secure) { const std::string base_uri = uri != nullptr - ? PathTraits::GetParentUTF8(uri) + ? PathTraitsUTF8::GetParent(uri) : std::string("."); - Song *song; + DetachedSong *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(); + delete song; continue; } @@ -54,8 +53,8 @@ playlist_load_into_queue(const char *uri, SongEnumerator &e, if (song == nullptr) continue; - PlaylistResult result = dest.AppendSong(pc, song); - song->Free(); + PlaylistResult result = dest.AppendSong(pc, std::move(*song)); + delete song; if (result != PlaylistResult::SUCCESS) return result; } diff --git a/src/PlaylistRegistry.cxx b/src/PlaylistRegistry.cxx index 9afbe349d..ddfd39613 100644 --- a/src/PlaylistRegistry.cxx +++ b/src/PlaylistRegistry.cxx @@ -40,18 +40,18 @@ #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, +#ifdef HAVE_EXPAT + &xspf_playlist_plugin, &asx_playlist_plugin, &rss_playlist_plugin, +#endif #ifdef ENABLE_DESPOTIFY &despotify_playlist_plugin, #endif @@ -130,13 +130,12 @@ 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) + const auto scheme = uri_get_scheme(uri); + if (scheme.empty()) return nullptr; for (unsigned i = 0; playlist_plugins[i] != nullptr; ++i) { @@ -146,7 +145,7 @@ playlist_list_open_uri_scheme(const char *uri, Mutex &mutex, Cond &cond, if (playlist_plugins_enabled[i] && plugin->open_uri != nullptr && plugin->schemes != nullptr && - string_array_contains(plugin->schemes, scheme)) { + string_array_contains(plugin->schemes, scheme.c_str())) { playlist = playlist_plugin_open_uri(plugin, uri, mutex, cond); if (playlist != nullptr) @@ -156,7 +155,6 @@ playlist_list_open_uri_scheme(const char *uri, Mutex &mutex, Cond &cond, } } - g_free(scheme); return playlist; } @@ -273,9 +271,7 @@ playlist_list_open_stream_suffix(InputStream &is, const char *suffix) SongEnumerator * playlist_list_open_stream(InputStream &is, const char *uri) { - const char *suffix; - - is.LockWaitReady(); + assert(is.ready); const char *const mime = is.GetMimeType(); if (mime != nullptr) { @@ -284,7 +280,7 @@ playlist_list_open_stream(InputStream &is, const char *uri) return playlist; } - suffix = uri != nullptr ? uri_get_suffix(uri) : nullptr; + 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) @@ -321,7 +317,7 @@ playlist_list_open_path(const char *path_fs, Mutex &mutex, Cond &cond, return nullptr; Error error; - InputStream *is = InputStream::Open(path_fs, mutex, cond, error); + InputStream *is = InputStream::OpenReady(path_fs, mutex, cond, error); if (is == nullptr) { if (error.IsDefined()) LogError(error); @@ -329,8 +325,6 @@ playlist_list_open_path(const char *path_fs, Mutex &mutex, Cond &cond, return nullptr; } - is->LockWaitReady(); - auto playlist = playlist_list_open_stream_suffix(*is, suffix); if (playlist != nullptr) *is_r = is; diff --git a/src/PlaylistSave.cxx b/src/PlaylistSave.cxx index 481d9bf75..3bc66d16e 100644 --- a/src/PlaylistSave.cxx +++ b/src/PlaylistSave.cxx @@ -23,21 +23,21 @@ #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()) { const auto path = map_song_fs(song); @@ -45,7 +45,7 @@ playlist_print_song(FILE *file, const Song &song) 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 70b40f3fa..d9126aed2 100644 --- a/src/PlaylistSave.hxx +++ b/src/PlaylistSave.hxx @@ -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 index 60774dc36..2da644bc2 100644 --- a/src/PlaylistSong.cxx +++ b/src/PlaylistSong.cxx @@ -24,43 +24,42 @@ #include "DatabaseGlue.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 "Song.hxx" -#include <glib.h> - #include <assert.h> #include <string.h> static void -merge_song_metadata(Song *dest, const Song *base, - const Song *add) +merge_song_metadata(DetachedSong *dest, const DetachedSong *base, + const DetachedSong *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; + { + TagBuilder builder(add->GetTag()); + builder.Complement(base->GetTag()); + dest->SetTag(builder.Commit()); + } + + dest->SetLastModified(base->GetLastModified()); + dest->SetStartMS(add->GetStartMS()); + dest->SetEndMS(add->GetEndMS()); } -static Song * -apply_song_metadata(Song *dest, const Song *src) +static DetachedSong * +apply_song_metadata(DetachedSong *dest, const DetachedSong *src) { - Song *tmp; + DetachedSong *tmp; assert(dest != nullptr); assert(src != nullptr); - if (src->tag == nullptr && src->start_ms == 0 && src->end_ms == 0) + if (!src->GetTag().IsDefined() && + src->GetStartMS() == 0 && src->GetEndMS() == 0) return dest; if (dest->IsInDatabase()) { @@ -72,37 +71,41 @@ apply_song_metadata(Song *dest, const Song *src) if (path_utf8.empty()) path_utf8 = path_fs.c_str(); - tmp = Song::NewFile(path_utf8.c_str(), nullptr); + tmp = new DetachedSong(std::move(path_utf8)); merge_song_metadata(tmp, dest, src); } else { - tmp = Song::NewFile(dest->uri, nullptr); + tmp = new DetachedSong(dest->GetURI()); 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) + 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 */ - tmp->tag->time = dest->tag->time - src->start_ms / 1000; + tmp->WritableTag().time = + dest->GetTag().time - src->GetStartMS() / 1000; - dest->Free(); + delete dest; return tmp; } -static Song * -playlist_check_load_song(const Song *song, const char *uri, bool secure) +static DetachedSong * +playlist_check_load_song(const DetachedSong *song, const char *uri, bool secure) { - Song *dest; + DetachedSong *dest; if (uri_has_scheme(uri)) { - dest = Song::NewRemote(uri); - } else if (PathTraits::IsAbsoluteUTF8(uri) && secure) { - dest = Song::LoadFile(uri, nullptr); - if (dest == nullptr) + dest = new DetachedSong(uri); + } else if (PathTraitsUTF8::IsAbsolute(uri) && secure) { + Song *tmp = Song::LoadFile(uri, nullptr); + if (tmp == nullptr) return nullptr; + + dest = new DetachedSong(*tmp); + delete tmp; } else { const Database *db = GetDatabase(); if (db == nullptr) @@ -113,22 +116,22 @@ playlist_check_load_song(const Song *song, const char *uri, bool secure) /* not found in database */ return nullptr; - dest = tmp->DupDetached(); + dest = new DetachedSong(*tmp); db->ReturnSong(tmp); } return apply_song_metadata(dest, song); } -Song * -playlist_check_translate_song(Song *song, const char *base_uri, +DetachedSong * +playlist_check_translate_song(DetachedSong *song, const char *base_uri, bool secure) { if (song->IsInDatabase()) /* already ok */ return song; - const char *uri = song->uri; + const char *uri = song->GetURI(); if (uri_has_scheme(uri)) { if (uri_supported_scheme(uri)) @@ -136,19 +139,19 @@ playlist_check_translate_song(Song *song, const char *base_uri, return song; else { /* unsupported remote song */ - song->Free(); + delete song; return nullptr; } } if (base_uri != nullptr && strcmp(base_uri, ".") == 0) - /* PathTraits::GetParentUTF8() returns "." when there + /* 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 (PathTraits::IsAbsoluteUTF8(uri)) { + if (PathTraitsUTF8::IsAbsolute(uri)) { /* XXX fs_charset vs utf8? */ const char *suffix = map_to_relative_path(uri); assert(suffix != nullptr); @@ -158,19 +161,21 @@ playlist_check_translate_song(Song *song, const char *base_uri, else if (!secure) { /* local files must be relative to the music directory when "secure" is enabled */ - song->Free(); + delete song; return nullptr; } base_uri = nullptr; } - char *allocated = nullptr; - if (base_uri != nullptr) - uri = allocated = g_build_filename(base_uri, uri, nullptr); + std::string full_uri; + if (base_uri != nullptr) { + full_uri = PathTraitsUTF8::Build(base_uri, uri); + uri = full_uri.c_str(); + } + + DetachedSong *dest = playlist_check_load_song(song, uri, secure); + delete song; - 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 index d0db99868..9cfb09b24 100644 --- a/src/PlaylistSong.hxx +++ b/src/PlaylistSong.hxx @@ -20,7 +20,7 @@ #ifndef MPD_PLAYLIST_SONG_HXX #define MPD_PLAYLIST_SONG_HXX -struct Song; +class DetachedSong; /** * Verifies the song, returns nullptr if it is unsafe. Translate the @@ -30,8 +30,8 @@ struct Song; * @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, +DetachedSong * +playlist_check_translate_song(DetachedSong *song, const char *base_uri, bool secure); #endif diff --git a/src/PlaylistState.cxx b/src/PlaylistState.cxx index ac2deebbf..bb776dce7 100644 --- a/src/PlaylistState.cxx +++ b/src/PlaylistState.cxx @@ -27,16 +27,15 @@ #include "PlaylistError.hxx" #include "Playlist.hxx" #include "QueueSave.hxx" -#include "TextFile.hxx" +#include "fs/TextFile.hxx" #include "PlayerControl.hxx" #include "ConfigGlobal.hxx" #include "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..408113e49 100644 --- a/src/PlaylistState.hxx +++ b/src/PlaylistState.hxx @@ -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..672111ea5 --- /dev/null +++ b/src/PlaylistTag.cxx @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * 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..91adc01b7 100644 --- a/src/PlaylistUpdate.cxx +++ b/src/PlaylistUpdate.cxx @@ -22,35 +22,36 @@ #include "DatabaseGlue.hxx" #include "DatabasePlugin.hxx" #include "Song.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()) /* only update Songs instances that are "detached" from the Database */ return false; - Song *original = db.GetSong(song.uri, IgnoreError()); + Song *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; + song.SetLastModified(original->mtime); if (original->tag != nullptr) - song.ReplaceTag(Tag(*original->tag)); + song.SetTag(*original->tag); db.ReturnSong(original); return true; diff --git a/src/PlaylistVector.cxx b/src/PlaylistVector.cxx index 5bb0cdf64..701edd896 100644 --- a/src/PlaylistVector.cxx +++ b/src/PlaylistVector.cxx @@ -24,7 +24,6 @@ #include <algorithm> #include <assert.h> -#include <string.h> PlaylistVector::iterator PlaylistVector::find(const char *name) diff --git a/src/Queue.cxx b/src/Queue.cxx index 451609438..d4d6531f9 100644 --- a/src/Queue.cxx +++ b/src/Queue.cxx @@ -19,9 +19,7 @@ #include "config.h" #include "Queue.hxx" -#include "Song.hxx" - -#include <stdlib.h> +#include "DetachedSong.hxx" queue::queue(unsigned _max_length) :max_length(_max_length), length(0), @@ -86,7 +84,7 @@ queue::ModifyAtOrder(unsigned _order) } unsigned -queue::Append(Song *song, uint8_t priority) +queue::Append(DetachedSong &&song, uint8_t priority) { assert(!IsFull()); @@ -94,7 +92,7 @@ queue::Append(Song *song, uint8_t priority) const unsigned id = id_table.Insert(position); auto &item = items[position]; - item.song = song->DupDetached(); + item.song = new DetachedSong(std::move(song)); item.id = id; item.version = version; item.priority = priority; @@ -221,11 +219,7 @@ queue::DeletePosition(unsigned position) { assert(position < length); - { - Song &song = Get(position); - assert(!song.IsInDatabase() || song.IsDetached()); - song.Free(); - } + delete items[position].song; const unsigned id = PositionToId(position); const unsigned _order = PositionToOrder(position); @@ -259,9 +253,7 @@ queue::Clear() for (unsigned i = 0; i < length; i++) { Item *item = &items[i]; - assert(!item->song->IsInDatabase() || - item->song->IsDetached()); - item->song->Free(); + delete item->song; id_table.Erase(item->id); } diff --git a/src/Queue.hxx b/src/Queue.hxx index da90d4a3d..8f893dbfa 100644 --- a/src/Queue.hxx +++ b/src/Queue.hxx @@ -29,7 +29,7 @@ #include <assert.h> #include <stdint.h> -struct Song; +class DetachedSong; /** * A queue of songs. This is the backend of the playlist: it contains @@ -53,7 +53,7 @@ struct queue { * information attached. */ struct Item { - Song *song; + DetachedSong *song; /** the unique id of this item in the queue */ unsigned id; @@ -200,7 +200,7 @@ struct queue { /** * Returns the song at the specified position. */ - Song &Get(unsigned position) const { + DetachedSong &Get(unsigned position) const { assert(position < length); return *items[position].song; @@ -209,7 +209,7 @@ struct queue { /** * Returns the song at the specified order number. */ - Song &GetOrder(unsigned _order) const { + DetachedSong &GetOrder(unsigned _order) const { return Get(OrderToPosition(_order)); } @@ -268,7 +268,7 @@ struct queue { * * @param priority the priority of this new queue item */ - unsigned Append(Song *song, uint8_t priority); + unsigned Append(DetachedSong &&song, uint8_t priority); /** * Swaps two songs, addressed by their position. diff --git a/src/QueuePrint.cxx b/src/QueuePrint.cxx index d5651cde0..ab00331b0 100644 --- a/src/QueuePrint.cxx +++ b/src/QueuePrint.cxx @@ -22,13 +22,8 @@ #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. @@ -99,7 +94,7 @@ queue_find(Client &client, const queue &queue, const SongFilter &filter) { for (unsigned i = 0; i < queue.GetLength(); i++) { - const Song &song = queue.Get(i); + const DetachedSong &song = queue.Get(i); if (filter.Match(song)) queue_print_song_info(client, queue, i); diff --git a/src/QueueSave.cxx b/src/QueueSave.cxx index 6a1a51992..b84c051bb 100644 --- a/src/QueueSave.cxx +++ b/src/QueueSave.cxx @@ -21,37 +21,35 @@ #include "QueueSave.hxx" #include "Queue.hxx" #include "PlaylistError.hxx" -#include "Song.hxx" +#include "DetachedSong.hxx" #include "SongSave.hxx" #include "DatabasePlugin.hxx" #include "DatabaseGlue.hxx" -#include "TextFile.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 <glib.h> - #include <stdlib.h> #define PRIO_LABEL "Prio: " static void -queue_save_database_song(FILE *fp, int idx, const Song &song) +queue_save_database_song(FILE *fp, int idx, const DetachedSong &song) { - const auto uri = song.GetURI(); - fprintf(fp, "%i:%s\n", idx, uri.c_str()); + fprintf(fp, "%i:%s\n", idx, song.GetURI()); } static void -queue_save_full_song(FILE *fp, const Song &song) +queue_save_full_song(FILE *fp, const DetachedSong &song) { song_save(fp, song); } static void -queue_save_song(FILE *fp, int idx, const Song &song) +queue_save_song(FILE *fp, int idx, const DetachedSong &song) { if (song.IsInDatabase()) queue_save_database_song(fp, idx, song); @@ -78,7 +76,7 @@ queue_load_song(TextFile &file, const char *line, queue &queue) return; uint8_t priority = 0; - if (g_str_has_prefix(line, PRIO_LABEL)) { + if (StringStartsWith(line, PRIO_LABEL)) { priority = strtoul(line + sizeof(PRIO_LABEL) - 1, nullptr, 10); line = file.ReadLine(); @@ -86,16 +84,15 @@ queue_load_song(TextFile &file, const char *line, queue &queue) return; } - const Database *db = nullptr; - Song *song; + DetachedSong *song; - if (g_str_has_prefix(line, SONG_BEGIN)) { + if (StringStartsWith(line, SONG_BEGIN)) { const char *uri = line + sizeof(SONG_BEGIN) - 1; - if (!uri_has_scheme(uri) && !PathTraits::IsAbsoluteUTF8(uri)) + if (!uri_has_scheme(uri) && !PathTraitsUTF8::IsAbsolute(uri)) return; Error error; - song = song_load(file, nullptr, uri, error); + song = song_load(file, uri, error); if (song == nullptr) { LogError(error); return; @@ -112,22 +109,21 @@ queue_load_song(TextFile &file, const char *line, queue &queue) const char *uri = endptr + 1; if (uri_has_scheme(uri)) { - song = Song::NewRemote(uri); + song = new DetachedSong(uri); } else { - db = GetDatabase(); + const Database *db = GetDatabase(); if (db == nullptr) return; - song = db->GetSong(uri, IgnoreError()); - if (song == nullptr) + Song *tmp = db->GetSong(uri, IgnoreError()); + if (tmp == nullptr) return; + + song = new DetachedSong(*tmp); + db->ReturnSong(tmp); } } - queue.Append(song, priority); - - if (db != nullptr) - db->ReturnSong(song); - else - song->Free(); + queue.Append(std::move(*song), priority); + delete song; } diff --git a/src/ReplayGainConfig.cxx b/src/ReplayGainConfig.cxx index 8f9b0d3f7..00ae5787f 100644 --- a/src/ReplayGainConfig.cxx +++ b/src/ReplayGainConfig.cxx @@ -22,12 +22,12 @@ #include "Idle.hxx" #include "ConfigData.hxx" #include "ConfigGlobal.hxx" -#include "Playlist.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/Song.cxx b/src/Song.cxx index 6213d5e66..bb74a6951 100644 --- a/src/Song.cxx +++ b/src/Song.cxx @@ -21,13 +21,12 @@ #include "Song.hxx" #include "Directory.hxx" #include "tag/Tag.hxx" - -#include <glib.h> +#include "util/Alloc.hxx" +#include "DetachedSong.hxx" #include <assert.h> #include <string.h> - -Directory detached_root; +#include <stdlib.h> static Song * song_alloc(const char *uri, Directory *parent) @@ -39,7 +38,7 @@ song_alloc(const char *uri, Directory *parent) assert(uri_length); Song *song = (Song *) - g_malloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1); + xalloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1); song->tag = nullptr; memcpy(song->uri, uri, uri_length + 1); @@ -51,104 +50,27 @@ song_alloc(const char *uri, Directory *parent) } Song * -Song::NewRemote(const char *uri) +Song::NewFrom(DetachedSong &&other, Directory *parent) { - return song_alloc(uri, nullptr); + Song *song = song_alloc(other.GetURI(), parent); + song->tag = new 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) { - 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; + free(this); } std::string @@ -156,7 +78,7 @@ Song::GetURI() const { assert(*uri); - if (!IsInDatabase() || parent->IsRoot()) + if (parent == nullptr || parent->IsRoot()) return std::string(uri); else { const char *path = parent->GetPath(); diff --git a/src/Song.hxx b/src/Song.hxx index b74690e77..c9719edd7 100644 --- a/src/Song.hxx +++ b/src/Song.hxx @@ -26,19 +26,18 @@ #include <string> #include <assert.h> -#include <sys/time.h> +#include <time.h> #define SONG_FILE "file: " #define SONG_TIME "Time: " struct Tag; +struct Directory; +class DetachedSong; /** - * A dummy #directory instance that is used for "detached" song - * copies. + * A song file inside the configured music directory. */ -extern struct Directory detached_root; - struct Song { /** * Pointers to the siblings of this directory within the @@ -51,7 +50,14 @@ struct Song { 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 *parent; + time_t mtime; /** @@ -65,11 +71,14 @@ struct Song { */ unsigned end_ms; + /** + * The file name. If #parent is nullptr, then this is the URI + * relative to the music directory. + */ char uri[sizeof(int)]; - /** allocate a new song with a remote URL */ gcc_malloc - static Song *NewRemote(const char *uri); + static Song *NewFrom(DetachedSong &&other, Directory *parent); /** allocate a new song with a local file name */ gcc_malloc @@ -87,47 +96,8 @@ struct Song { 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(); @@ -142,11 +112,4 @@ struct Song { 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 index 0e268a31a..2515647d1 100644 --- a/src/SongEnumerator.hxx +++ b/src/SongEnumerator.hxx @@ -20,7 +20,7 @@ #ifndef MPD_SONG_ENUMERATOR_HXX #define MPD_SONG_ENUMERATOR_HXX -struct Song; +class DetachedSong; /** * An object which provides serial access to a number of #Song @@ -35,7 +35,7 @@ public: * freeing the returned #Song object. Returns nullptr if * there are no more songs. */ - virtual Song *NextSong() = 0; + virtual DetachedSong *NextSong() = 0; }; #endif diff --git a/src/SongFilter.cxx b/src/SongFilter.cxx index 8d90c5fc8..53ab784c2 100644 --- a/src/SongFilter.cxx +++ b/src/SongFilter.cxx @@ -20,6 +20,7 @@ #include "config.h" #include "SongFilter.hxx" #include "Song.hxx" +#include "DetachedSong.hxx" #include "tag/Tag.hxx" #include "util/ASCII.hxx" #include "util/UriUtil.hxx" @@ -151,6 +152,18 @@ SongFilter::Item::Match(const Song &song) const return song.tag != NULL && Match(*song.tag); } +bool +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()); +} + SongFilter::SongFilter(unsigned tag, const char *value, bool fold_case) { items.push_back(Item(tag, value, fold_case)); @@ -203,6 +216,16 @@ SongFilter::Match(const Song &song) const return true; } +bool +SongFilter::Match(const DetachedSong &song) const +{ + for (const auto &i : items) + if (!i.Match(song)) + return false; + + return true; +} + std::string SongFilter::GetBase() const { diff --git a/src/SongFilter.hxx b/src/SongFilter.hxx index b15127c07..16970c350 100644 --- a/src/SongFilter.hxx +++ b/src/SongFilter.hxx @@ -38,6 +38,7 @@ struct Tag; struct TagItem; struct Song; +class DetachedSong; class SongFilter { public: @@ -80,6 +81,9 @@ public: gcc_pure bool Match(const Song &song) const; + + gcc_pure + bool Match(const DetachedSong &song) const; }; private: @@ -105,6 +109,9 @@ public: gcc_pure bool Match(const Song &song) const; + gcc_pure + bool Match(const DetachedSong &song) const; + const std::list<Item> &GetItems() const { return items; } diff --git a/src/SongPrint.cxx b/src/SongPrint.cxx index ea164d02b..b3e0c7b58 100644 --- a/src/SongPrint.cxx +++ b/src/SongPrint.cxx @@ -20,6 +20,7 @@ #include "config.h" #include "SongPrint.hxx" #include "Song.hxx" +#include "DetachedSong.hxx" #include "Directory.hxx" #include "TimePrint.hxx" #include "TagPrint.hxx" @@ -27,21 +28,31 @@ #include "Client.hxx" #include "util/UriUtil.hxx" +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) { - if (song.IsInDatabase() && !song.parent->IsRoot()) { + if (song.parent != nullptr && !song.parent->IsRoot()) { 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(); + } 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 @@ -66,3 +77,28 @@ song_print_info(Client &client, const Song &song) if (song.tag != nullptr) 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..5779af4c3 100644 --- a/src/SongPrint.hxx +++ b/src/SongPrint.hxx @@ -21,12 +21,19 @@ #define MPD_SONG_PRINT_HXX struct Song; +class DetachedSong; class Client; void +song_print_info(Client &client, const DetachedSong &song); + +void song_print_info(Client &client, const Song &song); void song_print_uri(Client &client, const Song &song); +void +song_print_uri(Client &client, const DetachedSong &song); + #endif diff --git a/src/SongSave.cxx b/src/SongSave.cxx index 63e279a16..d524f327e 100644 --- a/src/SongSave.cxx +++ b/src/SongSave.cxx @@ -20,9 +20,9 @@ #include "config.h" #include "SongSave.hxx" #include "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,15 +37,21 @@ 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); @@ -54,24 +60,33 @@ song_save(FILE *fp, const Song &song) 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 +94,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 +104,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 +124,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..6b458679a 100644 --- a/src/SongSave.hxx +++ b/src/SongSave.hxx @@ -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 index 4d422657a..1c2d01080 100644 --- a/src/SongSort.cxx +++ b/src/SongSort.cxx @@ -20,7 +20,6 @@ #include "config.h" #include "SongSort.hxx" #include "Song.hxx" -#include "util/list.h" #include "tag/Tag.hxx" extern "C" { @@ -29,7 +28,6 @@ extern "C" { #include <glib.h> -#include <assert.h> #include <stdlib.h> static const char * diff --git a/src/SongSort.hxx b/src/SongSort.hxx index b3b67b0c0..45ab3dae9 100644 --- a/src/SongSort.hxx +++ b/src/SongSort.hxx @@ -23,6 +23,6 @@ struct list_head; void -song_list_sort(struct list_head *songs); +song_list_sort(list_head *songs); #endif diff --git a/src/SongSticker.cxx b/src/SongSticker.cxx index a0c4d3585..b17820fd5 100644 --- a/src/SongSticker.cxx +++ b/src/SongSticker.cxx @@ -29,53 +29,38 @@ #include <string.h> std::string -sticker_song_get_value(const Song *song, const char *name) +sticker_song_get_value(const Song &song, const char *name) { - assert(song != nullptr); - assert(song->IsInDatabase()); - - const auto uri = song->GetURI(); + const auto uri = song.GetURI(); return sticker_load_value("song", uri.c_str(), name); } bool -sticker_song_set_value(const Song *song, +sticker_song_set_value(const Song &song, const char *name, const char *value) { - assert(song != nullptr); - assert(song->IsInDatabase()); - - const auto uri = song->GetURI(); + const auto uri = song.GetURI(); return sticker_store_value("song", uri.c_str(), name, value); } bool -sticker_song_delete(const Song *song) +sticker_song_delete(const Song &song) { - assert(song != nullptr); - assert(song->IsInDatabase()); - - const auto uri = song->GetURI(); + const auto uri = song.GetURI(); return sticker_delete("song", uri.c_str()); } bool -sticker_song_delete_value(const Song *song, const char *name) +sticker_song_delete_value(const Song &song, const char *name) { - assert(song != nullptr); - assert(song->IsInDatabase()); - - const auto uri = song->GetURI(); + const auto uri = song.GetURI(); return sticker_delete_value("song", uri.c_str(), name); } struct sticker * -sticker_song_get(const Song *song) +sticker_song_get(const Song &song) { - assert(song != nullptr); - assert(song->IsInDatabase()); - - const auto uri = song->GetURI(); + const auto uri = song.GetURI(); return sticker_load("song", uri.c_str()); } diff --git a/src/SongSticker.hxx b/src/SongSticker.hxx index 0923f0c3a..a5de90517 100644 --- a/src/SongSticker.hxx +++ b/src/SongSticker.hxx @@ -34,28 +34,28 @@ struct sticker; */ gcc_pure std::string -sticker_song_get_value(const Song *song, const char *name); +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, +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); +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); +sticker_song_delete_value(const Song &song, const char *name); /** * Loads the sticker for the specified song. @@ -63,8 +63,8 @@ sticker_song_delete_value(const Song *song, const char *name); * @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); +sticker * +sticker_song_get(const Song &song); /** * Finds stickers with the specified name below the specified diff --git a/src/SongUpdate.cxx b/src/SongUpdate.cxx index 1a873fedc..cba3c6957 100644 --- a/src/SongUpdate.cxx +++ b/src/SongUpdate.cxx @@ -20,14 +20,11 @@ #include "config.h" /* must be first for large file support */ #include "Song.hxx" #include "util/UriUtil.hxx" -#include "util/Error.hxx" #include "Directory.hxx" #include "Mapper.hxx" #include "fs/AllocatedPath.hxx" #include "fs/Traits.hxx" #include "fs/FileSystem.hxx" -#include "InputStream.hxx" -#include "DecoderPlugin.hxx" #include "DecoderList.hxx" #include "tag/Tag.hxx" #include "tag/TagBuilder.hxx" @@ -35,13 +32,11 @@ #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) @@ -49,7 +44,7 @@ Song::LoadFile(const char *path_utf8, Directory *parent) Song *song; bool ret; - assert((parent == nullptr) == PathTraits::IsAbsoluteUTF8(path_utf8)); + assert((parent == nullptr) == PathTraitsUTF8::IsAbsolute(path_utf8)); assert(!uri_has_scheme(path_utf8)); assert(strchr(path_utf8, '\n') == nullptr); @@ -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()) @@ -105,34 +98,31 @@ Song::UpdateFile() mtime = st.st_mtime; delete tag; - tag = tag_builder.Commit(); + tag = tag_builder.CommitNew(); 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; + delete tag; + tag = tag_builder.CommitNew(); return true; } diff --git a/src/StateFile.cxx b/src/StateFile.cxx index 75cef2c99..7ca8b8946 100644 --- a/src/StateFile.cxx +++ b/src/StateFile.cxx @@ -21,10 +21,10 @@ #include "StateFile.hxx" #include "OutputState.hxx" #include "PlaylistState.hxx" -#include "TextFile.hxx" +#include "fs/TextFile.hxx" #include "Partition.hxx" #include "Volume.hxx" -#include "event/Loop.hxx" + #include "fs/FileSystem.hxx" #include "util/Domain.hxx" #include "Log.hxx" diff --git a/src/Stats.cxx b/src/Stats.cxx index f224bdf49..1764516ef 100644 --- a/src/Stats.cxx +++ b/src/Stats.cxx @@ -26,38 +26,65 @@ #include "DatabasePlugin.hxx" #include "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..e11be0d68 100644 --- a/src/Stats.hxx +++ b/src/Stats.hxx @@ -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.hxx b/src/StickerDatabase.hxx index 42522b7b4..75f0fc77b 100644 --- a/src/StickerDatabase.hxx +++ b/src/StickerDatabase.hxx @@ -106,7 +106,7 @@ sticker_delete_value(const char *type, const char *uri, const char *name); * @param sticker the sticker object to be freed */ void -sticker_free(struct sticker *sticker); +sticker_free(sticker *sticker); /** * Determines a single value in a sticker. @@ -117,7 +117,7 @@ sticker_free(struct sticker *sticker); */ gcc_pure const char * -sticker_get_value(const struct sticker &sticker, const char *name); +sticker_get_value(const sticker &sticker, const char *name); /** * Iterates over all sticker items in a sticker. @@ -127,7 +127,7 @@ sticker_get_value(const struct sticker &sticker, const char *name); * @param user_data an opaque pointer for the callback function */ void -sticker_foreach(const struct sticker &sticker, +sticker_foreach(const sticker &sticker, void (*func)(const char *name, const char *value, void *user_data), void *user_data); @@ -139,7 +139,7 @@ sticker_foreach(const struct sticker &sticker, * @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 * sticker_load(const char *type, const char *uri); /** diff --git a/src/TagFile.cxx b/src/TagFile.cxx index 785a74987..3a6756008 100644 --- a/src/TagFile.cxx +++ b/src/TagFile.cxx @@ -29,58 +29,70 @@ #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..84ac6c259 100644 --- a/src/TagFile.hxx +++ b/src/TagFile.hxx @@ -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..0b096fdf7 100644 --- a/src/TagPrint.cxx +++ b/src/TagPrint.cxx @@ -35,6 +35,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..48ddc28ec 100644 --- a/src/TagPrint.hxx +++ b/src/TagPrint.hxx @@ -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/TagStream.cxx b/src/TagStream.cxx new file mode 100644 index 000000000..8fc1f3312 --- /dev/null +++ b/src/TagStream.cxx @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "TagStream.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "DecoderList.hxx" +#include "DecoderPlugin.hxx" +#include "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..8fe7d4101 --- /dev/null +++ b/src/TagStream.hxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_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/TextInputStream.cxx b/src/TextInputStream.cxx index 36a726aa6..036da052f 100644 --- a/src/TextInputStream.cxx +++ b/src/TextInputStream.cxx @@ -21,7 +21,6 @@ #include "TextInputStream.hxx" #include "InputStream.hxx" #include "util/CharUtil.hxx" -#include "util/fifo_buffer.h" #include "util/Error.hxx" #include "Log.hxx" diff --git a/src/TextInputStream.hxx b/src/TextInputStream.hxx index a6c15f670..350871488 100644 --- a/src/TextInputStream.hxx +++ b/src/TextInputStream.hxx @@ -25,7 +25,6 @@ #include <string> struct InputStream; -struct fifo_buffer; class TextInputStream { InputStream &is; diff --git a/src/Timer.cxx b/src/Timer.cxx index 661aa29ee..f6f5cffc2 100644 --- a/src/Timer.cxx +++ b/src/Timer.cxx @@ -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..96e25b27a 100644 --- a/src/Timer.hxx +++ b/src/Timer.hxx @@ -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.hxx b/src/UpdateArchive.hxx index 8f52ca0b6..35601a844 100644 --- a/src/UpdateArchive.hxx +++ b/src/UpdateArchive.hxx @@ -26,7 +26,6 @@ #include <sys/stat.h> struct Directory; -struct archive_plugin; #ifdef ENABLE_ARCHIVE diff --git a/src/UpdateContainer.cxx b/src/UpdateContainer.cxx index 80f059734..5b1fa2376 100644 --- a/src/UpdateContainer.cxx +++ b/src/UpdateContainer.cxx @@ -26,6 +26,7 @@ #include "Directory.hxx" #include "Song.hxx" #include "DecoderPlugin.hxx" +#include "DecoderList.hxx" #include "Mapper.hxx" #include "fs/AllocatedPath.hxx" #include "tag/TagHandler.hxx" @@ -64,14 +65,25 @@ make_directory_if_modified(Directory &parent, const char *name, 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 DecoderPlugin &plugin) + const char *suffix) { - if (plugin.container_scan == nullptr) + 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); @@ -102,7 +114,7 @@ update_container_file(Directory &directory, add_tag_handler, &tag_builder); if (tag_builder.IsDefined()) - song->tag = tag_builder.Commit(); + song->tag = tag_builder.CommitNew(); else tag_builder.Clear(); diff --git a/src/UpdateContainer.hxx b/src/UpdateContainer.hxx index 3b54fb39f..b8f0ad39a 100644 --- a/src/UpdateContainer.hxx +++ b/src/UpdateContainer.hxx @@ -31,6 +31,6 @@ bool update_container_file(Directory &directory, const char *name, const struct stat *st, - const DecoderPlugin &plugin); + const char *suffix); #endif diff --git a/src/UpdateGlue.cxx b/src/UpdateGlue.cxx index 12ea126a9..d2e9402bc 100644 --- a/src/UpdateGlue.cxx +++ b/src/UpdateGlue.cxx @@ -29,7 +29,6 @@ #include "GlobalEvents.hxx" #include "util/Error.hxx" #include "Log.hxx" -#include "Stats.hxx" #include "Main.hxx" #include "Instance.hxx" #include "system/FatalError.hxx" @@ -162,8 +161,6 @@ static void update_finished_event(void) spawn_update_task(std::move(i)); } else { progress = UPDATE_PROGRESS_IDLE; - - stats_update(); } } diff --git a/src/UpdateRemove.cxx b/src/UpdateRemove.cxx index f4043b2f3..374a767b8 100644 --- a/src/UpdateRemove.cxx +++ b/src/UpdateRemove.cxx @@ -20,7 +20,6 @@ #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" @@ -58,10 +57,13 @@ song_remove_event(void) #ifdef ENABLE_SQLITE /* if the song has a sticker, remove it */ if (sticker_enabled()) - sticker_song_delete(removed_song); + sticker_song_delete(*removed_song); #endif - instance->DeleteSong(*removed_song); + { + const auto uri = removed_song->GetURI(); + instance->DeleteSong(uri.c_str()); + } /* clear "removed_song" and send signal to update thread */ remove_mutex.lock(); diff --git a/src/UpdateSong.cxx b/src/UpdateSong.cxx index bfab5c4a0..dc49c7a84 100644 --- a/src/UpdateSong.cxx +++ b/src/UpdateSong.cxx @@ -27,7 +27,6 @@ #include "DatabaseLock.hxx" #include "Directory.hxx" #include "Song.hxx" -#include "DecoderPlugin.hxx" #include "DecoderList.hxx" #include "Log.hxx" @@ -36,7 +35,7 @@ static void update_song_file2(Directory &directory, const char *name, const struct stat *st, - const DecoderPlugin &plugin) + const char *suffix) { db_lock(); Song *song = directory.FindSong(name); @@ -57,7 +56,7 @@ update_song_file2(Directory &directory, if (!(song != nullptr && st->st_mtime == song->mtime && !walk_discard) && - update_container_file(directory, name, st, plugin)) { + update_container_file(directory, name, st, suffix)) { if (song != nullptr) { db_lock(); delete_song(directory, song); @@ -106,11 +105,9 @@ 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) + if (!decoder_plugins_supports_suffix(suffix)) return false; - update_song_file2(directory, name, st, *plugin); + update_song_file2(directory, name, st, suffix); return true; } diff --git a/src/UpdateWalk.cxx b/src/UpdateWalk.cxx index d4586456b..3afcbbdf2 100644 --- a/src/UpdateWalk.cxx +++ b/src/UpdateWalk.cxx @@ -38,16 +38,12 @@ #include "fs/Traits.hxx" #include "fs/FileSystem.hxx" #include "fs/DirectoryReader.hxx" +#include "util/Alloc.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> @@ -294,7 +290,7 @@ skip_symlink(const Directory *directory, const char *utf8_name) const char *target_str = target.c_str(); - if (PathTraits::IsAbsoluteFS(target_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); @@ -305,7 +301,7 @@ skip_symlink(const Directory *directory, const char *utf8_name) const char *p = target_str; while (*p == '.') { - if (p[1] == '.' && PathTraits::IsSeparatorFS(p[2])) { + if (p[1] == '.' && PathTraitsFS::IsSeparator(p[2])) { /* "../" moves to parent directory */ directory = directory->parent; if (directory == nullptr) { @@ -315,7 +311,7 @@ skip_symlink(const Directory *directory, const char *utf8_name) return !follow_outside_symlinks; } p += 3; - } else if (PathTraits::IsSeparatorFS(p[1])) + } else if (PathTraitsFS::IsSeparator(p[1])) /* eliminate "./" */ p += 2; else @@ -430,7 +426,7 @@ static Directory * directory_make_uri_parent_checked(const char *uri) { Directory *directory = db_get_root(); - char *duplicated = g_strdup(uri); + char *duplicated = xstrdup(uri); char *name_utf8 = duplicated, *slash; while ((slash = strchr(name_utf8, '/')) != nullptr) { @@ -447,7 +443,7 @@ directory_make_uri_parent_checked(const char *uri) name_utf8 = slash + 1; } - g_free(duplicated); + free(duplicated); return directory; } @@ -458,7 +454,7 @@ update_uri(const char *uri) if (parent == nullptr) return; - const char *name = PathTraits::GetBaseUTF8(uri); + const char *name = PathTraitsUTF8::GetBase(uri); struct stat st; if (!skip_symlink(parent, name) && diff --git a/src/Volume.cxx b/src/Volume.cxx index 6c5f8dc4d..a4112a4cc 100644 --- a/src/Volume.cxx +++ b/src/Volume.cxx @@ -22,11 +22,11 @@ #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 <glib.h> - #include <assert.h> #include <stdlib.h> @@ -39,7 +39,7 @@ 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; +static PeriodClock hardware_volume_clock; /** * Handler for #GlobalEvents::MIXER. @@ -54,29 +54,19 @@ mixer_event_callback(void) 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) + !hardware_volume_clock.CheckUpdate(1000)) /* 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; } @@ -115,7 +105,7 @@ read_sw_volume_state(const char *line) char *end = nullptr; long int sv; - if (!g_str_has_prefix(line, SW_VOLUME_STATE)) + if (!StringStartsWith(line, SW_VOLUME_STATE)) return false; line += sizeof(SW_VOLUME_STATE) - 1; diff --git a/src/Volume.hxx b/src/Volume.hxx index 6b937aca3..d448cc4e4 100644 --- a/src/Volume.hxx +++ b/src/Volume.hxx @@ -26,8 +26,6 @@ void volume_init(void); -void volume_finish(void); - gcc_pure int volume_level_get(void); diff --git a/src/ZeroconfAvahi.cxx b/src/ZeroconfAvahi.cxx index 083647b42..f486a2e31 100644 --- a/src/ZeroconfAvahi.cxx +++ b/src/ZeroconfAvahi.cxx @@ -29,14 +29,11 @@ #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; diff --git a/src/ZeroconfBonjour.cxx b/src/ZeroconfBonjour.cxx index 73e84fbc2..7aebd0514 100644 --- a/src/ZeroconfBonjour.cxx +++ b/src/ZeroconfBonjour.cxx @@ -43,7 +43,6 @@ public: } ~BonjourMonitor() { - Steal(); DNSServiceRefDeallocate(service_ref); } diff --git a/src/archive/Bzip2ArchivePlugin.cxx b/src/archive/Bzip2ArchivePlugin.cxx index d1e6b51af..7734d3f4b 100644 --- a/src/archive/Bzip2ArchivePlugin.cxx +++ b/src/archive/Bzip2ArchivePlugin.cxx @@ -35,9 +35,7 @@ #include <bzlib.h> -#include <stdint.h> #include <stddef.h> -#include <string.h> #ifdef HAVE_OLDER_BZIP2 #define BZ2_bzDecompressInit bzDecompressInit @@ -53,7 +51,7 @@ public: Bzip2ArchiveFile(const char *path, InputStream *_is) :ArchiveFile(bz2_archive_plugin), - name(PathTraits::GetBaseUTF8(path)), + name(PathTraitsUTF8::GetBase(path)), istream(_is) { // remove .bz2 suffix const size_t len = name.length(); @@ -148,7 +146,7 @@ bz2_open(const char *pathname, Error &error) { static Mutex mutex; static Cond cond; - InputStream *is = InputStream::Open(pathname, mutex, cond, error); + InputStream *is = InputStream::OpenReady(pathname, mutex, cond, error); if (is == nullptr) return nullptr; diff --git a/src/archive/Iso9660ArchivePlugin.cxx b/src/archive/Iso9660ArchivePlugin.cxx index ad21d4a3d..c83e38b0d 100644 --- a/src/archive/Iso9660ArchivePlugin.cxx +++ b/src/archive/Iso9660ArchivePlugin.cxx @@ -32,7 +32,6 @@ #include "util/Error.hxx" #include "util/Domain.hxx" -#include <cdio/cdio.h> #include <cdio/iso9660.h> #include <stdlib.h> @@ -41,11 +40,11 @@ #define CEILING(x, y) ((x+(y-1))/y) class Iso9660ArchiveFile final : public ArchiveFile { -public: RefCount ref; iso9660_t *iso; +public: Iso9660ArchiveFile(iso9660_t *_iso) :ArchiveFile(iso9660_archive_plugin), iso(_iso) {} @@ -53,11 +52,19 @@ public: 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 { @@ -131,31 +138,35 @@ Iso9660ArchiveFile::Visit(ArchiveVisitor &visitor) /* single archive handling */ -struct Iso9660InputStream { +class Iso9660InputStream { InputStream base; - Iso9660ArchiveFile *archive; + Iso9660ArchiveFile &archive; iso9660_stat_t *statbuf; - size_t max_blocks; +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), - max_blocks(CEILING(statbuf->size, ISO_BLOCKSIZE)) { - + archive(_archive), statbuf(_statbuf) { base.ready = true; base.size = statbuf->size; - archive->ref.Increment(); + archive.Ref(); } ~Iso9660InputStream() { free(statbuf); - archive->Unref(); + archive.Unref(); } + + InputStream *Get() { + return &base; + } + + size_t Read(void *ptr, size_t size, Error &error); }; InputStream * @@ -173,7 +184,7 @@ Iso9660ArchiveFile::OpenStream(const char *pathname, Iso9660InputStream *iis = new Iso9660InputStream(*this, pathname, mutex, cond, statbuf); - return &iis->base; + return iis->Get(); } static void @@ -184,45 +195,49 @@ iso9660_input_close(InputStream *is) delete iis; } - -static size_t -iso9660_input_read(InputStream *is, void *ptr, size_t size, - Error &error) +inline size_t +Iso9660InputStream::Read(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; + 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) { - cur_block = is->offset / ISO_BLOCKSIZE; + if (no_blocks == 0) + return 0; - readed = iso9660_iso_seek_read (iis->archive->iso, ptr, - iis->statbuf->lsn + cur_block, no_blocks); + cur_block = base.offset / ISO_BLOCKSIZE; - 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; - } + readed = archive.SeekRead(ptr, statbuf->lsn + cur_block, + no_blocks); - is->offset += readed; + 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) { diff --git a/src/archive/ZzipArchivePlugin.cxx b/src/archive/ZzipArchivePlugin.cxx index 973fe91dc..2ad4ea72e 100644 --- a/src/archive/ZzipArchivePlugin.cxx +++ b/src/archive/ZzipArchivePlugin.cxx @@ -34,8 +34,6 @@ #include <zzip/zzip.h> -#include <string.h> - class ZzipArchiveFile final : public ArchiveFile { public: RefCount ref; diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx index 74802ced4..23641ddec 100644 --- a/src/command/AllCommands.cxx +++ b/src/command/AllCommands.cxx @@ -20,6 +20,7 @@ #include "config.h" #include "AllCommands.hxx" #include "QueueCommands.hxx" +#include "TagCommands.hxx" #include "PlayerCommands.hxx" #include "PlaylistCommands.hxx" #include "DatabaseCommands.hxx" @@ -74,9 +75,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 }, diff --git a/src/command/CommandError.cxx b/src/command/CommandError.cxx index fc14d4a5d..c1ef09904 100644 --- a/src/command/CommandError.cxx +++ b/src/command/CommandError.cxx @@ -24,9 +24,8 @@ #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/DatabaseCommands.cxx b/src/command/DatabaseCommands.cxx index b86cbdae7..55405c1ef 100644 --- a/src/command/DatabaseCommands.cxx +++ b/src/command/DatabaseCommands.cxx @@ -26,14 +26,10 @@ #include "CommandError.hxx" #include "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/FileCommands.cxx b/src/command/FileCommands.cxx index eecc3102f..9c7118bcb 100644 --- a/src/command/FileCommands.cxx +++ b/src/command/FileCommands.cxx @@ -25,13 +25,16 @@ #include "ClientFile.hxx" #include "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/MessageCommands.cxx b/src/command/MessageCommands.cxx index 7d9893e70..0c2263d59 100644 --- a/src/command/MessageCommands.cxx +++ b/src/command/MessageCommands.cxx @@ -24,7 +24,6 @@ #include "Instance.hxx" #include "Main.hxx" #include "protocol/Result.hxx" -#include "protocol/ArgParser.hxx" #include <set> #include <string> diff --git a/src/command/OtherCommands.cxx b/src/command/OtherCommands.cxx index 7b2cb1331..6ac5ca1c7 100644 --- a/src/command/OtherCommands.cxx +++ b/src/command/OtherCommands.cxx @@ -26,6 +26,8 @@ #include "Song.hxx" #include "SongPrint.hxx" #include "TagPrint.hxx" +#include "TagStream.hxx" +#include "tag/TagHandler.hxx" #include "TimePrint.hxx" #include "Mapper.hxx" #include "DecoderPrint.hxx" @@ -44,10 +46,6 @@ #include "Client.hxx" #include "Idle.hxx" -#ifdef ENABLE_SQLITE -#include "StickerDatabase.hxx" -#endif - #include <assert.h> #include <string.h> @@ -102,6 +100,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[]) { @@ -140,6 +152,22 @@ handle_lsinfo(Client &client, int argc, char *argv[]) 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; + } + + return CommandResult::OK; + } + CommandResult result = handle_lsinfo2(client, argc, argv); if (result != CommandResult::OK) return result; diff --git a/src/command/OutputCommands.cxx b/src/command/OutputCommands.cxx index e949448af..d4d699180 100644 --- a/src/command/OutputCommands.cxx +++ b/src/command/OutputCommands.cxx @@ -24,8 +24,6 @@ #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/PlaylistCommands.cxx b/src/command/PlaylistCommands.cxx index d178fa097..20c5a2595 100644 --- a/src/command/PlaylistCommands.cxx +++ b/src/command/PlaylistCommands.cxx @@ -35,9 +35,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/StickerCommands.cxx b/src/command/StickerCommands.cxx index b65e6f607..e5451d260 100644 --- a/src/command/StickerCommands.cxx +++ b/src/command/StickerCommands.cxx @@ -31,8 +31,6 @@ #include "protocol/Result.hxx" #include "util/Error.hxx" -#include <glib.h> - #include <string.h> struct sticker_song_find_data { @@ -65,7 +63,7 @@ handle_sticker_song(Client &client, int argc, char *argv[]) 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, @@ -82,7 +80,7 @@ handle_sticker_song(Client &client, int argc, char *argv[]) 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); @@ -96,7 +94,7 @@ handle_sticker_song(Client &client, int argc, char *argv[]) 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, @@ -113,8 +111,8 @@ handle_sticker_song(Client &client, int argc, char *argv[]) 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/TagCommands.cxx b/src/command/TagCommands.cxx new file mode 100644 index 000000000..829d34b81 --- /dev/null +++ b/src/command/TagCommands.cxx @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "TagCommands.hxx" +#include "CommandError.hxx" +#include "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..8ccafa76a --- /dev/null +++ b/src/command/TagCommands.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_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/cue/CueParser.cxx b/src/cue/CueParser.cxx index 60b33b6b4..f05a69ae3 100644 --- a/src/cue/CueParser.cxx +++ b/src/cue/CueParser.cxx @@ -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(std::move(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..9a32a33c4 100644 --- a/src/cue/CueParser.hxx +++ b/src/cue/CueParser.hxx @@ -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/LazyDatabase.cxx b/src/db/LazyDatabase.cxx new file mode 100644 index 000000000..f767e89cd --- /dev/null +++ b/src/db/LazyDatabase.cxx @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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(); + } +} + +Song * +LazyDatabase::GetSong(const char *uri, Error &error) const +{ + return EnsureOpen(error) + ? db->GetSong(uri, error) + : nullptr; +} + +void +LazyDatabase::ReturnSong(Song *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/LazyDatabase.hxx b/src/db/LazyDatabase.hxx new file mode 100644 index 000000000..3910cb7fa --- /dev/null +++ b/src/db/LazyDatabase.hxx @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_LAZY_DATABASE_PLUGIN_HXX +#define MPD_LAZY_DATABASE_PLUGIN_HXX + +#include "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 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; + +private: + bool EnsureOpen(Error &error) const; +}; + +#endif diff --git a/src/db/ProxyDatabasePlugin.cxx b/src/db/ProxyDatabasePlugin.cxx index 00b5d445f..7e8dcf65f 100644 --- a/src/db/ProxyDatabasePlugin.cxx +++ b/src/db/ProxyDatabasePlugin.cxx @@ -20,9 +20,9 @@ #include "config.h" #include "ProxyDatabasePlugin.hxx" #include "DatabasePlugin.hxx" +#include "DatabaseListener.hxx" #include "DatabaseSelection.hxx" #include "DatabaseError.hxx" -#include "PlaylistVector.hxx" #include "Directory.hxx" #include "Song.hxx" #include "SongFilter.hxx" @@ -32,14 +32,21 @@ #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 ProxyDatabase : public Database { +class ProxyDatabase final : public Database, SocketMonitor, IdleMonitor { + DatabaseListener &listener; + std::string host; unsigned port; @@ -49,8 +56,25 @@ class ProxyDatabase : public Database { /* 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: - static Database *Create(const config_param ¶m, + 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; @@ -84,6 +108,14 @@ private: 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"); @@ -217,9 +249,10 @@ SendConstraints(mpd_connection *connection, const DatabaseSelection &selection) } Database * -ProxyDatabase::Create(const config_param ¶m, Error &error) +ProxyDatabase::Create(EventLoop &loop, DatabaseListener &listener, + const config_param ¶m, Error &error) { - ProxyDatabase *db = new ProxyDatabase(); + ProxyDatabase *db = new ProxyDatabase(loop, listener); if (!db->Configure(param, error)) { delete db; db = nullptr; @@ -252,10 +285,10 @@ ProxyDatabase::Open(Error &error) void ProxyDatabase::Close() { - root->Free(); + delete root; if (connection != nullptr) - mpd_connection_free(connection); + Disconnect(); } bool @@ -269,11 +302,15 @@ ProxyDatabase::Connect(Error &error) 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) { - mpd_connection_free(connection); - connection = nullptr; - } + if (connection != nullptr) + Disconnect(); return false; } @@ -287,10 +324,22 @@ ProxyDatabase::CheckConnection(Error &error) assert(connection != nullptr); if (!mpd_connection_clear_error(connection)) { - mpd_connection_free(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; } @@ -302,6 +351,79 @@ ProxyDatabase::EnsureConnected(Error &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(); +} + static Song * Convert(const struct mpd_song *song); @@ -341,20 +463,19 @@ void ProxyDatabase::ReturnSong(Song *song) const { assert(song != nullptr); - assert(song->IsInDatabase()); - assert(song->IsDetached()); + assert(song->parent == nullptr); song->Free(); } static bool -Visit(struct mpd_connection *connection, const char *uri, +Visit(struct mpd_connection *connection, Directory &root, 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, +Visit(struct mpd_connection *connection, Directory &root, bool recursive, const SongFilter *filter, const struct mpd_directory *directory, VisitDirectory visit_directory, VisitSong visit_song, @@ -362,16 +483,12 @@ Visit(struct mpd_connection *connection, { 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 (visit_directory && + !visit_directory(Directory(path, &root), error)) + return false; if (recursive && - !Visit(connection, path, recursive, filter, + !Visit(connection, root, path, recursive, filter, visit_directory, visit_song, visit_playlist, error)) return false; @@ -395,7 +512,7 @@ Copy(TagBuilder &tag, TagType d_tag, static Song * Convert(const struct mpd_song *song) { - Song *s = Song::NewDetached(mpd_song_get_uri(song)); + Song *s = Song::NewFile(mpd_song_get_uri(song), nullptr); s->mtime = mpd_song_get_last_modified(song); s->start_ms = mpd_song_get_start(song) * 1000; @@ -407,7 +524,7 @@ Convert(const struct mpd_song *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(); + s->tag = tag.CommitNew(); return s; } @@ -435,7 +552,7 @@ Visit(const SongFilter *filter, } static bool -Visit(const struct mpd_playlist *playlist, +Visit(const struct mpd_playlist *playlist, Directory &root, VisitPlaylist visit_playlist, Error &error) { if (!visit_playlist) @@ -444,7 +561,7 @@ Visit(const struct mpd_playlist *playlist, PlaylistInfo p(mpd_playlist_get_path(playlist), mpd_playlist_get_last_modified(playlist)); - return visit_playlist(p, detached_root, error); + return visit_playlist(p, root, error); } class ProxyEntity { @@ -486,7 +603,7 @@ ReceiveEntities(struct mpd_connection *connection) } static bool -Visit(struct mpd_connection *connection, const char *uri, +Visit(struct mpd_connection *connection, Directory &root, const char *uri, bool recursive, const SongFilter *filter, VisitDirectory visit_directory, VisitSong visit_song, VisitPlaylist visit_playlist, Error &error) @@ -504,7 +621,7 @@ Visit(struct mpd_connection *connection, const char *uri, break; case MPD_ENTITY_TYPE_DIRECTORY: - if (!Visit(connection, recursive, filter, + if (!Visit(connection, root, recursive, filter, mpd_entity_get_directory(entity), visit_directory, visit_song, visit_playlist, error)) @@ -519,7 +636,7 @@ Visit(struct mpd_connection *connection, const char *uri, break; case MPD_ENTITY_TYPE_PLAYLIST: - if (!Visit(mpd_entity_get_playlist(entity), + if (!Visit(mpd_entity_get_playlist(entity), root, visit_playlist, error)) return false; break; @@ -578,7 +695,7 @@ ProxyDatabase::Visit(const DatabaseSelection &selection, return ::SearchSongs(connection, selection, visit_song, error); /* fall back to recursive walk (slow!) */ - return ::Visit(connection, selection.uri.c_str(), + return ::Visit(connection, *root, selection.uri.c_str(), selection.recursive, selection.filter, visit_directory, visit_song, visit_playlist, error); diff --git a/src/db/SimpleDatabasePlugin.cxx b/src/db/SimpleDatabasePlugin.cxx index e7ea7a62d..31d4213a5 100644 --- a/src/db/SimpleDatabasePlugin.cxx +++ b/src/db/SimpleDatabasePlugin.cxx @@ -26,20 +26,21 @@ #include "DatabaseSave.hxx" #include "DatabaseLock.hxx" #include "DatabaseError.hxx" -#include "TextFile.hxx" +#include "fs/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::Create(gcc_unused EventLoop &loop, + gcc_unused DatabaseListener &listener, + const config_param ¶m, Error &error) { SimpleDatabase *db = new SimpleDatabase(); if (!db->Configure(param, error)) { @@ -72,7 +73,7 @@ SimpleDatabase::Check(Error &error) const assert(!path.IsNull()); /* Check if the file exists */ - if (!CheckAccess(path, F_OK)) { + 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 */ @@ -95,6 +96,7 @@ SimpleDatabase::Check(Error &error) const return false; } +#ifndef WIN32 /* Check if we can write to the directory */ if (!CheckAccess(dirPath, X_OK | W_OK)) { const int e = errno; @@ -103,7 +105,7 @@ SimpleDatabase::Check(Error &error) const dirPath_utf8.c_str()); return false; } - +#endif return true; } @@ -122,12 +124,14 @@ SimpleDatabase::Check(Error &error) const 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; } @@ -166,7 +170,7 @@ SimpleDatabase::Open(Error &error) #endif if (!Load(error)) { - root->Free(); + delete root; LogError(error); error.Clear(); @@ -186,7 +190,7 @@ SimpleDatabase::Close() assert(root != nullptr); assert(borrowed_song_count == 0); - root->Free(); + delete root; } Song * diff --git a/src/db/SimpleDatabasePlugin.hxx b/src/db/SimpleDatabasePlugin.hxx index dfe981dd8..98cbb96f0 100644 --- a/src/db/SimpleDatabasePlugin.hxx +++ b/src/db/SimpleDatabasePlugin.hxx @@ -53,7 +53,8 @@ public: bool Save(Error &error); - static Database *Create(const config_param ¶m, + static Database *Create(EventLoop &loop, DatabaseListener &listener, + const config_param ¶m, Error &error); virtual bool Open(Error &error) override; diff --git a/src/db/UpnpDatabasePlugin.cxx b/src/db/UpnpDatabasePlugin.cxx new file mode 100644 index 000000000..68a24cb4f --- /dev/null +++ b/src/db/UpnpDatabasePlugin.cxx @@ -0,0 +1,860 @@ +/* + * 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 "UpnpDatabasePlugin.hxx" +#include "upnp/Domain.hxx" +#include "upnp/upnpplib.hxx" +#include "upnp/Discovery.hxx" +#include "upnp/ContentDirectoryService.hxx" +#include "upnp/Directory.hxx" +#include "upnp/Util.hxx" +#include "LazyDatabase.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseSelection.hxx" +#include "DatabaseError.hxx" +#include "PlaylistVector.hxx" +#include "Directory.hxx" +#include "Song.hxx" +#include "ConfigData.hxx" +#include "tag/TagBuilder.hxx" +#include "tag/TagTable.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" +#include "SongFilter.hxx" + +#include <string> +#include <vector> +#include <map> +#include <set> + +#include <assert.h> +#include <string.h> + +static const char *const rootid = "0"; + +static 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 } +}; + +class UpnpDatabase : public Database { + LibUPnP *m_lib; + UPnPDeviceDirectory *m_superdir; + Directory *m_root; + +public: + UpnpDatabase() + : m_lib(0), m_superdir(0), m_root(0) + {} + + static Database *Create(EventLoop &loop, DatabaseListener &listener, + 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 {return 0;} + +protected: + bool Configure(const config_param ¶m, Error &error); + +private: + bool VisitServer(ContentDirectoryService* server, + const std::vector<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(ContentDirectoryService* server, + const char *objid, + const DatabaseSelection &selection, + VisitSong visit_song, + Error &error) const; + + bool SearchSongs(ContentDirectoryService* server, + const char *objid, + const DatabaseSelection &selection, + UPnPDirContent& dirbuf, + Error &error) const; + + bool Namei(ContentDirectoryService* server, + const std::vector<std::string> &vpath, + std::string &oobjid, UPnPDirObject &dirent, + Error &error) const; + + /** + * Take server and objid, return metadata. + */ + bool ReadNode(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(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 new LazyDatabase(db); +} + +bool +UpnpDatabase::Configure(const config_param &, Error &) +{ + return true; +} + +bool +UpnpDatabase::Open(Error &error) +{ + if (m_root) + return true; + + m_lib = LibUPnP::getLibUPnP(error); + if (!m_lib) + return false; + + m_superdir = UPnPDeviceDirectory::getTheDir(); + if (!m_superdir || !m_superdir->ok()) { + error.Set(upnp_domain, "Discovery services startup failed"); + return false; + } + + m_root = Directory::NewRoot(); + // Wait for device answers. This should be consistent with the value set + // in the lib (currently 2) + sleep(2); + return true; +} + +void +UpnpDatabase::Close() +{ + if (m_root) + delete m_root; + // TBD decide what we do with the lib and superdir objects +} + +void +UpnpDatabase::ReturnSong(Song *song) const +{ + assert(song != nullptr); + + song->Free(); +} + +/** + * 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(const std::string &in) +{ + std::string out; + for (auto it = in.begin(); it != in.end(); it++) { + if (*it == '/') { + out += '_'; + } else { + out += *it; + } + } + return out; +} + +// If uri is empty, we use the object's url instead. This happens +// when the target of a Visit() is a song, which only happens when +// "add"ing AFAIK. Visit() calls us with a null uri so that the url +// appropriate for fetching is used instead. +static Song * +upnpItemToSong(const UPnPDirObject &dirent, const char *uri) +{ + if (*uri == 0) + uri = dirent.url.c_str(); + + Song *s = Song::NewFile(uri, nullptr); + + TagBuilder tag; + + if (dirent.duration > 0) + tag.SetTime(dirent.duration); + + tag.AddItem(TAG_TITLE, titleToPathElt(dirent.m_title).c_str()); + + for (auto i = upnp_tags; i->name != nullptr; ++i) { + const char *value = dirent.getprop(i->name); + if (value != nullptr) + tag.AddItem(i->type, value); + } + + s->tag = tag.CommitNew(); + return s; +} + +// Get song info by path. We can receive either the id path, or the titles +// one +Song * +UpnpDatabase::GetSong(const char *uri, Error &error) const +{ + if (!m_superdir || !m_superdir->ok()) { + error.Set(upnp_domain, + "UpnpDatabase::GetSong() superdir is sick"); + return nullptr; + } + + Song *song = nullptr; + auto vpath = stringToTokens(uri, "/", true); + if (vpath.size() >= 2) { + ContentDirectoryService server; + if (!m_superdir->getServer(vpath[0].c_str(), server)) { + error.Set(upnp_domain, "server not found"); + return nullptr; + } + + vpath.erase(vpath.begin()); + UPnPDirObject dirent; + if (vpath[0].compare(rootid)) { + std::string objid; + if (!Namei(&server, vpath, objid, dirent, error)) + return nullptr; + } else { + if (!ReadNode(&server, vpath.back().c_str(), dirent, + error)) + return nullptr; + } + song = upnpItemToSong(dirent, ""); + } + if (song == nullptr) + error.Format(db_domain, DB_NOT_FOUND, "No such song: %s", uri); + + return song; +} + +/** + * Retrieve the value for an MPD tag from an object entry. + */ +static bool +getTagValue(UPnPDirObject& dirent, TagType tag, + std::string &tagvalue) +{ + if (tag == TAG_TITLE) { + if (!dirent.m_title.empty()) { + tagvalue = titleToPathElt(dirent.m_title); + return true; + } + return false; + } + + const char *name = tag_table_lookup(upnp_tags, tag); + if (name == nullptr) + return false; + + const char *value = dirent.getprop(name); + if (value == nullptr) + return false; + + tagvalue = value; + return true; +} + +/** + * Double-quote a string, adding internal backslash escaping. + */ +static void +dquote(std::string &out, const char *in) +{ + out.append(1, '"'); + + for (; *in != 0; ++in) { + switch(*in) { + case '\\': + case '"': + out.append(1, '\\'); + out.append(1, *in); + break; + + default: + out.append(1, *in); + } + } + + out.append(1, '"'); +} + +// Run an UPnP search, according to MPD parameters. Return results as +// UPnP items +bool +UpnpDatabase::SearchSongs(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::set<std::string> searchcaps; + if (!server->getSearchCapabilities(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(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; + Song *s = upnpItemToSong(meta, path); + if (!selection.Match(*s)) + return true; + bool success = visit_song(*s, error); + s->Free(); + return success; +} + +/** + * 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 const std::string +songPath(const std::string &servername, + const std::string &objid) +{ + return servername + "/" + rootid + "/" + objid; +} + +bool +UpnpDatabase::SearchSongs(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 (const auto &dirent : dirbuf.m_items) { + // 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. + std::string path = songPath(server->getFriendlyName(), + dirent.m_id); + //BuildPath(server, dirent, path); + if (!visitSong(dirent, path.c_str(), selection, visit_song, + error)) + return false; + } + + return true; +} + +bool +UpnpDatabase::ReadNode(ContentDirectoryService *server, + const char *objid, UPnPDirObject &dirent, + Error &error) const +{ + UPnPDirContent dirbuf; + if (!server->getMetadata(objid, dirbuf, error)) + return false; + + if (dirbuf.m_containers.size() == 1) { + dirent = dirbuf.m_containers[0]; + } else if (dirbuf.m_items.size() == 1) { + dirent = dirbuf.m_items[0]; + } else { + error.Format(upnp_domain, "Bad resource"); + return false; + } + + return true; +} + +bool +UpnpDatabase::BuildPath(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(); + path = titleToPathElt(dirent.m_title) + (path.empty()? "" : "/" + path); + } + path = std::string(server->getFriendlyName()) + "/" + path; + return true; +} + +// Take server and internal title pathname and return objid and metadata. +bool +UpnpDatabase::Namei(ContentDirectoryService* server, + const std::vector<std::string> &vpath, + std::string &oobjid, UPnPDirObject &odirent, + Error &error) const +{ + oobjid.clear(); + std::string objid(rootid); + + if (vpath.empty()) { + // looking for root info + if (!ReadNode(server, rootid, odirent, error)) + return false; + + oobjid = rootid; + return true; + } + + // Walk the path elements, read each directory and try to find the next one + for (unsigned int i = 0; i < vpath.size(); i++) { + UPnPDirContent dirbuf; + if (!server->readDir(objid.c_str(), dirbuf, error)) + return false; + + bool found = false; + + // Look for the name in the sub-container list + for (auto& dirent : dirbuf.m_containers) { + if (!vpath[i].compare(titleToPathElt(dirent.m_title.c_str()))) { + objid = dirent.m_id; // Next readdir target + found = true; + if (i == vpath.size() - 1) { + // The last element in the path was found and it's + // a container, we're done + oobjid = objid; + odirent = dirent; + return true; + } + break; + } + } + if (found) + continue; + + // Path elt was not a container, look at the items list + for (auto& dirent : dirbuf.m_items) { + if (!vpath[i].compare(titleToPathElt(dirent.m_title.c_str()))) { + // If this is the last path elt, we found the target, + // else it does not exist + if (i == vpath.size() - 1) { + oobjid = objid; + odirent = dirent; + return true; + } else { + return true; + } + } + } + + // Neither container nor item, we're done. + if (!found) + break; + } + + return true; +} + +// vpath is a parsed and writeable version of selection.uri. There is +// really just one path parameter. +bool +UpnpDatabase::VisitServer(ContentDirectoryService* server, + const std::vector<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[0].compare(rootid)) { + if (visit_song) { + UPnPDirObject dirent; + if (!ReadNode(server, vpath.back().c_str(), dirent, + error)) + return false; + + if (!visitSong(dirent, "", selection, + visit_song, error)) + return false; + } + return true; + } + + // Translate the target path into an object id and the associated metadata. + std::string objid; + UPnPDirObject tdirent; + if (!Namei(server, vpath, objid, tdirent, error)) + return false; + + if (objid.empty()) + // Not found, not a fatal error + return true; + + /* 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, objid.c_str(), selection, + visit_song, error); + + if (tdirent.type == UPnPDirObject::Type::ITEM) { + // Target is a song. Not too sure we ever get there actually, maybe + // this is always catched by the special uri test above. + switch (tdirent.item_class) { + case UPnPDirObject::ItemClass::MUSIC: + if (visit_song) + return visitSong(tdirent, "", selection, visit_song, + error); + break; + + 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... */ + } + break; + + case UPnPDirObject::ItemClass::UNKNOWN: + break; + } + + return true; + } + + /* 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(objid.c_str(), dirbuf, error)) + return false; + + if (visit_directory) { + for (auto& dirent : dirbuf.m_containers) { + Directory d((selection.uri + "/" + + titleToPathElt(dirent.m_title)).c_str(), + m_root); + if (!visit_directory(d, error)) + return false; + } + } + + if (visit_song || visit_playlist) { + for (const auto &dirent : dirbuf.m_items) { + switch (dirent.item_class) { + case UPnPDirObject::ItemClass::MUSIC: + if (visit_song) { + /* We identify songs by giving + them a special path. The Id + is enough to fetch them + from the server anyway. */ + + std::string p; + if (!selection.recursive) + p = selection.uri + "/" + + titleToPathElt(dirent.m_title); + + if (!visitSong(dirent, p.c_str(), + selection, visit_song, error)) + return false; + } + + break; + + 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... */ + } + + break; + + case UPnPDirObject::ItemClass::UNKNOWN: + break; + } + } + } + + 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 +{ + std::vector<ContentDirectoryService> servers; + if (!m_superdir || !m_superdir->ok() || + !m_superdir->getDirServices(servers)) { + error.Set(upnp_domain, + "UpnpDatabase::Visit() superdir is sick"); + return false; + } + + auto vpath = stringToTokens(selection.uri, "/", true); + if (vpath.empty()) { + if (!selection.recursive) { + // If the path is empty and recursive is not set, synthetize a + // pseudo-directory from the list of servers. + if (visit_directory) { + for (auto& server : servers) { + Directory d(server.getFriendlyName(), m_root); + if (!visit_directory(d, error)) + return false; + } + } + } else { + // Recursive is set: visit each server + for (auto& server : servers) { + if (!VisitServer(&server, std::vector<std::string>(), 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(vpath[0]); + vpath.erase(vpath.begin()); + + ContentDirectoryService* server = 0; + for (auto& dir : servers) { + if (!servername.compare(dir.getFriendlyName())) { + server = &dir; + break; + } + } + if (server == 0) { + FormatDebug(db_domain, "UpnpDatabase::Visit: server %s not found\n", + vpath[0].c_str()); + return true; + } + 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 (!m_superdir || !m_superdir->ok() || + !m_superdir->getDirServices(servers)) { + error.Set(upnp_domain, + "UpnpDatabase::Visit() superdir is sick"); + return false; + } + + std::set<std::string> values; + for (auto& server : servers) { + UPnPDirContent dirbuf; + if (!SearchSongs(&server, rootid, selection, dirbuf, error)) + return false; + + for (auto &dirent : dirbuf.m_items) { + std::string tagvalue; + if (getTagValue(dirent, tag, tagvalue)) + values.emplace(std::move(tagvalue)); + } + } + + 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/UpnpDatabasePlugin.hxx b/src/db/UpnpDatabasePlugin.hxx new file mode 100644 index 000000000..7fe2dde60 --- /dev/null +++ b/src/db/UpnpDatabasePlugin.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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/upnp/ContentDirectoryService.cxx b/src/db/upnp/ContentDirectoryService.cxx new file mode 100644 index 000000000..b40f55c54 --- /dev/null +++ b/src/db/upnp/ContentDirectoryService.cxx @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "Directory.hxx" +#include "Util.hxx" +#include "upnpplib.hxx" +#include "util/Error.hxx" + +#include <stdio.h> + +#include <upnp/upnp.h> +#include <upnp/upnptools.h> + +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; + } +} + +class DirBResFree { +public: + IXML_Document **rqpp, **rspp; + DirBResFree(IXML_Document** _rqpp, IXML_Document **_rspp) + :rqpp(_rqpp), rspp(_rspp) {} + ~DirBResFree() + { + if (*rqpp) + ixmlDocument_free(*rqpp); + if (*rspp) + ixmlDocument_free(*rspp); + } +}; + +bool +ContentDirectoryService::readDirSlice(const char *objectId, int offset, + int count, UPnPDirContent &dirbuf, + int *didreadp, int *totalp, + Error &error) +{ + LibUPnP *lib = LibUPnP::getLibUPnP(error); + if (lib == nullptr) + return false; + + UpnpClient_Handle hdl = lib->getclh(); + + IXML_Document *request(0); + IXML_Document *response(0); + DirBResFree cleaner(&request, &response); + + // Create request + char ofbuf[100], cntbuf[100]; + sprintf(ofbuf, "%d", offset); + sprintf(cntbuf, "%d", count); + int argcnt = 6; + // Some devices require an empty SortCriteria, else bad params + request = UpnpMakeAction("Browse", m_serviceType.c_str(), argcnt, + "ObjectID", objectId, + "BrowseFlag", "BrowseDirectChildren", + "Filter", "*", + "SortCriteria", "", + "StartingIndex", ofbuf, + "RequestedCount", cntbuf, + nullptr, nullptr); + if (request == nullptr) { + error.Set(upnp_domain, "UpnpMakeAction() failed"); + return false; + } + + int code = UpnpSendAction(hdl, m_actionURL.c_str(), m_serviceType.c_str(), + 0 /*devUDN*/, request, &response); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpSendAction() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + int didread = -1; + std::string tbuf = ixmlwrap::getFirstElementValue(response, "NumberReturned"); + if (!tbuf.empty()) + didread = atoi(tbuf.c_str()); + + if (count == -1 || count == 0) { + // TODO: what's this? + error.Set(upnp_domain, "got -1 or 0 entries"); + return false; + } + + tbuf = ixmlwrap::getFirstElementValue(response, "TotalMatches"); + if (!tbuf.empty()) + *totalp = atoi(tbuf.c_str()); + + tbuf = ixmlwrap::getFirstElementValue(response, "Result"); + + if (!dirbuf.parse(tbuf, error)) + return false; + + *didreadp = didread; + return true; +} + +bool +ContentDirectoryService::readDir(const char *objectId, + UPnPDirContent &dirbuf, + Error &error) +{ + int offset = 0; + int total = 1000;// Updated on first read. + + while (offset < total) { + int count; + if (!readDirSlice(objectId, offset, m_rdreqcnt, dirbuf, + &count, &total, error)) + return false; + + offset += count; + } + + return true; +} + +bool +ContentDirectoryService::search(const char *objectId, + const char *ss, + UPnPDirContent &dirbuf, + Error &error) +{ + LibUPnP *lib = LibUPnP::getLibUPnP(error); + if (lib == nullptr) + return false; + + UpnpClient_Handle hdl = lib->getclh(); + + IXML_Document *request(0); + IXML_Document *response(0); + + int offset = 0; + int total = 1000;// Updated on first read. + + while (offset < total) { + DirBResFree cleaner(&request, &response); + char ofbuf[100]; + sprintf(ofbuf, "%d", offset); + // Create request + int argcnt = 6; + request = UpnpMakeAction("Search", m_serviceType.c_str(), argcnt, + "ContainerID", objectId, + "SearchCriteria", ss, + "Filter", "*", + "SortCriteria", "", + "StartingIndex", ofbuf, + "RequestedCount", "0", // Setting a value here gets twonky into fits + nullptr, nullptr); + if (request == 0) { + error.Set(upnp_domain, "UpnpMakeAction() failed"); + return false; + } + + auto code = UpnpSendAction(hdl, m_actionURL.c_str(), + m_serviceType.c_str(), + 0 /*devUDN*/, request, &response); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpSendAction() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + int count = -1; + std::string tbuf = + ixmlwrap::getFirstElementValue(response, "NumberReturned"); + if (!tbuf.empty()) + count = atoi(tbuf.c_str()); + + if (count == -1 || count == 0) { + // TODO: what's this? + error.Set(upnp_domain, "got -1 or 0 entries"); + return false; + } + + offset += count; + + tbuf = ixmlwrap::getFirstElementValue(response, "TotalMatches"); + if (!tbuf.empty()) + total = atoi(tbuf.c_str()); + + tbuf = ixmlwrap::getFirstElementValue(response, "Result"); + + if (!dirbuf.parse(tbuf, error)) + return false; + } + + return true; +} + +bool +ContentDirectoryService::getSearchCapabilities(std::set<std::string> &result, + Error &error) +{ + LibUPnP *lib = LibUPnP::getLibUPnP(error); + if (lib == nullptr) + return false; + + UpnpClient_Handle hdl = lib->getclh(); + + IXML_Document *request(0); + IXML_Document *response(0); + + request = UpnpMakeAction("GetSearchCapabilities", m_serviceType.c_str(), + 0, + nullptr, nullptr); + if (request == 0) { + error.Set(upnp_domain, "UpnpMakeAction() failed"); + return false; + } + + auto code = UpnpSendAction(hdl, m_actionURL.c_str(), + m_serviceType.c_str(), + 0 /*devUDN*/, request, &response); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpSendAction() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + std::string tbuf = ixmlwrap::getFirstElementValue(response, "SearchCaps"); + + result.clear(); + if (!tbuf.compare("*")) { + result.insert(result.end(), "*"); + } else if (!tbuf.empty()) { + if (!csvToStrings(tbuf, result)) { + error.Set(upnp_domain, "Bad response"); + return false; + } + } + + return true; +} + +bool +ContentDirectoryService::getMetadata(const char *objectId, + UPnPDirContent &dirbuf, + Error &error) +{ + LibUPnP *lib = LibUPnP::getLibUPnP(error); + if (lib == nullptr) + return false; + + UpnpClient_Handle hdl = lib->getclh(); + + IXML_Document *response(0); + + // Create request + int argcnt = 6; + IXML_Document *request = + UpnpMakeAction("Browse", m_serviceType.c_str(), argcnt, + "ObjectID", objectId, + "BrowseFlag", "BrowseMetadata", + "Filter", "*", + "SortCriteria", "", + "StartingIndex", "0", + "RequestedCount", "1", + nullptr, nullptr); + DirBResFree cleaner(&request, &response); + if (request == nullptr) { + error.Set(upnp_domain, "UpnpMakeAction() failed"); + return false; + } + + auto code = UpnpSendAction(hdl, m_actionURL.c_str(), + m_serviceType.c_str(), + 0 /*devUDN*/, request, &response); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpSendAction() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + std::string tbuf = ixmlwrap::getFirstElementValue(response, "Result"); + return dirbuf.parse(tbuf, error); +} diff --git a/src/db/upnp/ContentDirectoryService.hxx b/src/db/upnp/ContentDirectoryService.hxx new file mode 100644 index 000000000..8fc28c382 --- /dev/null +++ b/src/db/upnp/ContentDirectoryService.hxx @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License 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 <string> +#include <set> + +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; + + /** 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(const char *objectId, UPnPDirContent &dirbuf, + Error &error); + + bool readDirSlice(const char *objectId, int offset, + int count, UPnPDirContent& dirbuf, + int *didread, int *total, + Error &error); + + /** 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(const char *objectId, const char *searchstring, + UPnPDirContent &dirbuf, + Error &error); + + /** 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(const char *objectId, UPnPDirContent &dirbuf, + Error &error); + + /** 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(std::set<std::string> &result, + Error &error); + + /** 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/db/upnp/Device.cxx b/src/db/upnp/Device.cxx new file mode 100644 index 000000000..37d68c232 --- /dev/null +++ b/src/db/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 "Expat.hxx" +#include "Log.hxx" +#include "util/Error.hxx" + +#include <stdlib.h> + +#include <string.h> + +/** + * An XML parser which constructs an UPnP device object from the + * device descriptor. + */ +class UPnPDeviceParser final : public CommonExpatParser { + UPnPDevice &m_device; + std::vector<std::string> m_path; + UPnPService m_tservice; + +public: + UPnPDeviceParser(UPnPDevice& device) + :m_device(device) {} + +protected: + virtual void StartElement(const XML_Char *name, const XML_Char **) { + m_path.push_back(name); + } + + virtual void EndElement(const XML_Char *name) { + if (!strcmp(name, "service")) { + m_device.services.push_back(m_tservice); + m_tservice.clear(); + } + + m_path.pop_back(); + } + + virtual void CharacterData(const XML_Char *s, int len) { + std::string str(s, len); + trimstring(str); + switch (m_path.back()[0]) { + case 'c': + if (!m_path.back().compare("controlURL")) + m_tservice.controlURL += str; + break; + case 'd': + if (!m_path.back().compare("deviceType")) + m_device.deviceType += str; + break; + case 'e': + if (!m_path.back().compare("eventSubURL")) + m_tservice.eventSubURL += str; + break; + case 'f': + if (!m_path.back().compare("friendlyName")) + m_device.friendlyName += str; + break; + case 'm': + if (!m_path.back().compare("manufacturer")) + m_device.manufacturer += str; + else if (!m_path.back().compare("modelName")) + m_device.modelName += str; + break; + case 's': + if (!m_path.back().compare("serviceType")) + m_tservice.serviceType = str; + else if (!m_path.back().compare("serviceId")) + m_tservice.serviceId += str; + case 'S': + if (!m_path.back().compare("SCPDURL")) + m_tservice.SCPDURL = str; + break; + case 'U': + if (!m_path.back().compare("UDN")) + m_device.UDN = str; + else if (!m_path.back().compare("URLBase")) + m_device.URLBase += str; + break; + } + } +}; + +UPnPDevice::UPnPDevice(const std::string &url, const std::string &description) + :ok(false) +{ + UPnPDeviceParser mparser(*this); + Error error; + if (!mparser.Parse(description.data(), description.length(), true, + error)) { + // TODO: pass Error to caller + LogError(error); + return; + } + + 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); + } + } + } + ok = true; +} diff --git a/src/db/upnp/Device.hxx b/src/db/upnp/Device.hxx new file mode 100644 index 000000000..53e1c4964 --- /dev/null +++ b/src/db/upnp/Device.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 _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; + // Unique Id inside device: e.g here THE ConnectionManager + std::string serviceId; // e.g. urn:upnp-org:serviceId:ConnectionManager + std::string SCPDURL; // Service description URL. e.g.: cm.xml + std::string controlURL; // e.g.: /upnp/control/cm + std::string eventSubURL; // e.g.: /upnp/event/cm + + void clear() + { + serviceType.clear(); + serviceId.clear(); + SCPDURL.clear(); + controlURL.clear(); + eventSubURL.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: + bool ok; + // 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; + + /** Build device from xml description downloaded from discovery + * @param url where the description came from + * @param description the xml device description + */ + UPnPDevice(const std::string &url, const std::string &description); + + UPnPDevice() : ok(false) {} +}; + +typedef std::vector<UPnPService>::iterator DevServIt; + +#endif /* _UPNPDEV_HXX_INCLUDED_ */ diff --git a/src/db/upnp/Directory.cxx b/src/db/upnp/Directory.cxx new file mode 100644 index 000000000..a42719e4d --- /dev/null +++ b/src/db/upnp/Directory.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. + */ + +#include "config.h" +#include "Directory.hxx" +#include "Util.hxx" +#include "Expat.hxx" + +#include <string> +#include <vector> + +#include <string.h> + +static const char *const upnptags[] = { + "upnp:artist", + "upnp:album", + "upnp:genre", + "upnp:originalTrackNumber", + nullptr, +}; + +gcc_pure +static UPnPDirObject::ItemClass +ParseItemClass(const char *name) +{ + if (strcmp(name, "object.item.audioItem.musicTrack") == 0) + return UPnPDirObject::ItemClass::MUSIC; + else if (strcmp(name, "object.item.playlistItem") == 0) + return UPnPDirObject::ItemClass::PLAYLIST; + else + return UPnPDirObject::ItemClass::UNKNOWN; +} + +gcc_pure +static int +ParseDuration(const std::string &duration) +{ + const auto v = stringToTokens(duration, ":"); + if (v.size() != 3) + return 0; + return atoi(v[0].c_str())*3600 + atoi(v[1].c_str())*60 + atoi(v[2].c_str()); +} + +/** + * An XML parser which builds directory contents from DIDL lite input. + */ +class UPnPDirParser final : public CommonExpatParser { + std::vector<std::string> m_path; + UPnPDirObject m_tobj; + +public: + UPnPDirParser(UPnPDirContent& dir) + :m_dir(dir) + { + } + UPnPDirContent& m_dir; + +protected: + virtual void StartElement(const XML_Char *name, const XML_Char **attrs) + { + m_path.push_back(name); + + 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; + + const char *item_class_name = + GetAttribute(attrs, "upnp:class"); + if (item_class_name != nullptr) + m_tobj.item_class = + ParseItemClass(item_class_name); + } + 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) + m_tobj.duration = ParseDuration(duration); + } + + break; + } + } + + bool checkobjok() { + if (m_tobj.m_id.empty() || m_tobj.m_pid.empty() || + m_tobj.m_title.empty() || + m_tobj.item_class == UPnPDirObject::ItemClass::UNKNOWN) + return false; + + return true; + } + + virtual void EndElement(const XML_Char *name) + { + if (!strcmp(name, "container")) { + if (checkobjok()) { + m_dir.m_containers.push_back(m_tobj); + } + } else if (!strcmp(name, "item")) { + if (checkobjok()) { + m_dir.m_items.push_back(m_tobj); + } + } + + m_path.pop_back(); + } + + virtual void CharacterData(const XML_Char *s, int len) + { + std::string str(s, len); + trimstring(str); + switch (m_path.back()[0]) { + case 'd': + if (!m_path.back().compare("dc:title")) + m_tobj.m_title += str; + break; + case 'r': + if (!m_path.back().compare("res")) { + m_tobj.url = str; + } + break; + case 'u': + for (auto i = upnptags; *i != nullptr; ++i) + if (!m_path.back().compare(*i)) + m_tobj.m_props[*i] += str; + break; + } + } +}; + +bool +UPnPDirContent::parse(const std::string &input, Error &error) +{ + UPnPDirParser parser(*this); + return parser.Parse(input.data(), input.length(), true, error); +} diff --git a/src/db/upnp/Directory.hxx b/src/db/upnp/Directory.hxx new file mode 100644 index 000000000..55dc09c71 --- /dev/null +++ b/src/db/upnp/Directory.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_UPNP_DIRECTORY_HXX +#define MPD_UPNP_DIRECTORY_HXX + +#include "Object.hxx" + +#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> m_containers; + std::vector<UPnPDirObject> m_items; + + /** + * 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 std::string &didltext, Error &error); +}; + +#endif /* _UPNPDIRCONTENT_H_X_INCLUDED_ */ diff --git a/src/db/upnp/Discovery.cxx b/src/db/upnp/Discovery.cxx new file mode 100644 index 000000000..a94f29d8c --- /dev/null +++ b/src/db/upnp/Discovery.cxx @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "Device.hxx" +#include "Domain.hxx" +#include "ContentDirectoryService.hxx" +#include "WorkQueue.hxx" +#include "upnpplib.hxx" +#include "thread/Mutex.hxx" + +#include <upnp/upnp.h> +#include <upnp/upnptools.h> + +#include <string.h> + +#include <map> + +// The service type string we are looking for. +static const char *const 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) +{ + const size_t sz = strlen(ContentDirectorySType) - 2; + return memcmp(ContentDirectorySType, st, sz) == 0; +} + +// The type of device we're asking for in search +static const char *const MediaServerDType = "urn:schemas-upnp-org:device:MediaServer:1"; + +gcc_pure +static bool +isMSDevice(const char *st) +{ + const size_t sz = strlen(MediaServerDType) - 2; + return memcmp(MediaServerDType, st, sz) == 0; +} + +/** + * Each appropriate discovery event (executing in a libupnp thread + * context) queues the following task object for processing by the + * discovery thread. + */ +struct DiscoveredTask { + bool alive; + std::string url; + std::string deviceId; + int expires; // Seconds valid + + DiscoveredTask(bool _alive, const Upnp_Discovery *disco) + : alive(_alive), url(disco->Location), + deviceId(disco->DeviceId), + expires(disco->Expires) {} + +}; +static WorkQueue<DiscoveredTask *> discoveredQueue("DiscoveredQueue"); + +// Descriptor for one device having a Content Directory service found +// on the network. +class ContentDirectoryDescriptor { +public: + ContentDirectoryDescriptor(const std::string &url, + const std::string &description, + time_t last, int exp) + :device(url, description), last_seen(last), expires(exp+20) {} + UPnPDevice device; + time_t last_seen; + int expires; // seconds valid +}; + +// A ContentDirectoryPool holds the characteristics of the servers +// currently on the network. +// The map is referenced by deviceId (==UDN) +// The class is instanciated as a static (unenforced) singleton. +class ContentDirectoryPool { +public: + Mutex m_mutex; + std::map<std::string, ContentDirectoryDescriptor> m_directories; +}; + +static ContentDirectoryPool contentDirectories; + +// Worker routine for the discovery queue. Get messages about devices +// appearing and disappearing, and update the directory pool +// accordingly. +static void * +discoExplorer(void *) +{ + for (;;) { + DiscoveredTask *tsk = 0; + size_t qsz; + if (!discoveredQueue.take(&tsk, &qsz)) { + discoveredQueue.workerExit(); + return (void*)1; + } + + const ScopeLock protect(contentDirectories.m_mutex); + if (!tsk->alive) { + // Device signals it is going off. + auto it = contentDirectories.m_directories.find(tsk->deviceId); + if (it != contentDirectories.m_directories.end()) { + contentDirectories.m_directories.erase(it); + } + } else { + // 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; + } + std::string sdesc(buf); + + // Update or insert the device + ContentDirectoryDescriptor d(tsk->url, sdesc, + time(0), tsk->expires); + if (!d.device.ok) { + continue; + } + + auto e = contentDirectories.m_directories.emplace(tsk->deviceId, d); + if (!e.second) + e.first->second = d; + } + delete tsk; + } +} + +// 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) +// It seems that this can get called by several threads. We have a +// mutex just for clarifying the message printing, the workqueue is +// mt-safe of course. +static int +cluCallBack(Upnp_EventType et, void *evp, void *) +{ + static Mutex cblock; + const ScopeLock protect(cblock); + + switch (et) { + case UPNP_DISCOVERY_SEARCH_RESULT: + case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE: + { + Upnp_Discovery *disco = (Upnp_Discovery *)evp; + if (isMSDevice(disco->DeviceType) || + isCDService(disco->ServiceType)) { + DiscoveredTask *tp = new DiscoveredTask(1, disco); + if (discoveredQueue.put(tp)) + return UPNP_E_FINISH; + } + break; + } + + case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE: + { + Upnp_Discovery *disco = (Upnp_Discovery *)evp; + + if (isMSDevice(disco->DeviceType) || + isCDService(disco->ServiceType)) { + DiscoveredTask *tp = new DiscoveredTask(0, disco); + if (discoveredQueue.put(tp)) + return UPNP_E_FINISH; + } + break; + } + + default: + // Ignore other events for now + break; + } + + return UPNP_E_SUCCESS; +} + +void +UPnPDeviceDirectory::expireDevices() +{ + const ScopeLock protect(contentDirectories.m_mutex); + time_t now = time(0); + bool didsomething = false; + + for (auto it = contentDirectories.m_directories.begin(); + it != contentDirectories.m_directories.end();) { + if (now - it->second.last_seen > it->second.expires) { + it = contentDirectories.m_directories.erase(it); + didsomething = true; + } else { + it++; + } + } + + if (didsomething) + search(); +} + +UPnPDeviceDirectory::UPnPDeviceDirectory() + :m_searchTimeout(2), m_lastSearch(0) +{ + if (!discoveredQueue.start(1, discoExplorer, 0)) { + error.Set(upnp_domain, "Discover work queue start failed"); + return; + } + + LibUPnP *lib = LibUPnP::getLibUPnP(error); + if (lib == nullptr) + return; + + lib->registerHandler(UPNP_DISCOVERY_SEARCH_RESULT, cluCallBack, this); + lib->registerHandler(UPNP_DISCOVERY_ADVERTISEMENT_ALIVE, + cluCallBack, this); + lib->registerHandler(UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE, + cluCallBack, this); + + search(); +} + +bool +UPnPDeviceDirectory::search() +{ + time_t now = time(0); + if (now - m_lastSearch < 10) + return true; + m_lastSearch = now; + + LibUPnP *lib = LibUPnP::getLibUPnP(error); + if (lib == nullptr) + return false; + + // We search both for device and service just in case. + int code = UpnpSearchAsync(lib->getclh(), m_searchTimeout, + ContentDirectorySType, lib); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpSearchAsync() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + code = UpnpSearchAsync(lib->getclh(), m_searchTimeout, + MediaServerDType, lib); + if (code != UPNP_E_SUCCESS) { + error.Format(upnp_domain, code, + "UpnpSearchAsync() failed: %s", + UpnpGetErrorMessage(code)); + return false; + } + + return true; +} + +UPnPDeviceDirectory *UPnPDeviceDirectory::getTheDir() +{ + // TODO: elimate static variable + static UPnPDeviceDirectory *theDevDir; + if (theDevDir == nullptr) + theDevDir = new UPnPDeviceDirectory(); + if (theDevDir && !theDevDir->ok()) + return 0; + return theDevDir; +} + +bool +UPnPDeviceDirectory::getDirServices(std::vector<ContentDirectoryService> &out) +{ + if (!ok()) + return false; + + // Has locking, do it before our own lock + expireDevices(); + + const ScopeLock protect(contentDirectories.m_mutex); + + for (auto dit = contentDirectories.m_directories.begin(); + dit != contentDirectories.m_directories.end(); dit++) { + for (const auto &service : dit->second.device.services) { + if (isCDService(service.serviceType.c_str())) { + out.emplace_back(dit->second.device, service); + } + } + } + + return true; +} + +bool +UPnPDeviceDirectory::getServer(const char *friendlyName, + ContentDirectoryService &server) +{ + std::vector<ContentDirectoryService> ds; + if (!getDirServices(ds)) { + return false; + } + + for (const auto &i : ds) { + if (strcmp(friendlyName, i.getFriendlyName()) == 0) { + server = i; + return true; + } + } + + return false; +} diff --git a/src/db/upnp/Discovery.hxx b/src/db/upnp/Discovery.hxx new file mode 100644 index 000000000..899ac83ab --- /dev/null +++ b/src/db/upnp/Discovery.hxx @@ -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. + */ + +#ifndef _UPNPPDISC_H_X_INCLUDED_ +#define _UPNPPDISC_H_X_INCLUDED_ + +#include "util/Error.hxx" + +#include <vector> + +#include <time.h> + +class ContentDirectoryService; + +/** + * 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 { + Error error; + + /** + * 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; + + time_t m_lastSearch; + + UPnPDeviceDirectory(); +public: + UPnPDeviceDirectory(const UPnPDeviceDirectory &) = delete; + UPnPDeviceDirectory& operator=(const UPnPDeviceDirectory &) = delete; + + /** This class is a singleton. Get the instance here */ + static UPnPDeviceDirectory *getTheDir(); + + /** Retrieve the directory services currently seen on the network */ + bool getDirServices(std::vector<ContentDirectoryService> &); + + /** + * Get server by friendly name. It's a bit wasteful to copy + * all servers for this, we could directly walk the list. Otoh + * there isn't going to be millions... + */ + bool getServer(const char *friendlyName, + ContentDirectoryService &server); + + /** My health */ + bool ok() const { + return !error.IsDefined(); + } + + /** My diagnostic if health is bad */ + const Error &GetError() const { + return error; + } + +private: + bool search(); + + /** + * 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. + */ + void expireDevices(); +}; + + +#endif /* _UPNPPDISC_H_X_INCLUDED_ */ diff --git a/src/db/upnp/Domain.cxx b/src/db/upnp/Domain.cxx new file mode 100644 index 000000000..12984bd19 --- /dev/null +++ b/src/db/upnp/Domain.cxx @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "Domain.hxx" +#include "util/Domain.hxx" + +const Domain upnp_domain("upnp"); diff --git a/src/db/upnp/Domain.hxx b/src/db/upnp/Domain.hxx new file mode 100644 index 000000000..e249aa5dc --- /dev/null +++ b/src/db/upnp/Domain.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPNP_DOMAIN_HXX +#define MPD_UPNP_DOMAIN_HXX + +class Domain; + +extern const Domain upnp_domain; + +#endif diff --git a/src/db/upnp/Object.hxx b/src/db/upnp/Object.hxx new file mode 100644 index 000000000..d158ab6f7 --- /dev/null +++ b/src/db/upnp/Object.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_UPNP_OBJECT_HXX +#define MPD_UPNP_OBJECT_HXX + +#include <string> +#include <map> + +/** + * 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; + std::string m_title; // dc:title. Directory name for a container. + Type type; + ItemClass item_class; + // Properties as gathered from the XML document (url, artist, etc.) + // The map keys are the XML tag or attribute names. + std::map<std::string, std::string> m_props; + + /** + * Song duration in seconds. 0 if unknown. + */ + int duration; + + /** Get named property + * @param property name (e.g. upnp:artist, upnp:album, + * upnp:originalTrackNumber, upnp:genre). Use m_title instead + * for dc:title. + * @param[out] value + * @return true if found. + */ + const char *getprop(const char *name) const { + auto it = m_props.find(name); + if (it == m_props.end()) + return nullptr; + return it->second.c_str(); + } + + void clear() + { + m_id.clear(); + m_pid.clear(); + url.clear(); + m_title.clear(); + type = Type::UNKNOWN; + item_class = ItemClass::UNKNOWN; + m_props.clear(); + duration = -1; + } +}; + +#endif /* _UPNPDIRCONTENT_H_X_INCLUDED_ */ diff --git a/src/db/upnp/Util.cxx b/src/db/upnp/Util.cxx new file mode 100644 index 000000000..afe68e619 --- /dev/null +++ b/src/db/upnp/Util.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. + */ + +#include "Util.hxx" + +#include <string> +#include <map> +#include <vector> +#include <set> + +#include <upnp/ixml.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) +{ + std::string out(s1); + if (out[out.size()-1] == '/') { + if (s2[0] == '/') + out.erase(out.size()-1); + } else { + if (s2[0] != '/') + out.push_back('/'); + } + out += s2; + return out; +} + +static void +path_catslash(std::string &s) +{ + if (s.empty() || s[s.length() - 1] != '/') + s += '/'; +} + +std::string +path_getfather(const std::string &s) +{ + std::string father = s; + + // ?? + if (father.empty()) + return "./"; + + if (father[father.length() - 1] == '/') { + // 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::vector<std::string> +stringToTokens(const std::string &str, + const char *delims, bool skipinit) +{ + std::vector<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.push_back(str.substr(startPos)); + break; + } else if (pos == startPos) { + // Dont' push empty tokens after first + if (tokens.empty()) + tokens.push_back(std::string()); + startPos = ++pos; + } else { + tokens.push_back(str.substr(startPos, pos - startPos)); + startPos = ++pos; + } + } + + return tokens; +} + +template <class T> +bool +csvToStrings(const std::string &s, T &tokens) +{ + std::string current; + tokens.clear(); + enum states {TOKEN, ESCAPE}; + states state = TOKEN; + for (unsigned int i = 0; i < s.length(); i++) { + switch (s[i]) { + case ',': + switch(state) { + case TOKEN: + tokens.insert(tokens.end(), current); + current.clear(); + continue; + case ESCAPE: + current += ','; + state = TOKEN; + continue; + } + break; + case '\\': + switch(state) { + case TOKEN: + state=ESCAPE; + continue; + case ESCAPE: + current += '\\'; + state = TOKEN; + continue; + } + break; + + default: + switch(state) { + case ESCAPE: + state = TOKEN; + break; + case TOKEN: + break; + } + current += s[i]; + } + } + switch(state) { + case TOKEN: + tokens.insert(tokens.end(), current); + break; + case ESCAPE: + return false; + } + return true; +} + +//template bool csvToStrings<list<string> >(const string &, list<string> &); +template bool csvToStrings<std::vector<std::string> >(const std::string &, std::vector<std::string> &); +template bool csvToStrings<std::set<std::string> >(const std::string &, std::set<std::string> &); diff --git a/src/db/upnp/Util.hxx b/src/db/upnp/Util.hxx new file mode 100644 index 000000000..08fe5f497 --- /dev/null +++ b/src/db/upnp/Util.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_UPNP_UTIL_HXX +#define MPD_UPNP_UTIL_HXX + +#include "Compiler.h" + +#include <string> +#include <vector> + +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::vector<std::string> +stringToTokens(const std::string &str, + const char *delims = "/", bool skipinit = true); + +template <class T> +bool csvToStrings(const std::string& s, T &tokens); + +#endif /* _UPNPP_H_X_INCLUDED_ */ diff --git a/src/db/upnp/WorkQueue.hxx b/src/db/upnp/WorkQueue.hxx new file mode 100644 index 000000000..851f81837 --- /dev/null +++ b/src/db/upnp/WorkQueue.hxx @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License 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 <pthread.h> +#include <time.h> + +#include <string> +#include <queue> +#include <unordered_map> + +//#include "debuglog.h" +#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 { + /** + * Store per-worker-thread data. Just an initialized timespec, + * and used at the moment. + */ + class WQTData { + public: + WQTData() {wstart.tv_sec = 0; wstart.tv_nsec = 0;} + struct timespec wstart; + }; + + // Configuration + std::string m_name; + size_t m_high; + size_t m_low; + + // Status + // Worker threads having called exit + unsigned int m_workers_exited; + bool m_ok; + + // Per-thread data. The data is not used currently, this could be + // a set<pthread_t> + std::unordered_map<pthread_t, WQTData> m_worker_threads; + + // Synchronization + std::queue<T> m_queue; + Cond m_ccond; + Cond m_wcond; + Mutex m_mutex; + // Client/Worker threads currently waiting for a job + unsigned int m_clients_waiting; + unsigned int m_workers_waiting; + + // Statistics + unsigned int m_tottasks; + unsigned int m_nowake; + unsigned int m_workersleeps; + unsigned int m_clientsleeps; + +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, size_t hi = 0, size_t lo = 1) + :m_name(name), m_high(hi), m_low(lo), + m_workers_exited(0), + m_ok(true), + m_clients_waiting(0), m_workers_waiting(0), + m_tottasks(0), m_nowake(0), m_workersleeps(0), m_clientsleeps(0) + { + } + + ~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(int nworkers, void *(*workproc)(void *), void *arg) + { + const ScopeLock protect(m_mutex); + + for (int i = 0; i < nworkers; i++) { + int err; + pthread_t thr; + if ((err = pthread_create(&thr, 0, workproc, arg))) { + LOGERR(("WorkQueue:%s: pthread_create failed, err %d\n", + m_name.c_str(), err)); + return false; + } + m_worker_threads.insert(std::make_pair(thr, WQTData())); + } + return true; + } + + /** Add item to work queue, called from client. + * + * Sleeps if there are already too many. + */ + bool put(T t) + { + const ScopeLock protect(m_mutex); + + if (!ok()) { + LOGERR(("WorkQueue::put:%s: !ok or mutex_lock failed\n", + m_name.c_str())); + return false; + } + + while (ok() && m_high > 0 && m_queue.size() >= m_high) { + m_clientsleeps++; + // Keep the order: we test ok() AFTER the sleep... + m_clients_waiting++; + m_ccond.wait(m_mutex); + if (!ok()) { + m_clients_waiting--; + return false; + } + m_clients_waiting--; + } + + m_queue.push(t); + if (m_workers_waiting > 0) { + // Just wake one worker, there is only one new task. + m_wcond.signal(); + } else { + m_nowake++; + } + + return true; + } + + /** + * Wait until the queue is inactive. Called from client. + * + * Waits until the task queue is empty and the workers are all + * back sleeping. Used by the client to wait for all current work + * to be completed, when it needs to perform work that couldn't be + * done in parallel with the worker's tasks, or before shutting + * down. Work can be resumed after calling this. Note that the + * only thread which can call it safely is the client just above + * (which can control the task flow), else there could be + * tasks in the intermediate queues. + * To rephrase: there is no warranty on return that the queue is actually + * idle EXCEPT if the caller knows that no jobs are still being created. + * It would be possible to transform this into a safe call if some kind + * of suspend condition was set on the queue by waitIdle(), to be reset by + * some kind of "resume" call. Not currently the case. + */ + bool waitIdle() + { + const ScopeLock protect(m_mutex); + + if (!ok()) { + LOGERR(("WorkQueue::waitIdle:%s: not ok or can't lock\n", + m_name.c_str())); + return false; + } + + // We're done when the queue is empty AND all workers are back + // waiting for a task. + while (ok() && (m_queue.size() > 0 || + m_workers_waiting != m_worker_threads.size())) { + m_clients_waiting++; + m_ccond.wait(m_mutex); + m_clients_waiting--; + } + + return ok(); + } + + + /** Tell the workers to exit, and wait for them. + * + * Does not bother about tasks possibly remaining on the queue, so + * should be called after waitIdle() for an orderly shutdown. + */ + void setTerminateAndWait() + { + const ScopeLock protect(m_mutex); + + if (m_worker_threads.empty()) + // Already called ? + return; + + // Wait for all worker threads to have called workerExit() + m_ok = false; + while (m_workers_exited < m_worker_threads.size()) { + m_wcond.broadcast(); + m_clients_waiting++; + m_ccond.wait(m_mutex); + m_clients_waiting--; + } + + // Perform the thread joins and compute overall status + // Workers return (void*)1 if ok + while (!m_worker_threads.empty()) { + void *status; + auto it = m_worker_threads.begin(); + pthread_join(it->first, &status); + m_worker_threads.erase(it); + } + + // Reset to start state. + m_workers_exited = m_clients_waiting = m_workers_waiting = + m_tottasks = m_nowake = m_workersleeps = m_clientsleeps = 0; + m_ok = true; + } + + /** 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, size_t *szp = 0) + { + const ScopeLock protect(m_mutex); + + if (!ok()) { + return false; + } + + while (ok() && m_queue.size() < m_low) { + m_workersleeps++; + m_workers_waiting++; + if (m_queue.empty()) + m_ccond.broadcast(); + m_wcond.wait(m_mutex); + if (!ok()) { + // !ok is a normal condition when shutting down + if (ok()) { + LOGERR(("WorkQueue::take:%s: cond_wait failed or !ok\n", + m_name.c_str())); + } + m_workers_waiting--; + return false; + } + m_workers_waiting--; + } + + m_tottasks++; + *tp = m_queue.front(); + if (szp) + *szp = m_queue.size(); + m_queue.pop(); + if (m_clients_waiting > 0) { + // No reason to wake up more than one client thread + m_ccond.signal(); + } else { + m_nowake++; + } + 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 m_ok is set to + * false by the shutdown code anyway). The thread must return/exit + * immediately after calling this. + */ + void workerExit() + { + const ScopeLock protect(m_mutex); + + m_workers_exited++; + m_ok = false; + m_ccond.broadcast(); + } + + size_t qsize() + { + const ScopeLock protect(m_mutex); + + size_t sz = m_queue.size(); + return sz; + } + +private: + bool ok() + { + return m_ok && m_workers_exited == 0 && !m_worker_threads.empty(); + } +}; + +#endif /* _WORKQUEUE_H_INCLUDED_ */ diff --git a/src/db/upnp/ixmlwrap.cxx b/src/db/upnp/ixmlwrap.cxx new file mode 100644 index 000000000..247c36d5d --- /dev/null +++ b/src/db/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 { + +std::string +getFirstElementValue(IXML_Document *doc, const char *name) +{ + std::string ret; + 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); + } + } + } + + if(nodes) + ixmlNodeList_free(nodes); + return ret; +} + +} diff --git a/src/db/upnp/ixmlwrap.hxx b/src/db/upnp/ixmlwrap.hxx new file mode 100644 index 000000000..8677d691a --- /dev/null +++ b/src/db/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 an empty string if the element does not + * contain a text node + */ + std::string getFirstElementValue(IXML_Document *doc, + const char *name); + +}; + +#endif /* _IXMLWRAP_H_INCLUDED_ */ diff --git a/src/db/upnp/upnpplib.cxx b/src/db/upnp/upnpplib.cxx new file mode 100644 index 000000000..d7c65ccf4 --- /dev/null +++ b/src/db/upnp/upnpplib.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 "upnpplib.hxx" +#include "Domain.hxx" +#include "Log.hxx" + +#include <upnp/ixml.h> +#include <upnp/upnptools.h> + +static LibUPnP *theLib; + +LibUPnP * +LibUPnP::getLibUPnP(Error &error) +{ + if (theLib == nullptr) + theLib = new LibUPnP; + + if (!theLib->ok()) { + error.Set(theLib->GetInitError()); + return nullptr; + } + + return theLib; +} + +LibUPnP::LibUPnP() +{ + auto code = UpnpInit(0, 0); + if (code != UPNP_E_SUCCESS) { + init_error.Format(upnp_domain, code, + "UpnpInit() failed: %s", + UpnpGetErrorMessage(code)); + return; + } + + UpnpSetMaxContentLength(2000*1024); + + code = UpnpRegisterClient(o_callback, (void *)this, &m_clh); + if (code != UPNP_E_SUCCESS) { + init_error.Format(upnp_domain, code, + "UpnpRegisterClient() failed: %s", + UpnpGetErrorMessage(code)); + return; + } + + // Servers sometimes make error (e.g.: minidlna returns bad utf-8) + ixmlRelaxParser(1); +} + +void +LibUPnP::registerHandler(Upnp_EventType et, Upnp_FunPtr handler, void *cookie) +{ + if (handler == nullptr) + m_handlers.erase(et); + else + m_handlers.emplace(et, Handler(handler, cookie)); +} + +int +LibUPnP::o_callback(Upnp_EventType et, void* evp, void* cookie) +{ + LibUPnP *ulib = (LibUPnP *)cookie; + if (ulib == nullptr) { + // Because the asyncsearch calls uses a null cookie. + ulib = theLib; + } + + auto it = ulib->m_handlers.find(et); + if (it != ulib->m_handlers.end()) { + (it->second.handler)(et, evp, it->second.cookie); + } + return UPNP_E_SUCCESS; +} + +LibUPnP::~LibUPnP() +{ + int error = UpnpFinish(); + if (error != UPNP_E_SUCCESS) + FormatError(upnp_domain, "UpnpFinish() failed: %s", + UpnpGetErrorMessage(error)); +} diff --git a/src/db/upnp/upnpplib.hxx b/src/db/upnp/upnpplib.hxx new file mode 100644 index 000000000..b6ce80212 --- /dev/null +++ b/src/db/upnp/upnpplib.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 _LIBUPNP_H_X_INCLUDED_ +#define _LIBUPNP_H_X_INCLUDED_ + +#include "util/Error.hxx" + +#include <map> + +#include <upnp/upnp.h> + +/** Our link to libupnp. Initialize and keep the handle around */ +class LibUPnP { + // A Handler object records the data from registerHandler. + class Handler { + public: + Handler(Upnp_FunPtr h, void *c) + : handler(h), cookie(c) {} + Upnp_FunPtr handler; + void *cookie; + }; + + Error init_error; + UpnpClient_Handle m_clh; + std::map<Upnp_EventType, Handler> m_handlers; + + LibUPnP(); + + LibUPnP(const LibUPnP &) = delete; + LibUPnP &operator=(const LibUPnP &) = delete; + + static int o_callback(Upnp_EventType, void *, void *); + +public: + ~LibUPnP(); + + /** Retrieve the singleton LibUPnP object */ + static LibUPnP *getLibUPnP(Error &error); + + /** Check state after initialization */ + bool ok() const + { + return !init_error.IsDefined(); + } + + /** Retrieve init error if state not ok */ + const Error &GetInitError() const { + return init_error; + } + + void registerHandler(Upnp_EventType et, Upnp_FunPtr handler, void *cookie); + + UpnpClient_Handle getclh() + { + return m_clh; + } +}; + +#endif /* _LIBUPNP.H_X_INCLUDED_ */ diff --git a/src/decoder/DsdLib.cxx b/src/decoder/DsdLib.cxx index 67cc7e945..b0ba73126 100644 --- a/src/decoder/DsdLib.cxx +++ b/src/decoder/DsdLib.cxx @@ -27,8 +27,6 @@ #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" diff --git a/src/decoder/DsdiffDecoderPlugin.cxx b/src/decoder/DsdiffDecoderPlugin.cxx index a3c0149b9..bc9fc2353 100644 --- a/src/decoder/DsdiffDecoderPlugin.cxx +++ b/src/decoder/DsdiffDecoderPlugin.cxx @@ -38,9 +38,6 @@ #include "DsdLib.hxx" #include "Log.hxx" -#include <unistd.h> -#include <stdio.h> /* for SEEK_SET, SEEK_CUR */ - struct DsdiffHeader { DsdId id; DffDsdUint64 size; diff --git a/src/decoder/DsfDecoderPlugin.cxx b/src/decoder/DsfDecoderPlugin.cxx index 5ef94e647..ab96e8ce6 100644 --- a/src/decoder/DsfDecoderPlugin.cxx +++ b/src/decoder/DsfDecoderPlugin.cxx @@ -39,9 +39,6 @@ #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; diff --git a/src/decoder/FaadDecoderPlugin.cxx b/src/decoder/FaadDecoderPlugin.cxx index 9fd20167d..a37bc88bf 100644 --- a/src/decoder/FaadDecoderPlugin.cxx +++ b/src/decoder/FaadDecoderPlugin.cxx @@ -24,6 +24,7 @@ #include "InputStream.hxx" #include "CheckAudioFormat.hxx" #include "tag/TagHandler.hxx" +#include "util/ConstBuffer.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" @@ -66,16 +67,11 @@ adts_check_frame(const unsigned char *data) 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) { + auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer)); + if (data.size < 8) { /* not enough data yet */ - ret = decoder_buffer_fill(buffer); - if (!ret) + if (!decoder_buffer_fill(buffer)) /* failed */ return 0; @@ -83,21 +79,22 @@ adts_find_frame(DecoderBuffer *buffer) } /* find the 0xff marker */ - const uint8_t *p = (const uint8_t *)memchr(data, 0xff, length); + const uint8_t *p = (const uint8_t *) + memchr(data.data, 0xff, data.size); if (p == nullptr) { /* no marker - discard the buffer */ - decoder_buffer_consume(buffer, length); + decoder_buffer_clear(buffer); continue; } - if (p > data) { + if (p > data.data) { /* discard data before 0xff */ - decoder_buffer_consume(buffer, p - data); + decoder_buffer_consume(buffer, p - data.data); continue; } /* is it a frame? */ - frame_length = adts_check_frame(data); + 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 */ @@ -105,19 +102,15 @@ adts_find_frame(DecoderBuffer *buffer) continue; } - if (length < frame_length) { + if (data.size < frame_length) { /* available buffer size is smaller than the frame will be - attempt to read more data */ - ret = decoder_buffer_fill(buffer); - if (!ret) { + if (!decoder_buffer_fill(buffer)) { /* 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); + decoder_buffer_clear(buffer); } continue; @@ -131,31 +124,27 @@ adts_find_frame(DecoderBuffer *buffer) 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); + unsigned frames = 0; + for (;; frames++) { + unsigned 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); + auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer)); + assert(!data.IsEmpty()); + assert(frame_length <= data.size); - sample_rate = adts_sample_rates[(data[2] & 0x3c) >> 2]; + sample_rate = adts_sample_rates[(data.data[2] & 0x3c) >> 2]; } decoder_buffer_consume(buffer, frame_length); } - frames_per_second = (float)sample_rate / 1024.0; + float frames_per_second = (float)sample_rate / 1024.0; if (frames_per_second <= 0) return -1; @@ -165,66 +154,58 @@ adts_song_duration(DecoderBuffer *buffer) 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; + const size_t fileread = size >= 0 ? size : 0; decoder_buffer_fill(buffer); - const uint8_t *data = (const uint8_t *) - decoder_buffer_read(buffer, &length); - if (data == nullptr) + auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer)); + if (data.IsEmpty()) return -1; - tagsize = 0; - if (length >= 10 && !memcmp(data, "ID3", 3)) { + size_t tagsize = 0; + if (data.size >= 10 && !memcmp(data.data, "ID3", 3)) { /* skip the ID3 tag */ - tagsize = (data[6] << 21) | (data[7] << 14) | - (data[8] << 7) | (data[9] << 0); + tagsize = (data.data[6] << 21) | (data.data[7] << 14) | + (data.data[8] << 7) | (data.data[9] << 0); tagsize += 10; - success = decoder_buffer_skip(buffer, tagsize) && + bool 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) + data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer)); + if (data.IsEmpty()) return -1; } - if (is.IsSeekable() && length >= 2 && - data[0] == 0xFF && ((data[1] & 0xF6) == 0xF0)) { + 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()); - data = (const uint8_t *)decoder_buffer_read(buffer, &length); - if (data != nullptr) - decoder_buffer_consume(buffer, length); + decoder_buffer_clear(buffer); decoder_buffer_fill(buffer); return song_length; - } else if (length >= 5 && memcmp(data, "ADIF", 4) == 0) { + } 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[4] & 0x80) ? 9 : 0; + size_t skip_size = (data.data[4] & 0x80) ? 9 : 0; - if (8 + skip_size > length) + if (8 + skip_size > data.size) /* 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); + 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; @@ -242,9 +223,7 @@ 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 @@ -254,19 +233,18 @@ faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer, 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) { + auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer)); + if (data.IsEmpty()) { 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); + 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; @@ -286,16 +264,14 @@ 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) + 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<unsigned char *>(data), - length); + const_cast<uint8_t *>(data.data), + data.size); } /** @@ -306,17 +282,12 @@ faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer *buffer, 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); + DecoderBuffer *buffer = + decoder_buffer_new(nullptr, is, + FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); + float length = faad_song_duration(buffer, is); if (length < 0) { - bool ret; - AudioFormat audio_format; - NeAACDecHandle decoder = NeAACDecOpen(); NeAACDecConfigurationPtr config = @@ -326,9 +297,9 @@ faad_get_file_time_float(InputStream &is) decoder_buffer_fill(buffer); - ret = faad_decoder_init(decoder, buffer, audio_format, - IgnoreError()); - if (ret) + AudioFormat audio_format; + if (faad_decoder_init(decoder, buffer, audio_format, + IgnoreError())) length = 0; NeAACDecClose(decoder); @@ -359,15 +330,10 @@ faad_get_file_time(InputStream &is) 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); + 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 */ @@ -389,8 +355,8 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is) /* initialize it */ Error error; - ret = faad_decoder_init(decoder, buffer, audio_format, error); - if (!ret) { + AudioFormat audio_format; + if (!faad_decoder_init(decoder, buffer, audio_format, error)) { LogError(error); NeAACDecClose(decoder); decoder_buffer_free(buffer); @@ -404,6 +370,7 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is) /* the decoder loop */ DecoderCommand cmd; + unsigned bit_rate = 0; do { size_t frame_size; const void *decoded; @@ -483,7 +450,7 @@ static const char *const faad_mime_types[] = { "audio/aac", "audio/aacp", nullptr }; -const struct DecoderPlugin faad_decoder_plugin = { +const DecoderPlugin faad_decoder_plugin = { "faad", nullptr, nullptr, diff --git a/src/decoder/FfmpegDecoderPlugin.cxx b/src/decoder/FfmpegDecoderPlugin.cxx index 47e1a3384..01b551bb1 100644 --- a/src/decoder/FfmpegDecoderPlugin.cxx +++ b/src/decoder/FfmpegDecoderPlugin.cxx @@ -38,7 +38,6 @@ extern "C" { #include <libavutil/avutil.h> #include <libavutil/log.h> #include <libavutil/mathematics.h> -#include <libavutil/dict.h> } #include <assert.h> diff --git a/src/decoder/FfmpegMetaData.hxx b/src/decoder/FfmpegMetaData.hxx index 0fd73df04..998cdf5a8 100644 --- a/src/decoder/FfmpegMetaData.hxx +++ b/src/decoder/FfmpegMetaData.hxx @@ -21,8 +21,6 @@ #define MPD_FFMPEG_METADATA_HXX extern "C" { -#include <libavformat/avformat.h> -#include <libavutil/avutil.h> #include <libavutil/dict.h> } @@ -35,6 +33,6 @@ struct tag_handler; void ffmpeg_scan_dictionary(AVDictionary *dict, - const struct tag_handler *handler, void *handler_ctx); + const tag_handler *handler, void *handler_ctx); #endif diff --git a/src/decoder/FlacCommon.cxx b/src/decoder/FlacCommon.cxx index e4b906c12..b0921056a 100644 --- a/src/decoder/FlacCommon.cxx +++ b/src/decoder/FlacCommon.cxx @@ -25,14 +25,10 @@ #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), @@ -107,8 +103,8 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block, decoder_mixramp(data->decoder, flac_parse_mixramp(block)); - flac_vorbis_comments_to_tag(data->tag, - &block->data.vorbis_comment); + data->tag = flac_vorbis_comments_to_tag(&block->data.vorbis_comment); + break; default: break; diff --git a/src/decoder/FlacCommon.hxx b/src/decoder/FlacCommon.hxx index de000dfa1..0f6b09973 100644 --- a/src/decoder/FlacCommon.hxx +++ b/src/decoder/FlacCommon.hxx @@ -29,7 +29,6 @@ #include "pcm/PcmBuffer.hxx" #include <FLAC/stream_decoder.h> -#include <FLAC/metadata.h> struct flac_data : public FlacInput { PcmBuffer buffer; diff --git a/src/decoder/FlacDecoderPlugin.cxx b/src/decoder/FlacDecoderPlugin.cxx index 1b5734434..2c811a8e0 100644 --- a/src/decoder/FlacDecoderPlugin.cxx +++ b/src/decoder/FlacDecoderPlugin.cxx @@ -26,10 +26,6 @@ #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 diff --git a/src/decoder/FlacMetadata.cxx b/src/decoder/FlacMetadata.cxx index 17cc4cd8d..96dbf2db0 100644 --- a/src/decoder/FlacMetadata.cxx +++ b/src/decoder/FlacMetadata.cxx @@ -21,43 +21,45 @@ #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 "tag/Tag.hxx" #include "ReplayGainInfo.hxx" #include "util/ASCII.hxx" +#include "util/SplitString.hxx" -#include <glib.h> - -#include <assert.h> #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) { - 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) + const char *value = vorbis_comment_value(block, cmnt); + if (value == nullptr) 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; - + *fl = (float)atof(value); return true; } @@ -88,23 +90,11 @@ 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) + const char *value = vorbis_comment_value(block, cmnt); + if (value == nullptr) 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); + return std::string(value); } MixRampInfo @@ -118,21 +108,19 @@ flac_parse_mixramp(const FLAC__StreamMetadata *block) /** * Checks if the specified name matches the entry's name, and if yes, - * returns the comment value (not null-temrinated). + * returns the comment value; */ static const char * flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, - const char *name, size_t *length_r) + const char *name) { size_t name_length = strlen(name); const char *comment = (const char*)entry->entry; - if (entry->length <= name_length || - !StringEqualsCaseASCII(comment, name, name_length)) + if (!StringEqualsCaseASCII(comment, name, name_length)) return nullptr; if (comment[name_length] == '=') { - *length_r = entry->length - name_length - 1; return comment + name_length + 1; } @@ -148,14 +136,9 @@ 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); + const char *value = flac_comment_value(entry, name); if (value != nullptr) { - char *p = g_strndup(value, value_length); - tag_handler_invoke_tag(handler, handler_ctx, tag_type, p); - g_free(p); + tag_handler_invoke_tag(handler, handler_ctx, tag_type, value); return true; } @@ -167,16 +150,12 @@ 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; + const char *comment = (const char *)entry->entry; + const SplitString split(comment, '='); + if (split.IsDefined() && !split.IsEmpty()) tag_handler_invoke_pair(handler, handler_ctx, - name, value); - } - - g_free(name); + split.GetFirst(), + split.GetSecond()); } for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i) @@ -221,13 +200,12 @@ flac_scan_metadata(const FLAC__StreamMetadata *block, } } -void -flac_vorbis_comments_to_tag(Tag &tag, - const FLAC__StreamMetadata_VorbisComment *comment) +Tag +flac_vorbis_comments_to_tag(const FLAC__StreamMetadata_VorbisComment *comment) { TagBuilder tag_builder; flac_scan_comments(comment, &add_tag_handler, &tag_builder); - tag_builder.Commit(tag); + return tag_builder.Commit(); } void diff --git a/src/decoder/FlacMetadata.hxx b/src/decoder/FlacMetadata.hxx index 96c61b8e6..33ef17e72 100644 --- a/src/decoder/FlacMetadata.hxx +++ b/src/decoder/FlacMetadata.hxx @@ -27,6 +27,7 @@ #include <assert.h> +struct tag_handler; class MixRampInfo; class FlacMetadataChain { @@ -81,7 +82,7 @@ public: return FLAC__Metadata_ChainStatusString[GetStatus()]; } - void Scan(const struct tag_handler *handler, void *handler_ctx); + void Scan(const tag_handler *handler, void *handler_ctx); }; class FLACMetadataIterator { @@ -110,7 +111,6 @@ public: } }; -struct tag_handler; struct Tag; struct ReplayGainInfo; @@ -130,12 +130,11 @@ flac_parse_replay_gain(ReplayGainInfo &rgi, MixRampInfo flac_parse_mixramp(const FLAC__StreamMetadata *block); -void -flac_vorbis_comments_to_tag(Tag &tag, - const FLAC__StreamMetadata_VorbisComment *comment); +Tag +flac_vorbis_comments_to_tag(const FLAC__StreamMetadata_VorbisComment *comment); void flac_scan_metadata(const FLAC__StreamMetadata *block, - const struct tag_handler *handler, void *handler_ctx); + const tag_handler *handler, void *handler_ctx); #endif diff --git a/src/decoder/GmeDecoderPlugin.cxx b/src/decoder/GmeDecoderPlugin.cxx index 815fd8d69..aafc8f07d 100644 --- a/src/decoder/GmeDecoderPlugin.cxx +++ b/src/decoder/GmeDecoderPlugin.cxx @@ -22,6 +22,7 @@ #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" @@ -53,7 +54,7 @@ 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 *path_container = xstrdup(path_fs); char pat[64]; snprintf(pat, sizeof(pat), "%s%s", @@ -137,7 +138,7 @@ gme_file_decode(Decoder &decoder, const char *path_fs) Music_Emu *emu; const char *gme_err = gme_open_file(path_container, &emu, GME_SAMPLE_RATE); - g_free(path_container); + free(path_container); if (gme_err != nullptr) { LogWarning(gme_domain, gme_err); return; diff --git a/src/decoder/MadDecoderPlugin.cxx b/src/decoder/MadDecoderPlugin.cxx index 9dd86c55f..76eead96d 100644 --- a/src/decoder/MadDecoderPlugin.cxx +++ b/src/decoder/MadDecoderPlugin.cxx @@ -26,23 +26,23 @@ #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 <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 +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + #define FRAMES_CUSHION 2000 #define READ_BUFFER_SIZE 40960 @@ -349,24 +349,14 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag) id3_data = stream.this_frame; mad_stream_skip(&(stream), tagsize); } else { - allocated = (id3_byte_t *)g_malloc(tagsize); + allocated = new id3_byte_t[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) { + if (!decoder_read_full(decoder, input_stream, + allocated + count, tagsize - count)) { LogDebug(mad_domain, "error parsing ID3 tag"); - g_free(allocated); + delete[] allocated; return; } @@ -375,7 +365,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag) id3_tag = id3_tag_parse(id3_data, tagsize); if (id3_tag == nullptr) { - g_free(allocated); + delete[] allocated; return; } @@ -400,7 +390,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag) id3_tag_delete(id3_tag); - g_free(allocated); + delete[] allocated; #else /* !HAVE_ID3TAG */ (void)mpd_tag; @@ -413,20 +403,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag) 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; - } + decoder_skip(decoder, input_stream, tagsize - count); } #endif } @@ -690,7 +667,7 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) /* 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")) + if (!StringStartsWith(lame->encoder, "LAME")) return false; if (sscanf(lame->encoder+4, "%u.%u", diff --git a/src/decoder/MikmodDecoderPlugin.cxx b/src/decoder/MikmodDecoderPlugin.cxx index 34381aafa..93a3cc280 100644 --- a/src/decoder/MikmodDecoderPlugin.cxx +++ b/src/decoder/MikmodDecoderPlugin.cxx @@ -25,8 +25,8 @@ #include "util/Domain.hxx" #include "Log.hxx" -#include <glib.h> #include <mikmod.h> + #include <assert.h> static constexpr Domain mikmod_domain("mikmod"); diff --git a/src/decoder/MpcdecDecoderPlugin.cxx b/src/decoder/MpcdecDecoderPlugin.cxx index dc258623c..620bfad30 100644 --- a/src/decoder/MpcdecDecoderPlugin.cxx +++ b/src/decoder/MpcdecDecoderPlugin.cxx @@ -30,8 +30,6 @@ #include <mpc/mpcdec.h> -#include <assert.h> -#include <unistd.h> #include <math.h> struct mpc_decoder_data { diff --git a/src/decoder/Mpg123DecoderPlugin.cxx b/src/decoder/Mpg123DecoderPlugin.cxx index df23f7847..895c8b865 100644 --- a/src/decoder/Mpg123DecoderPlugin.cxx +++ b/src/decoder/Mpg123DecoderPlugin.cxx @@ -26,9 +26,8 @@ #include "util/Domain.hxx" #include "Log.hxx" -#include <glib.h> - #include <mpg123.h> + #include <stdio.h> static constexpr Domain mpg123_domain("mpg123"); diff --git a/src/decoder/OggCodec.cxx b/src/decoder/OggCodec.cxx index 565dbafcf..cc6cd20c0 100644 --- a/src/decoder/OggCodec.cxx +++ b/src/decoder/OggCodec.cxx @@ -23,6 +23,7 @@ #include "config.h" #include "OggCodec.hxx" +#include "DecoderAPI.hxx" #include <string.h> diff --git a/src/decoder/OggCodec.hxx b/src/decoder/OggCodec.hxx index 857871607..13eab2cdb 100644 --- a/src/decoder/OggCodec.hxx +++ b/src/decoder/OggCodec.hxx @@ -24,7 +24,8 @@ #ifndef MPD_OGG_CODEC_HXX #define MPD_OGG_CODEC_HXX -#include "DecoderAPI.hxx" +struct Decoder; +struct InputStream; enum ogg_codec { OGG_CODEC_UNKNOWN, diff --git a/src/decoder/OggFind.hxx b/src/decoder/OggFind.hxx index ad51ccdf3..a329e42a4 100644 --- a/src/decoder/OggFind.hxx +++ b/src/decoder/OggFind.hxx @@ -25,7 +25,6 @@ #include <ogg/ogg.h> -struct InputStream; class OggSyncState; /** diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/OpusDecoderPlugin.cxx index f3d7b342e..04f549f4f 100644 --- a/src/decoder/OpusDecoderPlugin.cxx +++ b/src/decoder/OpusDecoderPlugin.cxx @@ -22,12 +22,10 @@ #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" @@ -294,8 +292,7 @@ MPDOpusDecoder::HandleTags(const ogg_packet &packet) !tag_builder.IsEmpty()) { decoder_replay_gain(decoder, &rgi); - Tag tag; - tag_builder.Commit(tag); + Tag tag = tag_builder.Commit(); cmd = decoder_tag(decoder, input_stream, std::move(tag)); } else cmd = decoder_get_command(decoder); diff --git a/src/decoder/OpusHead.cxx b/src/decoder/OpusHead.cxx index 0417d3905..dd2b125b0 100644 --- a/src/decoder/OpusHead.cxx +++ b/src/decoder/OpusHead.cxx @@ -21,7 +21,6 @@ #include "OpusHead.hxx" #include <stdint.h> -#include <string.h> struct OpusHead { char signature[8]; diff --git a/src/decoder/PcmDecoderPlugin.cxx b/src/decoder/PcmDecoderPlugin.cxx index dbc38fb76..0bef8dc69 100644 --- a/src/decoder/PcmDecoderPlugin.cxx +++ b/src/decoder/PcmDecoderPlugin.cxx @@ -25,8 +25,6 @@ #include "util/ByteReverse.hxx" #include "Log.hxx" -#include <glib.h> -#include <unistd.h> #include <string.h> #include <stdio.h> /* for SEEK_SET */ diff --git a/src/decoder/SidplayDecoderPlugin.cxx b/src/decoder/SidplayDecoderPlugin.cxx index 160337594..9b3a4ad40 100644 --- a/src/decoder/SidplayDecoderPlugin.cxx +++ b/src/decoder/SidplayDecoderPlugin.cxx @@ -21,6 +21,7 @@ #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" @@ -121,7 +122,7 @@ sidplay_finish() static char * get_container_name(const char *path_fs) { - char *path_container=g_strdup(path_fs); + char *path_container = strdup(path_fs); if(!g_pattern_match(path_with_subtune, strlen(path_container), path_container, nullptr)) @@ -163,9 +164,9 @@ get_song_length(const char *path_fs) if (songlength_database == nullptr) return -1; - gchar *sid_file=get_container_name(path_fs); + char *sid_file = get_container_name(path_fs); SidTuneMod tune(sid_file); - g_free(sid_file); + free(sid_file); if(!tune) { LogWarning(sidplay_domain, "failed to load file for calculating md5 sum"); diff --git a/src/decoder/VorbisComments.cxx b/src/decoder/VorbisComments.cxx index d4f019b58..e6a99ca6b 100644 --- a/src/decoder/VorbisComments.cxx +++ b/src/decoder/VorbisComments.cxx @@ -20,16 +20,13 @@ #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 "util/SplitString.hxx" -#include <glib.h> - -#include <assert.h> #include <stddef.h> #include <string.h> #include <stdlib.h> @@ -104,16 +101,11 @@ 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; + const SplitString split(comment, '='); + if (split.IsDefined() && !split.IsEmpty()) tag_handler_invoke_pair(handler, handler_ctx, - name, value); - } - - g_free(name); + split.GetFirst(), + split.GetSecond()); } for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i) @@ -145,5 +137,5 @@ vorbis_comments_to_tag(char **comments) vorbis_comments_scan(comments, &add_tag_handler, &tag_builder); return tag_builder.IsEmpty() ? nullptr - : tag_builder.Commit(); + : tag_builder.CommitNew(); } diff --git a/src/decoder/VorbisComments.hxx b/src/decoder/VorbisComments.hxx index e5a48ef6b..f537bf30f 100644 --- a/src/decoder/VorbisComments.hxx +++ b/src/decoder/VorbisComments.hxx @@ -31,7 +31,7 @@ vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments); void vorbis_comments_scan(char **comments, - const struct tag_handler *handler, void *handler_ctx); + const tag_handler *handler, void *handler_ctx); Tag * vorbis_comments_to_tag(char **comments); diff --git a/src/decoder/VorbisDecoderPlugin.cxx b/src/decoder/VorbisDecoderPlugin.cxx index 4d3e48528..a637241b1 100644 --- a/src/decoder/VorbisDecoderPlugin.cxx +++ b/src/decoder/VorbisDecoderPlugin.cxx @@ -25,9 +25,7 @@ #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" @@ -48,7 +46,6 @@ #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 { diff --git a/src/decoder/VorbisDomain.hxx b/src/decoder/VorbisDomain.hxx index a35edd041..7946bf52a 100644 --- a/src/decoder/VorbisDomain.hxx +++ b/src/decoder/VorbisDomain.hxx @@ -22,6 +22,8 @@ #include "check.h" -extern const class Domain vorbis_domain; +class Domain; + +extern const Domain vorbis_domain; #endif diff --git a/src/encoder/FlacEncoderPlugin.cxx b/src/encoder/FlacEncoderPlugin.cxx index fa7ed992d..91c9382ad 100644 --- a/src/encoder/FlacEncoderPlugin.cxx +++ b/src/encoder/FlacEncoderPlugin.cxx @@ -23,16 +23,10 @@ #include "AudioFormat.hxx" #include "pcm/PcmBuffer.hxx" #include "ConfigError.hxx" +#include "util/Manual.hxx" +#include "util/DynamicFifoBuffer.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> @@ -54,7 +48,7 @@ struct flac_encoder { * This buffer will hold encoded data from libFLAC until it is * picked up with flac_encoder_read(). */ - struct fifo_buffer *output_buffer; + Manual<DynamicFifoBuffer<uint8_t>> output_buffer; flac_encoder():encoder(flac_encoder_plugin) {} }; @@ -141,7 +135,7 @@ flac_write_callback(gcc_unused const FLAC__StreamEncoder *fse, struct flac_encoder *encoder = (struct flac_encoder *) client_data; //transfer data to buffer - growing_fifo_append(&encoder->output_buffer, data, bytes); + encoder->output_buffer->Append((const uint8_t *)data, bytes); return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; } @@ -154,7 +148,7 @@ flac_encoder_close(Encoder *_encoder) FLAC__stream_encoder_delete(encoder->fse); encoder->expand_buffer.Clear(); - fifo_buffer_free(encoder->output_buffer); + encoder->output_buffer.Destruct(); } static bool @@ -196,7 +190,7 @@ flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error) return false; } - encoder->output_buffer = growing_fifo_new(); + encoder->output_buffer.Construct(8192); /* this immediately outputs data through callback */ @@ -305,18 +299,7 @@ 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; + return encoder->output_buffer->Read((uint8_t *)dest, length); } static const char * diff --git a/src/encoder/NullEncoderPlugin.cxx b/src/encoder/NullEncoderPlugin.cxx index 3b1aae5e2..d35d880d4 100644 --- a/src/encoder/NullEncoderPlugin.cxx +++ b/src/encoder/NullEncoderPlugin.cxx @@ -20,21 +20,19 @@ #include "config.h" #include "NullEncoderPlugin.hxx" #include "EncoderAPI.hxx" -#include "util/fifo_buffer.h" -extern "C" { -#include "util/growing_fifo.h" -} +#include "util/Manual.hxx" +#include "util/DynamicFifoBuffer.hxx" #include "Compiler.h" #include <assert.h> -#include <string.h> struct NullEncoder final { Encoder encoder; - struct fifo_buffer *buffer; + Manual<DynamicFifoBuffer<uint8_t>> buffer; - NullEncoder():encoder(null_encoder_plugin) {} + NullEncoder() + :encoder(null_encoder_plugin) {} }; static Encoder * @@ -58,7 +56,7 @@ null_encoder_close(Encoder *_encoder) { NullEncoder *encoder = (NullEncoder *)_encoder; - fifo_buffer_free(encoder->buffer); + encoder->buffer.Destruct(); } @@ -68,7 +66,7 @@ null_encoder_open(Encoder *_encoder, gcc_unused Error &error) { NullEncoder *encoder = (NullEncoder *)_encoder; - encoder->buffer = growing_fifo_new(); + encoder->buffer.Construct(8192); return true; } @@ -79,7 +77,7 @@ null_encoder_write(Encoder *_encoder, { NullEncoder *encoder = (NullEncoder *)_encoder; - growing_fifo_append(&encoder->buffer, data, length); + encoder->buffer->Append((const uint8_t *)data, length); return length; } @@ -88,17 +86,7 @@ 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; + return encoder->buffer->Read((uint8_t *)dest, length); } const EncoderPlugin null_encoder_plugin = { diff --git a/src/encoder/ShineEncoderPlugin.cxx b/src/encoder/ShineEncoderPlugin.cxx new file mode 100644 index 000000000..009ffd1b9 --- /dev/null +++ b/src/encoder/ShineEncoderPlugin.cxx @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License 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 "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; + + uint16_t frame_size; + int16_t buffer[SHINE_MAX_SAMPLES * CHANNELS]; + int16_t channels[CHANNELS][SHINE_MAX_SAMPLES]; + int16_t *stereo[CHANNELS]; + + Manual<DynamicFifoBuffer<int16_t>> input_buffer; + size_t input_size; + + Manual<DynamicFifoBuffer<uint8_t>> output_buffer; + + ShineEncoder() + :encoder(shine_encoder_plugin), + stereo { channels[0], channels[1] }, + input_size(0) {} + + bool Configure(const config_param ¶m, Error &error); + + bool Setup(Error &error); + + bool WriteChunks(bool flush, Error &error); +}; + +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) +{ + input_size = 0; + 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->input_buffer.Construct(BUFFER_INIT_SIZE); + encoder->output_buffer.Construct(BUFFER_INIT_SIZE); + + return true; +} + +static void +shine_encoder_close(Encoder *_encoder) +{ + ShineEncoder *encoder = (ShineEncoder *)_encoder; + + shine_close(encoder->shine); + encoder->input_buffer.Destruct(); + encoder->output_buffer.Destruct(); +} + +bool +ShineEncoder::WriteChunks(bool flush, gcc_unused Error &error) +{ + const size_t chunk_size = frame_size * audio_format.channels; + + /* + * shine requires data in specific sizes, therefore we need to + * buffer the received data and encode in chunks. + */ + while (input_size >= chunk_size || (flush && input_size > 0)) { + long written; + + const size_t read = input_buffer->Read(buffer, chunk_size); + input_size -= read; + + /* de-interleave data */ + for (size_t i = 0; i < read / audio_format.channels; i++) { + channels[0][i] = buffer[i * audio_format.channels]; + channels[1][i] = buffer[i * audio_format.channels + 1]; + } + + if (flush) { + /* fill remaining with 0s */ + for (size_t i = read / audio_format.channels; i < frame_size; i++) { + channels[0][i] = channels[1][i] = 0; + } + } + + const uint8_t *out = shine_encode_buffer(shine, stereo, &written); + + if (written > 0) + output_buffer->Append((const uint8_t *)out, written); + } + + 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; + + encoder->input_buffer->Append(data, length / sizeof(*data)); + encoder->input_size += length / sizeof(*data); + + return encoder->WriteChunks(false, error); +} + +static bool +shine_encoder_flush(Encoder *_encoder, gcc_unused Error &error) +{ + ShineEncoder *encoder = (ShineEncoder *)_encoder; + long written; + + encoder->WriteChunks(true, error); + 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/ShineEncoderPlugin.hxx b/src/encoder/ShineEncoderPlugin.hxx new file mode 100644 index 000000000..8b1520a74 --- /dev/null +++ b/src/encoder/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/VorbisEncoderPlugin.cxx b/src/encoder/VorbisEncoderPlugin.cxx index 5b40aaea1..830910288 100644 --- a/src/encoder/VorbisEncoderPlugin.cxx +++ b/src/encoder/VorbisEncoderPlugin.cxx @@ -33,8 +33,6 @@ #include <glib.h> -#include <assert.h> - struct vorbis_encoder { /** the base class */ Encoder encoder; diff --git a/src/encoder/WaveEncoderPlugin.cxx b/src/encoder/WaveEncoderPlugin.cxx index acae0be9e..083d25f95 100644 --- a/src/encoder/WaveEncoderPlugin.cxx +++ b/src/encoder/WaveEncoderPlugin.cxx @@ -21,10 +21,8 @@ #include "WaveEncoderPlugin.hxx" #include "EncoderAPI.hxx" #include "system/ByteOrder.hxx" -#include "util/fifo_buffer.h" -extern "C" { -#include "util/growing_fifo.h" -} +#include "util/Manual.hxx" +#include "util/DynamicFifoBuffer.hxx" #include <assert.h> #include <string.h> @@ -33,7 +31,7 @@ struct WaveEncoder { Encoder encoder; unsigned bits; - struct fifo_buffer *buffer; + Manual<DynamicFifoBuffer<uint8_t>> buffer; WaveEncoder():encoder(wave_encoder_plugin) {} }; @@ -128,9 +126,11 @@ wave_encoder_open(Encoder *_encoder, break; } - encoder->buffer = growing_fifo_new(); - wave_header *header = (wave_header *) - growing_fifo_write(&encoder->buffer, sizeof(*header)); + 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, @@ -138,7 +138,8 @@ wave_encoder_open(Encoder *_encoder, encoder->bits, audio_format.sample_rate, (encoder->bits / 8) * audio_format.channels); - fifo_buffer_append(encoder->buffer, sizeof(*header)); + + encoder->buffer->Append(sizeof(*header)); return true; } @@ -148,7 +149,7 @@ wave_encoder_close(Encoder *_encoder) { WaveEncoder *encoder = (WaveEncoder *)_encoder; - fifo_buffer_free(encoder->buffer); + encoder->buffer.Destruct(); } static size_t @@ -198,7 +199,7 @@ wave_encoder_write(Encoder *_encoder, { WaveEncoder *encoder = (WaveEncoder *)_encoder; - uint8_t *dst = (uint8_t *)growing_fifo_write(&encoder->buffer, length); + uint8_t *dst = encoder->buffer->Write(length); if (IsLittleEndian()) { switch (encoder->bits) { @@ -230,7 +231,7 @@ wave_encoder_write(Encoder *_encoder, } } - fifo_buffer_append(encoder->buffer, length); + encoder->buffer->Append(length); return true; } @@ -239,17 +240,7 @@ 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; + return encoder->buffer->Read((uint8_t *)dest, length); } static const char * diff --git a/src/event/BufferedSocket.cxx b/src/event/BufferedSocket.cxx index 92e350e85..0f71bd941 100644 --- a/src/event/BufferedSocket.cxx +++ b/src/event/BufferedSocket.cxx @@ -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..c31d7953e 100644 --- a/src/event/BufferedSocket.hxx +++ b/src/event/BufferedSocket.hxx @@ -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..bc6106bf7 100644 --- a/src/event/Call.cxx +++ b/src/event/Call.cxx @@ -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/DeferredMonitor.cxx b/src/event/DeferredMonitor.cxx index 4ffffaa89..ef7a98911 100644 --- a/src/event/DeferredMonitor.cxx +++ b/src/event/DeferredMonitor.cxx @@ -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..293fc5ea2 100644 --- a/src/event/DeferredMonitor.hxx +++ b/src/event/DeferredMonitor.hxx @@ -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..375132e34 100644 --- a/src/event/FullyBufferedSocket.cxx +++ b/src/event/FullyBufferedSocket.cxx @@ -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..083fab15e 100644 --- a/src/event/FullyBufferedSocket.hxx +++ b/src/event/FullyBufferedSocket.hxx @@ -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..dd8d64f94 100644 --- a/src/event/IdleMonitor.cxx +++ b/src/event/IdleMonitor.cxx @@ -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..dac7ab5b0 100644 --- a/src/event/IdleMonitor.hxx +++ b/src/event/IdleMonitor.hxx @@ -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..9c76c422c 100644 --- a/src/event/Loop.cxx +++ b/src/event/Loop.cxx @@ -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..d03259fb0 100644 --- a/src/event/Loop.hxx +++ b/src/event/Loop.hxx @@ -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..5ef8b98af 100644 --- a/src/event/MultiSocketMonitor.cxx +++ b/src/event/MultiSocketMonitor.cxx @@ -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..150f7baad 100644 --- a/src/event/MultiSocketMonitor.hxx +++ b/src/event/MultiSocketMonitor.hxx @@ -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..038ffc13e --- /dev/null +++ b/src/event/PollGroup.hxx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_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..21bb3a322 --- /dev/null +++ b/src/event/PollGroupEPoll.hxx @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_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..89d09d995 --- /dev/null +++ b/src/event/PollGroupPoll.cxx @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#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..6c69787ac --- /dev/null +++ b/src/event/PollGroupPoll.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_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..b184ff2b2 --- /dev/null +++ b/src/event/PollGroupWinSelect.cxx @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#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..7a6d12d8a --- /dev/null +++ b/src/event/PollGroupWinSelect.hxx @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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..1c2c0d00b --- /dev/null +++ b/src/event/PollResultGeneric.hxx @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_EVENT_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..11592b4fb 100644 --- a/src/event/ServerSocket.cxx +++ b/src/event/ServerSocket.cxx @@ -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..05e13ff63 100644 --- a/src/event/ServerSocket.hxx +++ b/src/event/ServerSocket.hxx @@ -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..eac011616 100644 --- a/src/event/SignalMonitor.cxx +++ b/src/event/SignalMonitor.cxx @@ -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/SocketMonitor.cxx b/src/event/SocketMonitor.cxx index 2b97059f7..ef2128684 100644 --- a/src/event/SocketMonitor.cxx +++ b/src/event/SocketMonitor.cxx @@ -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..d0fcf1b57 100644 --- a/src/event/SocketMonitor.hxx +++ b/src/event/SocketMonitor.hxx @@ -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..55260af2a 100644 --- a/src/event/TimeoutMonitor.cxx +++ b/src/event/TimeoutMonitor.cxx @@ -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..0e3f4bdb7 100644 --- a/src/event/TimeoutMonitor.hxx +++ b/src/event/TimeoutMonitor.hxx @@ -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/filter/AutoConvertFilterPlugin.cxx b/src/filter/AutoConvertFilterPlugin.cxx index 918a16e53..44adc8a66 100644 --- a/src/filter/AutoConvertFilterPlugin.cxx +++ b/src/filter/AutoConvertFilterPlugin.cxx @@ -88,7 +88,11 @@ AutoConvertFilter::Open(AudioFormat &in_audio_format, Error &error) assert(audio_format2 == in_audio_format); - convert_filter_set(convert, child_audio_format); + if (!convert_filter_set(convert, child_audio_format, error)) { + delete convert; + filter->Close(); + return AudioFormat::Undefined(); + } } else /* no */ convert = nullptr; diff --git a/src/filter/ConvertFilterPlugin.cxx b/src/filter/ConvertFilterPlugin.cxx index 040f8426f..e718844e7 100644 --- a/src/filter/ConvertFilterPlugin.cxx +++ b/src/filter/ConvertFilterPlugin.cxx @@ -28,7 +28,6 @@ #include "poison.h" #include <assert.h> -#include <string.h> class ConvertFilter final : public Filter { /** @@ -39,21 +38,18 @@ class ConvertFilter final : public Filter { /** * 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(). + * 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: - 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; - } + bool Set(const AudioFormat &_out_audio_format, Error &error); virtual AudioFormat Open(AudioFormat &af, Error &error) override; virtual void Close() override; @@ -69,12 +65,40 @@ convert_filter_init(gcc_unused const config_param ¶m, 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 = out_audio_format = audio_format; + in_audio_format = audio_format; + out_audio_format.Clear(); + state.Construct(); return in_audio_format; @@ -83,6 +107,11 @@ ConvertFilter::Open(AudioFormat &audio_format, gcc_unused Error &error) 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)); @@ -93,15 +122,15 @@ 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) { + 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(in_audio_format, - src, src_size, - out_audio_format, dest_size_r, + return state->Convert(src, src_size, dest_size_r, error); } @@ -110,10 +139,11 @@ const struct filter_plugin convert_filter_plugin = { convert_filter_init, }; -void -convert_filter_set(Filter *_filter, const AudioFormat out_audio_format) +bool +convert_filter_set(Filter *_filter, AudioFormat out_audio_format, + Error &error) { ConvertFilter *filter = (ConvertFilter *)_filter; - filter->Set(out_audio_format); + return filter->Set(out_audio_format, error); } diff --git a/src/filter/ConvertFilterPlugin.hxx b/src/filter/ConvertFilterPlugin.hxx index c814aaf49..f414e59bf 100644 --- a/src/filter/ConvertFilterPlugin.hxx +++ b/src/filter/ConvertFilterPlugin.hxx @@ -21,6 +21,7 @@ #define MPD_CONVERT_FILTER_PLUGIN_HXX class Filter; +class Error; struct AudioFormat; /** @@ -29,7 +30,8 @@ struct AudioFormat; * 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); +bool +convert_filter_set(Filter *filter, AudioFormat out_audio_format, + Error &error); #endif diff --git a/src/filter/NormalizeFilterPlugin.cxx b/src/filter/NormalizeFilterPlugin.cxx index 6c4f6b0e5..93cca9fef 100644 --- a/src/filter/NormalizeFilterPlugin.cxx +++ b/src/filter/NormalizeFilterPlugin.cxx @@ -25,7 +25,6 @@ #include "AudioFormat.hxx" #include "AudioCompress/compress.h" -#include <assert.h> #include <string.h> class NormalizeFilter final : public Filter { diff --git a/src/filter/ReplayGainFilterPlugin.cxx b/src/filter/ReplayGainFilterPlugin.cxx index b2dcde4cc..998fda6f7 100644 --- a/src/filter/ReplayGainFilterPlugin.cxx +++ b/src/filter/ReplayGainFilterPlugin.cxx @@ -26,8 +26,9 @@ #include "ReplayGainInfo.hxx" #include "ReplayGainConfig.hxx" #include "MixerControl.hxx" -#include "pcm/PcmVolume.hxx" +#include "pcm/Volume.hxx" #include "pcm/PcmBuffer.hxx" +#include "util/ConstBuffer.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" @@ -55,8 +56,8 @@ class ReplayGainFilter final : public Filter { ReplayGainInfo info; /** - * The current volume, between 0 and a value that may or may not exceed - * #PCM_VOLUME_1. + * 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. @@ -66,16 +67,11 @@ class ReplayGainFilter final : public Filter { * 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; + PcmVolume pv; public: ReplayGainFilter() - :mixer(nullptr), mode(REPLAY_GAIN_OFF), - volume(PCM_VOLUME_1) { + :mixer(nullptr), mode(REPLAY_GAIN_OFF) { info.Clear(); } @@ -125,6 +121,7 @@ public: 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, @@ -134,8 +131,9 @@ ReplayGainFilter::Update() "scale=%f\n", (double)scale); volume = pcm_float_to_volume(scale); - } else - volume = PCM_VOLUME_1; + } + + pv.SetVolume(volume); if (mixer != nullptr) { /* update the hardware mixer volume */ @@ -160,48 +158,25 @@ replay_gain_filter_init(gcc_unused const config_param ¶m, AudioFormat ReplayGainFilter::Open(AudioFormat &af, gcc_unused Error &error) { - format = af; + if (!pv.Open(af.format, error)) + return AudioFormat::Undefined(); - return format; + return af; } void ReplayGainFilter::Close() { - buffer.Clear(); + pv.Close(); } const void * ReplayGainFilter::FilterPCM(const void *src, size_t src_size, - size_t *dest_size_r, Error &error) + size_t *dest_size_r, gcc_unused 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 auto dest = pv.Apply({src, src_size}); + *dest_size_r = dest.size; + return dest.data; } const struct filter_plugin replay_gain_filter_plugin = { diff --git a/src/filter/RouteFilterPlugin.cxx b/src/filter/RouteFilterPlugin.cxx index d9042c21f..d2dc2d563 100644 --- a/src/filter/RouteFilterPlugin.cxx +++ b/src/filter/RouteFilterPlugin.cxx @@ -43,7 +43,6 @@ #include "ConfigError.hxx" #include "ConfigData.hxx" #include "AudioFormat.hxx" -#include "CheckAudioFormat.hxx" #include "FilterPlugin.hxx" #include "FilterInternal.hxx" #include "FilterRegistry.hxx" diff --git a/src/filter/VolumeFilterPlugin.cxx b/src/filter/VolumeFilterPlugin.cxx index 1b663f6eb..8b9c6f8e9 100644 --- a/src/filter/VolumeFilterPlugin.cxx +++ b/src/filter/VolumeFilterPlugin.cxx @@ -22,9 +22,9 @@ #include "FilterPlugin.hxx" #include "FilterInternal.hxx" #include "FilterRegistry.hxx" -#include "pcm/PcmVolume.hxx" -#include "pcm/PcmBuffer.hxx" +#include "pcm/Volume.hxx" #include "AudioFormat.hxx" +#include "util/ConstBuffer.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" @@ -32,29 +32,15 @@ #include <string.h> class VolumeFilter final : public Filter { - /** - * The current volume, from 0 to #PCM_VOLUME_1. - */ - unsigned volume; - - AudioFormat format; - - PcmBuffer buffer; + PcmVolume pv; public: - VolumeFilter() - :volume(PCM_VOLUME_1) {} - unsigned GetVolume() const { - assert(volume <= PCM_VOLUME_1); - - return volume; + return pv.GetVolume(); } void SetVolume(unsigned _volume) { - assert(_volume <= PCM_VOLUME_1); - - volume = _volume; + pv.SetVolume(_volume); } virtual AudioFormat Open(AudioFormat &af, Error &error) override; @@ -73,50 +59,27 @@ volume_filter_init(gcc_unused const config_param ¶m, } AudioFormat -VolumeFilter::Open(AudioFormat &audio_format, gcc_unused Error &error) +VolumeFilter::Open(AudioFormat &audio_format, Error &error) { - format = audio_format; + if (!pv.Open(audio_format.format, error)) + return AudioFormat::Undefined(); - return format; + return audio_format; } void VolumeFilter::Close() { - buffer.Clear(); + pv.Close(); } const void * VolumeFilter::FilterPCM(const void *src, size_t src_size, - size_t *dest_size_r, Error &error) + size_t *dest_size_r, gcc_unused 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 auto dest = pv.Apply({src, src_size}); + *dest_size_r = dest.size; + return dest.data; } const struct filter_plugin volume_filter_plugin = { diff --git a/src/fs/AllocatedPath.cxx b/src/fs/AllocatedPath.cxx index 37b79a685..0d0aaacbe 100644 --- a/src/fs/AllocatedPath.cxx +++ b/src/fs/AllocatedPath.cxx @@ -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..d44953cd6 100644 --- a/src/fs/AllocatedPath.hxx +++ b/src/fs/AllocatedPath.hxx @@ -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..dcd291a2d 100644 --- a/src/fs/Charset.cxx +++ b/src/fs/Charset.cxx @@ -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/Config.cxx b/src/fs/Config.cxx index 63e64ef99..87e77512d 100644 --- a/src/fs/Config.cxx +++ b/src/fs/Config.cxx @@ -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 <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/FileSystem.hxx b/src/fs/FileSystem.hxx index cb2f82d22..e9751c73b 100644 --- a/src/fs/FileSystem.hxx +++ b/src/fs/FileSystem.hxx @@ -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/Path.cxx b/src/fs/Path.cxx index 0ff0591fb..4b7fb9319 100644 --- a/src/fs/Path.cxx +++ b/src/fs/Path.cxx @@ -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..807677f38 100644 --- a/src/fs/Path.hxx +++ b/src/fs/Path.hxx @@ -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..889f6b3ea --- /dev/null +++ b/src/fs/StandardDirectory.cxx @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +// 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..cc5c5ec2a --- /dev/null +++ b/src/fs/StandardDirectory.hxx @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_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/TextFile.cxx b/src/fs/TextFile.cxx index 4a64ee963..4a64ee963 100644 --- a/src/TextFile.cxx +++ b/src/fs/TextFile.cxx diff --git a/src/TextFile.hxx b/src/fs/TextFile.hxx index 9d8608711..9d8608711 100644 --- a/src/TextFile.hxx +++ b/src/fs/TextFile.hxx diff --git a/src/fs/Traits.cxx b/src/fs/Traits.cxx index 2c3ce075b..3e874a224 100644 --- a/src/fs/Traits.cxx +++ b/src/fs/Traits.cxx @@ -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..927496e53 100644 --- a/src/fs/Traits.hxx +++ b/src/fs/Traits.hxx @@ -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/input/AlsaInputPlugin.cxx b/src/input/AlsaInputPlugin.cxx new file mode 100644 index 000000000..9990091d3 --- /dev/null +++ b/src/input/AlsaInputPlugin.cxx @@ -0,0 +1,431 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 = strdup("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/AlsaInputPlugin.hxx b/src/input/AlsaInputPlugin.hxx new file mode 100644 index 000000000..ac9519588 --- /dev/null +++ b/src/input/AlsaInputPlugin.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_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/ArchiveInputPlugin.cxx b/src/input/ArchiveInputPlugin.cxx index 5288f2b3b..597a91604 100644 --- a/src/input/ArchiveInputPlugin.cxx +++ b/src/input/ArchiveInputPlugin.cxx @@ -25,11 +25,11 @@ #include "ArchivePlugin.hxx" #include "ArchiveFile.hxx" #include "InputPlugin.hxx" -#include "util/Error.hxx" #include "fs/Traits.hxx" +#include "util/Alloc.hxx" #include "Log.hxx" -#include <glib.h> +#include <stdlib.h> /** * select correct archive plugin to handle the input stream @@ -47,16 +47,16 @@ input_archive_open(const char *pathname, const struct archive_plugin *arplug; InputStream *is; - if (!PathTraits::IsAbsoluteFS(pathname)) + if (!PathTraitsFS::IsAbsolute(pathname)) return nullptr; - char *pname = g_strdup(pathname); + 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); - g_free(pname); + free(pname); return nullptr; } @@ -65,19 +65,19 @@ input_archive_open(const char *pathname, if (!arplug) { FormatWarning(archive_domain, "can't handle archive %s", archive); - g_free(pname); + free(pname); return nullptr; } auto file = archive_file_open(arplug, archive, error); if (file == nullptr) { - g_free(pname); + free(pname); return nullptr; } //setup fileops is = file->OpenStream(filename, mutex, cond, error); - g_free(pname); + free(pname); file->Close(); return is; diff --git a/src/input/CdioParanoiaInputPlugin.cxx b/src/input/CdioParanoiaInputPlugin.cxx index b3ac57413..bf1c3c908 100644 --- a/src/input/CdioParanoiaInputPlugin.cxx +++ b/src/input/CdioParanoiaInputPlugin.cxx @@ -25,6 +25,7 @@ #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" @@ -122,7 +123,7 @@ struct cdio_uri { static bool parse_cdio_uri(struct cdio_uri *dest, const char *src, Error &error) { - if (!g_str_has_prefix(src, "cdda://")) + if (!StringStartsWith(src, "cdda://")) return false; src += 7; diff --git a/src/input/CurlInputPlugin.cxx b/src/input/CurlInputPlugin.cxx index b78545951..b74dc12a9 100644 --- a/src/input/CurlInputPlugin.cxx +++ b/src/input/CurlInputPlugin.cxx @@ -24,6 +24,7 @@ #include "ConfigGlobal.hxx" #include "ConfigData.hxx" #include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" #include "IcyMetaDataParser.hxx" #include "event/SocketMonitor.hxx" #include "event/TimeoutMonitor.hxx" @@ -199,8 +200,6 @@ public: Abandon() would be most appropriate, but it breaks the second case - is that a CURL bug? is there a better solution? */ - - Steal(); } /** @@ -780,8 +779,11 @@ copy_icy_tag(struct input_curl *c) delete c->tag; - if (!c->meta_name.empty() && !tag->HasType(TAG_NAME)) - tag->AddItem(TAG_NAME, c->meta_name.c_str()); + 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; } @@ -910,8 +912,10 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) delete c->tag; - c->tag = new Tag(); - c->tag->AddItem(TAG_NAME, c->meta_name.c_str()); + 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; diff --git a/src/input/DespotifyInputPlugin.cxx b/src/input/DespotifyInputPlugin.cxx index b08299516..18704bd40 100644 --- a/src/input/DespotifyInputPlugin.cxx +++ b/src/input/DespotifyInputPlugin.cxx @@ -23,26 +23,25 @@ #include "InputStream.hxx" #include "InputPlugin.hxx" #include "tag/Tag.hxx" +#include "util/StringUtil.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 { +class DespotifyInputStream { InputStream base; struct despotify_session *session; struct ds_track *track; - Tag *tag; + Tag tag; struct ds_pcm_data pcm; size_t len_available; bool eof; @@ -53,7 +52,7 @@ struct DespotifyInputStream { ds_track *_track) :base(input_plugin_despotify, uri, mutex, cond), session(_session), track(_track), - tag(mpd_despotify_tag_from_track(track)), + tag(mpd_despotify_tag_from_track(*track)), len_available(0), eof(false) { memset(&pcm, 0, sizeof(pcm)); @@ -63,30 +62,53 @@ struct DespotifyInputStream { base.ready = true; } +public: ~DespotifyInputStream() { - delete tag; - 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(); }; -static void -refill_buffer(DespotifyInputStream *ctx) +inline void +DespotifyInputStream::FillBuffer() { /* Wait until there is data */ while (1) { - int rc = despotify_get_pcm(ctx->session, &ctx->pcm); + int rc = despotify_get_pcm(session, &pcm); - if (rc == 0 && ctx->pcm.len) { - ctx->len_available = ctx->pcm.len; + if (rc == 0 && pcm.len) { + len_available = pcm.len; break; } - if (ctx->eof == true) + + if (eof == true) break; if (rc < 0) { LogDebug(despotify_domain, "despotify_get_pcm error"); - ctx->eof = true; + eof = true; break; } @@ -95,11 +117,9 @@ refill_buffer(DespotifyInputStream *ctx) } } -static void callback(gcc_unused struct despotify_session* ds, - int sig, gcc_unused void* data, void* callback_data) +inline void +DespotifyInputStream::Callback(int sig) { - DespotifyInputStream *ctx = (DespotifyInputStream *)callback_data; - switch (sig) { case DESPOTIFY_NEW_TRACK: break; @@ -109,35 +129,38 @@ static void callback(gcc_unused struct despotify_session* ds, case DESPOTIFY_TRACK_PLAY_ERROR: LogWarning(despotify_domain, "Track play error"); - ctx->eof = true; - ctx->len_available = 0; + eof = true; + len_available = 0; break; case DESPOTIFY_END_OF_PLAYLIST: - ctx->eof = true; - FormatDebug(despotify_domain, "End of playlist: %d", ctx->eof); + eof = true; + LogDebug(despotify_domain, "End of playlist"); break; } } - -static InputStream * -input_despotify_open(const char *url, - Mutex &mutex, Cond &cond, - gcc_unused Error &error) +static void callback(gcc_unused struct despotify_session* ds, + int sig, gcc_unused void* data, void* callback_data) { - struct despotify_session *session; - struct ds_link *ds_link; - struct ds_track *track; + DespotifyInputStream *ctx = (DespotifyInputStream *)callback_data; - if (!g_str_has_prefix(url, "spt://")) + ctx->Callback(sig); +} + +inline InputStream * +DespotifyInputStream::Open(const char *url, + Mutex &mutex, Cond &cond, + gcc_unused Error &error) +{ + if (!StringStartsWith(url, "spt://")) return nullptr; - session = mpd_despotify_get_session(); - if (!session) + despotify_session *session = mpd_despotify_get_session(); + if (session == nullptr) return nullptr; - ds_link = despotify_link_from_uri(url + 6); + ds_link *ds_link = despotify_link_from_uri(url + 6); if (!ds_link) { FormatDebug(despotify_domain, "Can't find %s", url); return nullptr; @@ -147,7 +170,7 @@ input_despotify_open(const char *url, return nullptr; } - track = despotify_link_get_track(session, ds_link); + ds_track *track = despotify_link_get_track(session, ds_link); despotify_free_link(ds_link); if (!track) return nullptr; @@ -170,26 +193,34 @@ input_despotify_open(const char *url, return &ctx->base; } -static size_t -input_despotify_read(InputStream *is, void *ptr, size_t size, - gcc_unused Error &error) +static InputStream * +input_despotify_open(const char *url, Mutex &mutex, Cond &cond, Error &error) { - DespotifyInputStream *ctx = (DespotifyInputStream *)is; - size_t to_cpy = size; + return DespotifyInputStream::Open(url, mutex, cond, error); +} - if (ctx->len_available == 0) - refill_buffer(ctx); +inline size_t +DespotifyInputStream::Read(void *ptr, size_t size, gcc_unused Error &error) +{ + if (len_available == 0) + FillBuffer(); - if (ctx->len_available < size) - to_cpy = ctx->len_available; - memcpy(ptr, ctx->pcm.buf, to_cpy); - ctx->len_available -= to_cpy; + size_t to_cpy = std::min(size, len_available); + memcpy(ptr, pcm.buf, to_cpy); + len_available -= to_cpy; - is->offset += 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) { @@ -204,18 +235,15 @@ input_despotify_eof(InputStream *is) { DespotifyInputStream *ctx = (DespotifyInputStream *)is; - return ctx->eof; + return ctx->IsEOF(); } static Tag * input_despotify_tag(InputStream *is) { DespotifyInputStream *ctx = (DespotifyInputStream *)is; - Tag *tag = ctx->tag; - - ctx->tag = nullptr; - return tag; + return ctx->ReadTag(); } const InputPlugin input_plugin_despotify = { diff --git a/src/input/FfmpegInputPlugin.cxx b/src/input/FfmpegInputPlugin.cxx index 8f9cd0b86..7d041677b 100644 --- a/src/input/FfmpegInputPlugin.cxx +++ b/src/input/FfmpegInputPlugin.cxx @@ -24,17 +24,15 @@ #include "FfmpegInputPlugin.hxx" #include "InputStream.hxx" #include "InputPlugin.hxx" +#include "util/StringUtil.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; @@ -91,12 +89,12 @@ 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://")) + if (!StringStartsWith(uri, "gopher://") && + !StringStartsWith(uri, "rtp://") && + !StringStartsWith(uri, "rtsp://") && + !StringStartsWith(uri, "rtmp://") && + !StringStartsWith(uri, "rtmpt://") && + !StringStartsWith(uri, "rtmps://")) return nullptr; AVIOContext *h; diff --git a/src/input/FileInputPlugin.cxx b/src/input/FileInputPlugin.cxx index 26e40d609..5a63a469c 100644 --- a/src/input/FileInputPlugin.cxx +++ b/src/input/FileInputPlugin.cxx @@ -30,8 +30,6 @@ #include <sys/stat.h> #include <unistd.h> #include <errno.h> -#include <string.h> -#include <glib.h> static constexpr Domain file_domain("file"); @@ -62,7 +60,7 @@ input_file_open(const char *filename, int fd, ret; struct stat st; - if (!PathTraits::IsAbsoluteFS(filename)) + if (!PathTraitsFS::IsAbsolute(filename)) return nullptr; fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0); diff --git a/src/input/MmsInputPlugin.cxx b/src/input/MmsInputPlugin.cxx index e97c1eb3f..2c7f6d166 100644 --- a/src/input/MmsInputPlugin.cxx +++ b/src/input/MmsInputPlugin.cxx @@ -21,15 +21,12 @@ #include "MmsInputPlugin.hxx" #include "InputStream.hxx" #include "InputPlugin.hxx" +#include "util/StringUtil.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; @@ -61,10 +58,10 @@ 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://")) + 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); diff --git a/src/input/RewindInputPlugin.cxx b/src/input/RewindInputPlugin.cxx index e11f56631..78ab75660 100644 --- a/src/input/RewindInputPlugin.cxx +++ b/src/input/RewindInputPlugin.cxx @@ -21,7 +21,6 @@ #include "RewindInputPlugin.hxx" #include "InputStream.hxx" #include "InputPlugin.hxx" -#include "tag/Tag.hxx" #include <assert.h> #include <string.h> diff --git a/src/input/SmbclientInputPlugin.cxx b/src/input/SmbclientInputPlugin.cxx new file mode 100644 index 000000000..f97aff7d5 --- /dev/null +++ b/src/input/SmbclientInputPlugin.cxx @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this 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 "InputStream.hxx" +#include "InputPlugin.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" + +#include <libsmbclient.h> + +#include <string.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; + } +}; + +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, "foo"); + strcpy(pw, "bar"); +} + +/* + * InputPlugin methods + * + */ + +static bool +input_smbclient_init(gcc_unused const config_param ¶m, Error &error) +{ + constexpr int debug = 0; + if (smbc_init(mpd_smbc_get_auth_data, debug) < 0) { + error.SetErrno("smbc_init() failed"); + 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/SmbclientInputPlugin.hxx b/src/input/SmbclientInputPlugin.hxx new file mode 100644 index 000000000..7203a01b8 --- /dev/null +++ b/src/input/SmbclientInputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_SMBCLIENT_H +#define MPD_INPUT_SMBCLIENT_H + +extern const struct InputPlugin input_plugin_smbclient; + +#endif diff --git a/src/ls.cxx b/src/ls.cxx index b1a636416..1de515a1f 100644 --- a/src/ls.cxx +++ b/src/ls.cxx @@ -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 <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..6d5f989bc 100644 --- a/src/ls.hxx +++ b/src/ls.hxx @@ -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 index 4a4ca433c..4f92e697d 100644 --- a/src/mixer/AlsaMixerPlugin.cxx +++ b/src/mixer/AlsaMixerPlugin.cxx @@ -23,10 +23,11 @@ #include "GlobalEvents.hxx" #include "Main.hxx" #include "event/MultiSocketMonitor.hxx" +#include "event/DeferredMonitor.hxx" #include "event/Loop.hxx" -#include "event/Call.hxx" #include "util/ASCII.hxx" #include "util/ReusableArray.hxx" +#include "util/Clamp.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" @@ -39,29 +40,22 @@ #define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM" static constexpr unsigned VOLUME_MIXER_ALSA_INDEX_DEFAULT = 0; -class AlsaMixerMonitor final : private MultiSocketMonitor { +class AlsaMixerMonitor final : MultiSocketMonitor, DeferredMonitor { 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 + :MultiSocketMonitor(_loop), DeferredMonitor(_loop), + mixer(_mixer) { + DeferredMonitor::Schedule(); } private: -#ifndef USE_EPOLL - static gboolean InitAlsaMixerMonitor(gpointer data) { - AlsaMixerMonitor &amm = *(AlsaMixerMonitor *)data; - amm.InvalidateSockets(); - return false; + virtual void RunDeferred() override { + InvalidateSockets(); } -#endif virtual int PrepareSockets() override; virtual void DispatchSockets() override; @@ -97,8 +91,10 @@ static constexpr Domain alsa_mixer_domain("alsa_mixer"); int AlsaMixerMonitor::PrepareSockets() { - if (mixer == nullptr) + if (mixer == nullptr) { + ClearSocketList(); return -1; + } int count = snd_mixer_poll_descriptors_count(mixer); if (count < 0) @@ -110,24 +106,7 @@ AlsaMixerMonitor::PrepareSockets() 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); - + ReplaceSocketList(pfds, count); return -1; } @@ -372,8 +351,7 @@ AlsaMixer::SetVolume(unsigned volume, Error &error) 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; + level = Clamp(level, volume_min, volume_max); err = snd_mixer_selem_set_playback_volume_all(elem, level); if (err < 0) { diff --git a/src/mixer/OssMixerPlugin.cxx b/src/mixer/OssMixerPlugin.cxx index 0a459bc97..7a91cfcb1 100644 --- a/src/mixer/OssMixerPlugin.cxx +++ b/src/mixer/OssMixerPlugin.cxx @@ -19,7 +19,7 @@ #include "config.h" #include "MixerInternal.hxx" -#include "OutputAPI.hxx" +#include "ConfigData.hxx" #include "system/fd_util.h" #include "util/ASCII.hxx" #include "util/Error.hxx" @@ -28,10 +28,8 @@ #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> diff --git a/src/mixer/PulseMixerPlugin.cxx b/src/mixer/PulseMixerPlugin.cxx index ff10256cb..84d42c392 100644 --- a/src/mixer/PulseMixerPlugin.cxx +++ b/src/mixer/PulseMixerPlugin.cxx @@ -26,7 +26,6 @@ #include "util/Domain.hxx" #include "Log.hxx" -#include <pulse/thread-mainloop.h> #include <pulse/context.h> #include <pulse/introspect.h> #include <pulse/stream.h> @@ -34,7 +33,6 @@ #include <pulse/error.h> #include <assert.h> -#include <string.h> struct PulseMixer final : public Mixer { PulseOutput *output; diff --git a/src/mixer/PulseMixerPlugin.hxx b/src/mixer/PulseMixerPlugin.hxx index fa73e0f5e..46bd06a01 100644 --- a/src/mixer/PulseMixerPlugin.hxx +++ b/src/mixer/PulseMixerPlugin.hxx @@ -20,20 +20,17 @@ #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); +pulse_mixer_on_connect(PulseMixer *pm, 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); +pulse_mixer_on_change(PulseMixer *pm, pa_context *context, pa_stream *stream); #endif diff --git a/src/mixer/RoarMixerPlugin.cxx b/src/mixer/RoarMixerPlugin.cxx index 6bd700551..75147329e 100644 --- a/src/mixer/RoarMixerPlugin.cxx +++ b/src/mixer/RoarMixerPlugin.cxx @@ -21,8 +21,8 @@ #include "config.h" #include "MixerInternal.hxx" -#include "OutputAPI.hxx" #include "output/RoarOutputPlugin.hxx" +#include "Compiler.h" struct RoarMixer final : public Mixer { /** the base mixer class */ diff --git a/src/mixer/SoftwareMixerPlugin.cxx b/src/mixer/SoftwareMixerPlugin.cxx index 193e68f23..e5f4b1659 100644 --- a/src/mixer/SoftwareMixerPlugin.cxx +++ b/src/mixer/SoftwareMixerPlugin.cxx @@ -24,7 +24,7 @@ #include "FilterRegistry.hxx" #include "FilterInternal.hxx" #include "filter/VolumeFilterPlugin.hxx" -#include "pcm/PcmVolume.hxx" +#include "pcm/Volume.hxx" #include "ConfigData.hxx" #include "util/Error.hxx" diff --git a/src/output/AlsaOutputPlugin.cxx b/src/output/AlsaOutputPlugin.cxx index 4877d3a46..b5ca511b2 100644 --- a/src/output/AlsaOutputPlugin.cxx +++ b/src/output/AlsaOutputPlugin.cxx @@ -27,7 +27,6 @@ #include "util/Domain.hxx" #include "Log.hxx" -#include <glib.h> #include <alsa/asoundlib.h> #include <string> @@ -118,7 +117,7 @@ struct AlsaOutput { * It contains silence samples, enough to fill one period (see * #period_frames). */ - void *silence; + uint8_t *silence; AlsaOutput():mode(0), writei(snd_pcm_writei) { } @@ -593,8 +592,8 @@ configure_hw: ad->period_frames = alsa_period_size; ad->period_position = 0; - ad->silence = g_malloc(snd_pcm_frames_to_bytes(ad->pcm, - alsa_period_size)); + 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); @@ -641,7 +640,7 @@ alsa_setup_dsd(AlsaOutput *ad, const AudioFormat audio_format, error.Format(alsa_output_domain, "Failed to configure DSD-over-USB on ALSA device \"%s\"", alsa_device(ad)); - g_free(ad->silence); + delete[] ad->silence; return false; } @@ -811,7 +810,7 @@ alsa_close(struct audio_output *ao) AlsaOutput *ad = (AlsaOutput *)ao; snd_pcm_close(ad->pcm); - g_free(ad->silence); + delete[] ad->silence; } static size_t diff --git a/src/output/FifoOutputPlugin.cxx b/src/output/FifoOutputPlugin.cxx index aeb9a6a87..7963d8c82 100644 --- a/src/output/FifoOutputPlugin.cxx +++ b/src/output/FifoOutputPlugin.cxx @@ -22,7 +22,6 @@ #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" @@ -30,10 +29,8 @@ #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 */ diff --git a/src/output/HttpdClient.cxx b/src/output/HttpdClient.cxx index 8e13fda38..102ce1ec3 100644 --- a/src/output/HttpdClient.cxx +++ b/src/output/HttpdClient.cxx @@ -37,24 +37,26 @@ HttpdClient::~HttpdClient() if (current_page != nullptr) current_page->Unref(); - for (auto page : pages) - page->Unref(); + ClearQueue(); } if (metadata) metadata->Unref(); + + if (IsDefined()) + BufferedSocket::Close(); } void HttpdClient::Close() { - httpd->RemoveClient(*this); + httpd.RemoveClient(*this); } void HttpdClient::LockClose() { - const ScopeLock protect(httpd->mutex); + const ScopeLock protect(httpd.mutex); Close(); } @@ -67,7 +69,7 @@ HttpdClient::BeginResponse() current_page = nullptr; if (!head_method) - httpd->SendHeader(*this); + httpd.SendHeader(*this); } /** @@ -155,13 +157,13 @@ HttpdClient::SendResponse() "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); + httpd.content_type); } else if (metadata_requested) { char *metadata_header = - icy_server_metadata_header(httpd->name, httpd->genre, - httpd->website, - httpd->content_type, + icy_server_metadata_header(httpd.name, httpd.genre, + httpd.website, + httpd.content_type, metaint); g_strlcpy(buffer, metadata_header, sizeof(buffer)); @@ -176,7 +178,7 @@ HttpdClient::SendResponse() "Pragma: no-cache\r\n" "Cache-Control: no-cache, no-store\r\n" "\r\n", - httpd->content_type); + httpd.content_type); } ssize_t nbytes = SocketMonitor::Write(buffer, strlen(buffer)); @@ -192,11 +194,12 @@ HttpdClient::SendResponse() return true; } -HttpdClient::HttpdClient(HttpdOutput *_httpd, int _fd, EventLoop &_loop, +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), @@ -207,16 +210,24 @@ HttpdClient::HttpdClient(HttpdOutput *_httpd, int _fd, EventLoop &_loop, { } -size_t -HttpdClient::GetQueueSize() const +void +HttpdClient::ClearQueue() { - if (state != RESPONSE) - return 0; + assert(state == RESPONSE); - size_t size = 0; - for (auto page : pages) - size += page->size; - return size; + 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 @@ -225,9 +236,7 @@ HttpdClient::CancelQueue() if (state != RESPONSE) return; - for (auto page : pages) - page->Unref(); - pages.clear(); + ClearQueue(); if (current_page == nullptr) CancelWrite(); @@ -262,7 +271,7 @@ HttpdClient::GetBytesTillMetaData() const inline bool HttpdClient::TryWrite() { - const ScopeLock protect(httpd->mutex); + const ScopeLock protect(httpd.mutex); assert(state == RESPONSE); @@ -270,14 +279,17 @@ HttpdClient::TryWrite() if (pages.empty()) { /* another thread has removed the event source while this thread was waiting for - httpd->mutex */ + httpd.mutex */ CancelWrite(); return true; } current_page = pages.front(); - pages.pop_front(); + pages.pop(); current_position = 0; + + assert(queue_size >= current_page->size); + queue_size -= current_page->size; } const ssize_t bytes_to_write = GetBytesTillMetaData(); @@ -378,8 +390,15 @@ HttpdClient::PushPage(Page *page) /* 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_back(page); + pages.push(page); + queue_size += page->size; ScheduleWrite(); } diff --git a/src/output/HttpdClient.hxx b/src/output/HttpdClient.hxx index 66a819232..94fe2ae62 100644 --- a/src/output/HttpdClient.hxx +++ b/src/output/HttpdClient.hxx @@ -23,18 +23,19 @@ #include "event/BufferedSocket.hxx" #include "Compiler.h" +#include <queue> #include <list> #include <stddef.h> -struct HttpdOutput; +class HttpdOutput; class Page; -class HttpdClient final : public BufferedSocket { +class HttpdClient final : BufferedSocket { /** * The httpd output object this client is connected to. */ - HttpdOutput *const httpd; + HttpdOutput &httpd; /** * The current state of the client. @@ -53,7 +54,12 @@ class HttpdClient final : public BufferedSocket { /** * A queue of #Page objects to be sent to the client. */ - std::list<Page *> pages; + 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. @@ -120,7 +126,7 @@ public: * @param httpd the HTTP output device * @param fd the socket file descriptor */ - HttpdClient(HttpdOutput *httpd, int _fd, EventLoop &_loop, + HttpdClient(HttpdOutput &httpd, int _fd, EventLoop &_loop, bool _metadata_supported); /** @@ -137,12 +143,6 @@ public: void LockClose(); /** - * Returns the total size of this client's page queue. - */ - gcc_pure - size_t GetQueueSize() const; - - /** * Clears the page queue. */ void CancelQueue(); @@ -180,6 +180,9 @@ public: */ void PushMetaData(Page *page); +private: + void ClearQueue(); + protected: virtual bool OnSocketReady(unsigned flags) override; virtual InputResult OnSocketInput(void *data, size_t length) override; diff --git a/src/output/HttpdInternal.hxx b/src/output/HttpdInternal.hxx index b76493a44..8d35d35e9 100644 --- a/src/output/HttpdInternal.hxx +++ b/src/output/HttpdInternal.hxx @@ -29,6 +29,8 @@ #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++ */ @@ -36,6 +38,8 @@ #endif #include <forward_list> +#include <queue> +#include <list> struct config_param; class Error; @@ -46,7 +50,7 @@ class Page; struct Encoder; struct Tag; -struct HttpdOutput final : private ServerSocket { +class HttpdOutput final : ServerSocket, DeferredMonitor { struct audio_output base; /** @@ -68,6 +72,7 @@ struct HttpdOutput final : private ServerSocket { */ size_t unflushed_input; +public: /** * The MIME type produced by the #encoder. */ @@ -80,6 +85,13 @@ struct HttpdOutput final : private ServerSocket { 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. */ @@ -96,6 +108,15 @@ struct HttpdOutput final : private ServerSocket { 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; @@ -108,6 +129,7 @@ struct HttpdOutput final : private ServerSocket { */ char const *website; +private: /** * A linked list containing all clients which are currently * connected. @@ -126,11 +148,46 @@ struct HttpdOutput final : private ServerSocket { */ 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(); @@ -181,6 +238,9 @@ struct HttpdOutput final : private ServerSocket { */ 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. @@ -203,7 +263,13 @@ struct HttpdOutput final : private ServerSocket { 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; }; diff --git a/src/output/HttpdOutputPlugin.cxx b/src/output/HttpdOutputPlugin.cxx index 369c06937..4cd2b4ae8 100644 --- a/src/output/HttpdOutputPlugin.cxx +++ b/src/output/HttpdOutputPlugin.cxx @@ -28,13 +28,12 @@ #include "Page.hxx" #include "IcyMetaDataServer.hxx" #include "system/fd_util.h" -#include "Main.hxx" +#include "IOThread.hxx" +#include "event/Call.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" -#include <glib.h> - #include <assert.h> #include <sys/types.h> @@ -51,7 +50,7 @@ const Domain httpd_output_domain("httpd_output"); inline HttpdOutput::HttpdOutput(EventLoop &_loop) - :ServerSocket(_loop), + :ServerSocket(_loop), DeferredMonitor(_loop), encoder(nullptr), unflushed_input(0), metadata(nullptr) { @@ -72,8 +71,11 @@ HttpdOutput::Bind(Error &error) { open = false; - const ScopeLock protect(mutex); - return ServerSocket::Open(error); + bool result = false; + BlockingCall(GetEventLoop(), [this, &error, &result](){ + result = ServerSocket::Open(error); + }); + return result; } inline void @@ -81,8 +83,9 @@ HttpdOutput::Unbind() { assert(!open); - const ScopeLock protect(mutex); - ServerSocket::Close(); + BlockingCall(GetEventLoop(), [this](){ + ServerSocket::Close(); + }); } inline bool @@ -130,47 +133,30 @@ HttpdOutput::Configure(const config_param ¶m, Error &error) 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(*main_loop); + HttpdOutput *httpd = new HttpdOutput(io_thread_get()); - if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, - error)) { + audio_output *result = httpd->InitAndConfigure(param, error); + if (result == nullptr) 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)); + return result; } -#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); + HttpdOutput *httpd = HttpdOutput::Cast(ao); - ao_base_finish(&httpd->base); + httpd->Finish(); delete httpd; } @@ -181,7 +167,7 @@ httpd_output_finish(struct audio_output *ao) inline void HttpdOutput::AddClient(int fd) { - clients.emplace_front(this, fd, GetEventLoop(), + clients.emplace_front(*this, fd, GetEventLoop(), encoder->plugin.tag == nullptr); ++clients_cnt; @@ -191,6 +177,29 @@ HttpdOutput::AddClient(int fd) } 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) { @@ -199,9 +208,10 @@ HttpdOutput::OnAccept(int fd, const sockaddr &address, #ifdef HAVE_LIBWRAP if (address.sa_family != AF_UNIX) { - char *hostaddr = sockaddr_to_string(&address, address_length, - IgnoreError()); - const char *progname = g_get_prgname(); + 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); @@ -212,13 +222,10 @@ HttpdOutput::OnAccept(int fd, const sockaddr &address, /* tcp wrappers says no */ FormatWarning(httpd_output_domain, "libwrap refused connection (libwrap=%s) from %s", - progname, hostaddr); - g_free(hostaddr); + progname, hostaddr.c_str()); close_socket(fd); return; } - - g_free(hostaddr); } #else (void)address; @@ -271,7 +278,7 @@ HttpdOutput::ReadPage() static bool httpd_output_enable(struct audio_output *ao, Error &error) { - HttpdOutput *httpd = Cast(ao); + HttpdOutput *httpd = HttpdOutput::Cast(ao); return httpd->Bind(error); } @@ -279,7 +286,7 @@ httpd_output_enable(struct audio_output *ao, Error &error) static void httpd_output_disable(struct audio_output *ao) { - HttpdOutput *httpd = Cast(ao); + HttpdOutput *httpd = HttpdOutput::Cast(ao); httpd->Unbind(); } @@ -325,9 +332,7 @@ static bool httpd_output_open(struct audio_output *ao, AudioFormat &audio_format, Error &error) { - HttpdOutput *httpd = Cast(ao); - - assert(httpd->clients.empty()); + HttpdOutput *httpd = HttpdOutput::Cast(ao); const ScopeLock protect(httpd->mutex); return httpd->Open(audio_format, error); @@ -342,7 +347,9 @@ HttpdOutput::Close() delete timer; - clients.clear(); + BlockingCall(GetEventLoop(), [this](){ + clients.clear(); + }); if (header != nullptr) header->Unref(); @@ -353,7 +360,7 @@ HttpdOutput::Close() static void httpd_output_close(struct audio_output *ao) { - HttpdOutput *httpd = Cast(ao); + HttpdOutput *httpd = HttpdOutput::Cast(ao); const ScopeLock protect(httpd->mutex); httpd->Close(); @@ -382,17 +389,15 @@ HttpdOutput::SendHeader(HttpdClient &client) const client.PushPage(header); } -static unsigned -httpd_output_delay(struct audio_output *ao) +inline unsigned +HttpdOutput::Delay() const { - HttpdOutput *httpd = Cast(ao); - - if (!httpd->LockHasClients() && httpd->base.pause) { + 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 */ - httpd->timer->Reset(); + timer->Reset(); /* some arbitrary delay that is long enough to avoid consuming too much CPU, and short enough to notice @@ -400,39 +405,47 @@ httpd_output_delay(struct audio_output *ao) return 1000; } - return httpd->timer->IsStarted() - ? httpd->timer->GetDelay() + 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); - const ScopeLock protect(mutex); - for (auto &client : clients) - client.PushPage(page); + mutex.lock(); + pages.push(page); + page->Ref(); + mutex.unlock(); + + DeferredMonitor::Schedule(); } void HttpdOutput::BroadcastFromEncoder() { + /* synchronize with the IOThread */ 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(); + while (!pages.empty()) + cond.wait(mutex); Page *page; - while ((page = ReadPage()) != nullptr) { - BroadcastPage(page); - page->Unref(); - } + while ((page = ReadPage()) != nullptr) + pages.push(page); + + mutex.unlock(); + + DeferredMonitor::Schedule(); } inline bool @@ -447,28 +460,34 @@ HttpdOutput::EncodeAndPlay(const void *chunk, size_t size, Error &error) return true; } -static size_t -httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, - Error &error) +inline size_t +HttpdOutput::Play(const void *chunk, size_t size, Error &error) { - HttpdOutput *httpd = Cast(ao); - - if (httpd->LockHasClients()) { - if (!httpd->EncodeAndPlay(chunk, size, error)) + if (LockHasClients()) { + if (!EncodeAndPlay(chunk, size, error)) return 0; } - if (!httpd->timer->IsStarted()) - httpd->timer->Start(); - httpd->timer->Add(size); + 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 = Cast(ao); + HttpdOutput *httpd = HttpdOutput::Cast(ao); if (httpd->LockHasClients()) { static const char silence[1020] = { 0 }; @@ -531,19 +550,36 @@ HttpdOutput::SendTag(const Tag *tag) static void httpd_output_tag(struct audio_output *ao, const Tag *tag) { - HttpdOutput *httpd = Cast(ao); + 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 = Cast(ao); + HttpdOutput *httpd = HttpdOutput::Cast(ao); - const ScopeLock protect(httpd->mutex); - for (auto &client : httpd->clients) - client.CancelQueue(); + BlockingCall(io_thread_get(), [httpd](){ + httpd->CancelAllClients(); + }); } const struct audio_output_plugin httpd_output_plugin = { diff --git a/src/output/JackOutputPlugin.cxx b/src/output/JackOutputPlugin.cxx index 7ed672f95..ef06b5abf 100644 --- a/src/output/JackOutputPlugin.cxx +++ b/src/output/JackOutputPlugin.cxx @@ -34,10 +34,6 @@ #include <stdlib.h> #include <string.h> -#include <stdio.h> -#include <sys/types.h> -#include <unistd.h> -#include <errno.h> enum { MAX_PORTS = 16, diff --git a/src/output/NullOutputPlugin.cxx b/src/output/NullOutputPlugin.cxx index e2eec9dbc..a901b7d2a 100644 --- a/src/output/NullOutputPlugin.cxx +++ b/src/output/NullOutputPlugin.cxx @@ -22,8 +22,6 @@ #include "OutputAPI.hxx" #include "Timer.hxx" -#include <assert.h> - struct NullOutput { struct audio_output base; diff --git a/src/output/OSXOutputPlugin.cxx b/src/output/OSXOutputPlugin.cxx index 97ebae056..586210b21 100644 --- a/src/output/OSXOutputPlugin.cxx +++ b/src/output/OSXOutputPlugin.cxx @@ -20,7 +20,7 @@ #include "config.h" #include "OSXOutputPlugin.hxx" #include "OutputAPI.hxx" -#include "util/fifo_buffer.h" +#include "util/DynamicFifoBuffer.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "thread/Mutex.hxx" @@ -44,7 +44,7 @@ struct OSXOutput { Mutex mutex; Cond condition; - struct fifo_buffer *buffer; + DynamicFifoBuffer<uint8_t> *buffer; }; static constexpr Domain osx_output_domain("osx_output"); @@ -72,7 +72,7 @@ osx_output_configure(OSXOutput *oo, const config_param ¶m) } else { oo->component_subtype = kAudioUnitSubType_HALOutput; - /* XXX am I supposed to g_strdup() this? */ + /* XXX am I supposed to strdup() this? */ oo->device_name = device; } } @@ -207,22 +207,19 @@ osx_render(void *vdata, od->mutex.lock(); - size_t nbytes; - const void *src = fifo_buffer_read(od->buffer, &nbytes); + auto src = od->buffer->Read(); + if (!src.IsEmpty()) { + if (src.size > buffer_size) + src.size = buffer_size; - if (src != NULL) { - if (nbytes > buffer_size) - nbytes = buffer_size; - - memcpy(buffer->mData, src, nbytes); - fifo_buffer_consume(od->buffer, nbytes); - } else - nbytes = 0; + memcpy(buffer->mData, src.data, src.size); + od->buffer->Consume(src.size); + } od->condition.signal(); od->mutex.unlock(); - buffer->mDataByteSize = nbytes; + buffer->mDataByteSize = src.size; unsigned i; for (i = 1; i < buffer_list->mNumberBuffers; ++i) { @@ -298,7 +295,7 @@ osx_output_cancel(struct audio_output *ao) OSXOutput *od = (OSXOutput *)ao; const ScopeLock protect(od->mutex); - fifo_buffer_clear(od->buffer); + od->buffer->Clear(); } static void @@ -309,7 +306,7 @@ osx_output_close(struct audio_output *ao) AudioOutputUnitStop(od->au); AudioUnitUninitialize(od->au); - fifo_buffer_free(od->buffer); + delete od->buffer; } static bool @@ -370,8 +367,8 @@ osx_output_open(struct audio_output *ao, AudioFormat &audio_format, } /* create a buffer of 1s */ - od->buffer = fifo_buffer_new(audio_format.sample_rate * - audio_format.GetFrameSize()); + od->buffer = new DynamicFifoBuffer<uint8_t>(audio_format.sample_rate * + audio_format.GetFrameSize()); status = AudioOutputUnitStart(od->au); if (status != 0) { @@ -393,23 +390,21 @@ osx_output_play(struct audio_output *ao, const void *chunk, size_t size, const ScopeLock protect(od->mutex); - void *dest; - size_t max_length; - + DynamicFifoBuffer<uint8_t>::Range dest; while (true) { - dest = fifo_buffer_write(od->buffer, &max_length); - if (dest != NULL) + dest = od->buffer->Write(); + if (!dest.IsEmpty()) break; /* wait for some free space in the buffer */ od->condition.wait(od->mutex); } - if (size > max_length) - size = max_length; + if (size > dest.size) + size = dest.size; - memcpy(dest, chunk, size); - fifo_buffer_append(od->buffer, size); + memcpy(dest.data, chunk, size); + od->buffer->Append(size); return size; } diff --git a/src/output/PulseOutputPlugin.hxx b/src/output/PulseOutputPlugin.hxx index 0ed8404bc..69d6c5f99 100644 --- a/src/output/PulseOutputPlugin.hxx +++ b/src/output/PulseOutputPlugin.hxx @@ -41,6 +41,6 @@ pulse_output_clear_mixer(PulseOutput *po, PulseMixer *pm); bool pulse_output_set_volume(PulseOutput *po, - const struct pa_cvolume *volume, Error &error); + const pa_cvolume *volume, Error &error); #endif diff --git a/src/pcm/ChannelsConverter.cxx b/src/pcm/ChannelsConverter.cxx new file mode 100644 index 000000000..8ffcbfe41 --- /dev/null +++ b/src/pcm/ChannelsConverter.cxx @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#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..4311b9671 --- /dev/null +++ b/src/pcm/ChannelsConverter.hxx @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_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..845fa2332 --- /dev/null +++ b/src/pcm/ConfiguredResampler.cxx @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "ConfiguredResampler.hxx" +#include "FallbackResampler.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigOption.hxx" +#include "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 (strcmp(converter, "soxr") == 0) { + selected_resampler = SelectedResampler::SOXR; + return true; + } +#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..6d12ee9c6 --- /dev/null +++ b/src/pcm/ConfiguredResampler.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_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..9f07d80dd --- /dev/null +++ b/src/pcm/Domain.cxx @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "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..170d7406d --- /dev/null +++ b/src/pcm/Domain.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef 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..a3b6b78ee --- /dev/null +++ b/src/pcm/FallbackResampler.cxx @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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..1b8d0377d --- /dev/null +++ b/src/pcm/FallbackResampler.hxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_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..8ff8b8940 --- /dev/null +++ b/src/pcm/FormatConverter.cxx @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "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..f5b13a0b0 --- /dev/null +++ b/src/pcm/FormatConverter.hxx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_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..ef80e08a5 --- /dev/null +++ b/src/pcm/GlueResampler.cxx @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "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..7bd923bab --- /dev/null +++ b/src/pcm/GlueResampler.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_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..586391e67 --- /dev/null +++ b/src/pcm/LibsamplerateResampler.cxx @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "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..0c1f613c8 --- /dev/null +++ b/src/pcm/LibsamplerateResampler.hxx @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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..5af9a978d 100644 --- a/src/pcm/PcmBuffer.cxx +++ b/src/pcm/PcmBuffer.cxx @@ -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..44a3ebf99 100644 --- a/src/pcm/PcmBuffer.hxx +++ b/src/pcm/PcmBuffer.hxx @@ -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..2e433e611 100644 --- a/src/pcm/PcmChannels.cxx +++ b/src/pcm/PcmChannels.cxx @@ -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..ca4e06a8c 100644 --- a/src/pcm/PcmChannels.hxx +++ b/src/pcm/PcmChannels.hxx @@ -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..6eecd3b01 100644 --- a/src/pcm/PcmConvert.cxx +++ b/src/pcm/PcmConvert.cxx @@ -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..586c303f2 100644 --- a/src/pcm/PcmConvert.hxx +++ b/src/pcm/PcmConvert.hxx @@ -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..d9d4d28ca 100644 --- a/src/pcm/PcmDither.cxx +++ b/src/pcm/PcmDither.cxx @@ -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..473a45ac3 100644 --- a/src/pcm/PcmDither.hxx +++ b/src/pcm/PcmDither.hxx @@ -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..5952cad7c 100644 --- a/src/pcm/PcmDsd.cxx +++ b/src/pcm/PcmDsd.cxx @@ -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..b9b6d51ee 100644 --- a/src/pcm/PcmDsd.hxx +++ b/src/pcm/PcmDsd.hxx @@ -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..833d84653 100644 --- a/src/pcm/PcmDsdUsb.cxx +++ b/src/pcm/PcmDsdUsb.cxx @@ -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/PcmFormat.cxx b/src/pcm/PcmFormat.cxx index 4565c71c6..ff8f2a80c 100644 --- a/src/pcm/PcmFormat.cxx +++ b/src/pcm/PcmFormat.cxx @@ -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..378ac8e2a 100644 --- a/src/pcm/PcmFormat.hxx +++ b/src/pcm/PcmFormat.hxx @@ -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..c1ffd8633 100644 --- a/src/pcm/PcmMix.cxx +++ b/src/pcm/PcmMix.cxx @@ -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..a6657a979 100644 --- a/src/pcm/PcmMix.hxx +++ b/src/pcm/PcmMix.hxx @@ -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/PcmPrng.hxx b/src/pcm/PcmPrng.hxx index 0c823250d..73b1456a8 100644 --- a/src/pcm/PcmPrng.hxx +++ b/src/pcm/PcmPrng.hxx @@ -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..9c741e55b 100644 --- a/src/pcm/PcmUtils.hxx +++ b/src/pcm/PcmUtils.hxx @@ -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..a74ef4e77 --- /dev/null +++ b/src/pcm/Resampler.hxx @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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..e82ae1481 --- /dev/null +++ b/src/pcm/SoxrResampler.cxx @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "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"); + +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 = soxr_create(af.sample_rate, new_sample_rate, + af.channels, &e, + nullptr, nullptr, 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/SongPointer.hxx b/src/pcm/SoxrResampler.hxx index ded3b3e1d..69c173741 100644 --- a/src/SongPointer.hxx +++ b/src/pcm/SoxrResampler.hxx @@ -17,47 +17,31 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_SONG_POINTER_HXX -#define MPD_SONG_POINTER_HXX +#ifndef MPD_PCM_SOXR_RESAMPLER_HXX +#define MPD_PCM_SOXR_RESAMPLER_HXX -#include "Song.hxx" +#include "Resampler.hxx" +#include "PcmBuffer.hxx" -#include <utility> +struct AudioFormat; -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; +/** + * A resampler using soxr. + */ +class SoxrPcmResampler final : public PcmResampler { + struct soxr *soxr; - SongPointer &operator=(SongPointer &&other) { - std::swap(song, other.song); - return *this; - } + unsigned channels; + float ratio; - operator const Song *() const { - return song; - } + PcmBuffer buffer; - Song *Steal() { - auto result = song; - song = nullptr; - return result; - } +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/Traits.hxx b/src/pcm/Traits.hxx new file mode 100644 index 000000000..ac1ac532d --- /dev/null +++ b/src/pcm/Traits.hxx @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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..988d3fbbf --- /dev/null +++ b/src/pcm/Volume.cxx @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "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: + case SampleFormat::DSD: + 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; + } + + 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: + case SampleFormat::DSD: + 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; + } + + return { data, src.size }; +} diff --git a/src/pcm/Volume.hxx b/src/pcm/Volume.hxx new file mode 100644 index 000000000..6a6f6196d --- /dev/null +++ b/src/pcm/Volume.hxx @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * 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 index 94198b8c3..4e48dbf8d 100644 --- a/src/playlist/AsxPlaylistPlugin.cxx +++ b/src/playlist/AsxPlaylistPlugin.cxx @@ -21,21 +21,13 @@ #include "AsxPlaylistPlugin.hxx" #include "PlaylistPlugin.hxx" #include "MemorySongEnumerator.hxx" -#include "InputStream.hxx" #include "Song.hxx" -#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" #include "util/ASCII.hxx" #include "util/Error.hxx" -#include "util/Domain.hxx" +#include "Expat.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. */ @@ -44,7 +36,7 @@ struct AsxParser { * The list of songs (in reverse order because that's faster * while adding). */ - std::forward_list<SongPointer> songs; + std::forward_list<DetachedSong> songs; /** * The current position in the XML file. @@ -58,36 +50,23 @@ struct AsxParser { * valid if state==ENTRY. TAG_NUM_OF_ITEM_TYPES means there * is no (known) tag. */ - TagType tag; + TagType tag_type; /** - * The current song. It is allocated after the "location" - * element. + * The current song URI. It is set by the "ref" element. */ - Song *song; + std::string location; + + TagBuilder tag_builder; 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) +static void XMLCALL +asx_start_element(void *user_data, const XML_Char *element_name, + const XML_Char **atts) { AsxParser *parser = (AsxParser *)user_data; @@ -95,48 +74,31 @@ asx_start_element(gcc_unused GMarkupParseContext *context, case AsxParser::ROOT: if (StringEqualsCaseASCII(element_name, "entry")) { parser->state = AsxParser::ENTRY; - parser->song = Song::NewRemote("asx:"); - parser->tag = TAG_NUM_OF_ITEM_TYPES; + parser->location.clear(); + parser->tag_type = 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; - } + 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 = TAG_ARTIST; + parser->tag_type = TAG_ARTIST; else if (StringEqualsCaseASCII(element_name, "title")) - parser->tag = TAG_TITLE; + parser->tag_type = TAG_TITLE; break; } } -static void -asx_end_element(gcc_unused GMarkupParseContext *context, - const gchar *element_name, - gpointer user_data, gcc_unused GError **error) +static void XMLCALL +asx_end_element(void *user_data, const XML_Char *element_name) { AsxParser *parser = (AsxParser *)user_data; @@ -146,23 +108,20 @@ asx_end_element(gcc_unused GMarkupParseContext *context, 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(); + if (!parser->location.empty()) + parser->songs.emplace_front(std::move(parser->location), + parser->tag_builder.Commit()); parser->state = AsxParser::ROOT; } else - parser->tag = TAG_NUM_OF_ITEM_TYPES; + parser->tag_type = 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) +static void XMLCALL +asx_char_data(void *user_data, const XML_Char *s, int len) { AsxParser *parser = (AsxParser *)user_data; @@ -171,34 +130,13 @@ asx_text(gcc_unused GMarkupParseContext *context, 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); - } + if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES) + parser->tag_builder.AddItem(parser->tag_type, s, 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 * @@ -208,58 +146,21 @@ 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); + { + 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; } } - 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; + return new MemorySongEnumerator(std::move(parser.songs)); } static const char *const asx_suffixes[] = { diff --git a/src/playlist/CuePlaylistPlugin.cxx b/src/playlist/CuePlaylistPlugin.cxx index 42a43bbad..1da76d372 100644 --- a/src/playlist/CuePlaylistPlugin.cxx +++ b/src/playlist/CuePlaylistPlugin.cxx @@ -21,13 +21,10 @@ #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> +#include <string> class CuePlaylist final : public SongEnumerator { InputStream &is; @@ -39,7 +36,7 @@ class CuePlaylist final : public SongEnumerator { :is(_is), tis(is) { } - virtual Song *NextSong() override; + virtual DetachedSong *NextSong() override; }; static SongEnumerator * @@ -48,10 +45,10 @@ cue_playlist_open_stream(InputStream &is) return new CuePlaylist(is); } -Song * +DetachedSong * CuePlaylist::NextSong() { - Song *song = parser.Get(); + DetachedSong *song = parser.Get(); if (song != nullptr) return song; diff --git a/src/playlist/DespotifyPlaylistPlugin.cxx b/src/playlist/DespotifyPlaylistPlugin.cxx index a1a865c08..d73c7fe72 100644 --- a/src/playlist/DespotifyPlaylistPlugin.cxx +++ b/src/playlist/DespotifyPlaylistPlugin.cxx @@ -23,7 +23,7 @@ #include "PlaylistPlugin.hxx" #include "MemorySongEnumerator.hxx" #include "tag/Tag.hxx" -#include "Song.hxx" +#include "DetachedSong.hxx" #include "Log.hxx" extern "C" { @@ -34,10 +34,9 @@ extern "C" { #include <stdlib.h> static void -add_song(std::forward_list<SongPointer> &songs, struct ds_track *track) +add_song(std::forward_list<DetachedSong> &songs, ds_track &track) { const char *dsp_scheme = despotify_playlist_plugin.schemes[0]; - Song *song; char uri[128]; char *ds_uri; @@ -45,35 +44,32 @@ add_song(std::forward_list<SongPointer> &songs, struct ds_track *track) snprintf(uri, sizeof(uri), "%s://", dsp_scheme); ds_uri = uri + strlen(dsp_scheme) + 3; - if (despotify_track_to_uri(track, ds_uri) != ds_uri) { + 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); + "Can't add track %s", track.title); return; } - song = Song::NewRemote(uri); - song->tag = mpd_despotify_tag_from_track(track); - - songs.emplace_front(song); + songs.emplace_front(uri, mpd_despotify_tag_from_track(track)); } static bool parse_track(struct despotify_session *session, - std::forward_list<SongPointer> &songs, + 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); + add_song(songs, *track); return true; } static bool parse_playlist(struct despotify_session *session, - std::forward_list<SongPointer> &songs, + std::forward_list<DetachedSong> &songs, struct ds_link *link) { ds_playlist *playlist = despotify_link_get_playlist(session, link); @@ -82,7 +78,7 @@ parse_playlist(struct despotify_session *session, for (ds_track *track = playlist->tracks; track != nullptr; track = track->next) - add_song(songs, track); + add_song(songs, *track); return true; } @@ -103,7 +99,7 @@ despotify_playlist_open_uri(const char *url, return nullptr; } - std::forward_list<SongPointer> songs; + std::forward_list<DetachedSong> songs; bool parse_result; switch (link->type) { diff --git a/src/playlist/EmbeddedCuePlaylistPlugin.cxx b/src/playlist/EmbeddedCuePlaylistPlugin.cxx index d758650eb..77df51778 100644 --- a/src/playlist/EmbeddedCuePlaylistPlugin.cxx +++ b/src/playlist/EmbeddedCuePlaylistPlugin.cxx @@ -27,18 +27,16 @@ #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 "DetachedSong.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 { @@ -71,7 +69,7 @@ public: delete parser; } - virtual Song *NextSong() override; + virtual DetachedSong *NextSong() override; }; static void @@ -95,7 +93,7 @@ embcue_playlist_open_uri(const char *uri, gcc_unused Mutex &mutex, gcc_unused Cond &cond) { - if (!PathTraits::IsAbsoluteUTF8(uri)) + if (!PathTraitsUTF8::IsAbsolute(uri)) /* only local files supported */ return nullptr; @@ -105,7 +103,7 @@ embcue_playlist_open_uri(const char *uri, const auto playlist = new EmbeddedCuePlaylist(); - tag_file_scan(path_fs, &embcue_tag_handler, playlist); + 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()) @@ -118,7 +116,7 @@ embcue_playlist_open_uri(const char *uri, return nullptr; } - playlist->filename = PathTraits::GetBaseUTF8(uri); + playlist->filename = PathTraitsUTF8::GetBase(uri); playlist->next = &playlist->cuesheet[0]; playlist->parser = new CueParser(); @@ -126,10 +124,10 @@ embcue_playlist_open_uri(const char *uri, return playlist; } -Song * +DetachedSong * EmbeddedCuePlaylist::NextSong() { - Song *song = parser->Get(); + DetachedSong *song = parser->Get(); if (song != nullptr) return song; @@ -147,14 +145,16 @@ EmbeddedCuePlaylist::NextSong() parser->Feed(line); song = parser->Get(); - if (song != nullptr) - return song->ReplaceURI(filename.c_str()); + if (song != nullptr) { + song->SetURI(filename); + return song; + } } parser->Finish(); song = parser->Get(); if (song != nullptr) - song = song->ReplaceURI(filename.c_str()); + song->SetURI(filename); return song; } diff --git a/src/playlist/ExtM3uPlaylistPlugin.cxx b/src/playlist/ExtM3uPlaylistPlugin.cxx index 8d260fec7..51211988c 100644 --- a/src/playlist/ExtM3uPlaylistPlugin.cxx +++ b/src/playlist/ExtM3uPlaylistPlugin.cxx @@ -21,13 +21,12 @@ #include "ExtM3uPlaylistPlugin.hxx" #include "PlaylistPlugin.hxx" #include "SongEnumerator.hxx" -#include "Song.hxx" +#include "DetachedSong.hxx" #include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" #include "util/StringUtil.hxx" #include "TextInputStream.hxx" -#include <glib.h> - #include <string.h> #include <stdlib.h> @@ -45,7 +44,7 @@ public: strcmp(line.c_str(), "#EXTM3U") == 0; } - virtual Song *NextSong() override; + virtual DetachedSong *NextSong() override; }; static SongEnumerator * @@ -74,7 +73,6 @@ extm3u_parse_tag(const char *line) long duration; char *endptr; const char *name; - Tag *tag; duration = strtol(line, &endptr, 10); if (endptr[0] != ',') @@ -91,35 +89,34 @@ extm3u_parse_tag(const char *line) object */ return NULL; - tag = new Tag(); - tag->time = duration; + 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); + tag.AddItem(TAG_NAME, name); - return tag; + return tag.CommitNew(); } -Song * +DetachedSong * 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:")) { + if (StringStartsWith(line_s, "#EXTINF:")) { delete tag; tag = extm3u_parse_tag(line_s + 8); continue; @@ -128,8 +125,8 @@ ExtM3uPlaylist::NextSong() line_s = strchug_fast(line_s); } while (line_s[0] == '#' || *line_s == 0); - song = Song::NewRemote(line_s); - song->tag = tag; + DetachedSong *song = new DetachedSong(line_s, std::move(*tag)); + delete tag; return song; } diff --git a/src/playlist/M3uPlaylistPlugin.cxx b/src/playlist/M3uPlaylistPlugin.cxx index 3f99bdfdf..09f2c91cb 100644 --- a/src/playlist/M3uPlaylistPlugin.cxx +++ b/src/playlist/M3uPlaylistPlugin.cxx @@ -21,7 +21,7 @@ #include "M3uPlaylistPlugin.hxx" #include "PlaylistPlugin.hxx" #include "SongEnumerator.hxx" -#include "Song.hxx" +#include "DetachedSong.hxx" #include "util/StringUtil.hxx" #include "TextInputStream.hxx" @@ -33,7 +33,7 @@ public: :tis(is) { } - virtual Song *NextSong() override; + virtual DetachedSong *NextSong() override; }; static SongEnumerator * @@ -42,7 +42,7 @@ m3u_open_stream(InputStream &is) return new M3uPlaylist(is); } -Song * +DetachedSong * M3uPlaylist::NextSong() { std::string line; @@ -56,7 +56,7 @@ M3uPlaylist::NextSong() line_s = strchug_fast(line_s); } while (line_s[0] == '#' || *line_s == 0); - return Song::NewRemote(line_s); + return new DetachedSong(line_s); } static const char *const m3u_suffixes[] = { diff --git a/src/playlist/PlsPlaylistPlugin.cxx b/src/playlist/PlsPlaylistPlugin.cxx index 99be3ad35..103451dfe 100644 --- a/src/playlist/PlsPlaylistPlugin.cxx +++ b/src/playlist/PlsPlaylistPlugin.cxx @@ -22,8 +22,8 @@ #include "PlaylistPlugin.hxx" #include "MemorySongEnumerator.hxx" #include "InputStream.hxx" -#include "Song.hxx" -#include "tag/Tag.hxx" +#include "DetachedSong.hxx" +#include "tag/TagBuilder.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" @@ -32,13 +32,14 @@ #include <string> +#include <stdio.h> + static constexpr Domain pls_domain("pls"); static void -pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs) +pls_parser(GKeyFile *keyfile, std::forward_list<DetachedSong> &songs) { gchar *value; - int length; GError *error = nullptr; int num_entries = g_key_file_get_integer(keyfile, "playlist", "NumberOfEntries", &error); @@ -57,13 +58,11 @@ pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs) } } - while (num_entries > 0) { - Song *song; - + for (; num_entries > 0; --num_entries) { char key[64]; sprintf(key, "File%u", num_entries); - value = g_key_file_get_string(keyfile, "playlist", key, - &error); + char *uri = g_key_file_get_string(keyfile, "playlist", key, + &error); if(error) { FormatError(pls_domain, "Invalid PLS entry %s: '%s'", key, error->message); @@ -71,36 +70,24 @@ pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs) return; } - song = Song::NewRemote(value); - g_free(value); + TagBuilder tag; 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; + nullptr); + if (value != nullptr) + tag.AddItem(TAG_TITLE, value); + 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; + int length = g_key_file_get_integer(keyfile, "playlist", key, + nullptr); + if (length > 0) + tag.SetTime(length); - songs.emplace_front(song); - num_entries--; + songs.emplace_front(uri, tag.Commit()); + g_free(uri); } } @@ -110,15 +97,12 @@ 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); + char buffer[1024]; + size_t nbytes = is.LockRead(buffer, sizeof(buffer), error2); if (nbytes == 0) { if (error2.IsDefined()) { LogError(error2); @@ -137,12 +121,10 @@ pls_open_stream(InputStream &is) 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) { + 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); @@ -150,7 +132,7 @@ pls_open_stream(InputStream &is) return nullptr; } - std::forward_list<SongPointer> songs; + std::forward_list<DetachedSong> songs; pls_parser(keyfile, songs); g_key_file_free(keyfile); diff --git a/src/playlist/RssPlaylistPlugin.cxx b/src/playlist/RssPlaylistPlugin.cxx index e2a44bfd3..604f99baf 100644 --- a/src/playlist/RssPlaylistPlugin.cxx +++ b/src/playlist/RssPlaylistPlugin.cxx @@ -21,21 +21,13 @@ #include "RssPlaylistPlugin.hxx" #include "PlaylistPlugin.hxx" #include "MemorySongEnumerator.hxx" -#include "InputStream.hxx" #include "Song.hxx" -#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" #include "util/ASCII.hxx" #include "util/Error.hxx" -#include "util/Domain.hxx" +#include "Expat.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. */ @@ -44,7 +36,7 @@ struct RssParser { * The list of songs (in reverse order because that's faster * while adding). */ - std::forward_list<SongPointer> songs; + std::forward_list<DetachedSong> songs; /** * The current position in the XML file. @@ -58,35 +50,23 @@ struct RssParser { * valid if state==ITEM. TAG_NUM_OF_ITEM_TYPES means there * is no (known) tag. */ - TagType tag; + TagType tag_type; /** - * The current song. It is allocated after the "location" + * The current song URI. It is set by the "enclosure" * element. */ - Song *song; + std::string location; + + TagBuilder tag_builder; 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) +static void XMLCALL +rss_start_element(void *user_data, const XML_Char *element_name, + const XML_Char **atts) { RssParser *parser = (RssParser *)user_data; @@ -94,46 +74,29 @@ rss_start_element(gcc_unused GMarkupParseContext *context, case RssParser::ROOT: if (StringEqualsCaseASCII(element_name, "item")) { parser->state = RssParser::ITEM; - parser->song = Song::NewRemote("rss:"); - parser->tag = TAG_NUM_OF_ITEM_TYPES; + parser->location.clear(); + parser->tag_type = 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; - } + const char *href = + ExpatParser::GetAttributeCase(atts, "url"); + if (href != nullptr) + parser->location = href; } else if (StringEqualsCaseASCII(element_name, "title")) - parser->tag = TAG_TITLE; + parser->tag_type = TAG_TITLE; else if (StringEqualsCaseASCII(element_name, "itunes:author")) - parser->tag = TAG_ARTIST; + parser->tag_type = TAG_ARTIST; break; } } -static void -rss_end_element(gcc_unused GMarkupParseContext *context, - const gchar *element_name, - gpointer user_data, gcc_unused GError **error) +static void XMLCALL +rss_end_element(void *user_data, const XML_Char *element_name) { RssParser *parser = (RssParser *)user_data; @@ -143,23 +106,20 @@ rss_end_element(gcc_unused GMarkupParseContext *context, 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(); + if (!parser->location.empty()) + parser->songs.emplace_front(std::move(parser->location), + parser->tag_builder.Commit()); parser->state = RssParser::ROOT; } else - parser->tag = TAG_NUM_OF_ITEM_TYPES; + parser->tag_type = 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) +static void XMLCALL +rss_char_data(void *user_data, const XML_Char *s, int len) { RssParser *parser = (RssParser *)user_data; @@ -168,34 +128,13 @@ rss_text(gcc_unused GMarkupParseContext *context, 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); - } + if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES) + parser->tag_builder.AddItem(parser->tag_type, s, 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 * @@ -205,58 +144,21 @@ 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; - } + { + ExpatParser expat(&parser); + expat.SetElementHandler(rss_start_element, rss_end_element); + expat.SetCharacterDataHandler(rss_char_data); - 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); + Error error; + if (!expat.Parse(is, error)) { + LogError(error); 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; + return new MemorySongEnumerator(std::move(parser.songs)); } static const char *const rss_suffixes[] = { diff --git a/src/playlist/SoundCloudPlaylistPlugin.cxx b/src/playlist/SoundCloudPlaylistPlugin.cxx index f6797b14d..50a9cb214 100644 --- a/src/playlist/SoundCloudPlaylistPlugin.cxx +++ b/src/playlist/SoundCloudPlaylistPlugin.cxx @@ -24,7 +24,8 @@ #include "ConfigData.hxx" #include "InputStream.hxx" #include "Song.hxx" -#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" +#include "util/StringUtil.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" @@ -45,7 +46,8 @@ static constexpr Domain soundcloud_domain("soundcloud"); static bool soundcloud_init(const config_param ¶m) { - soundcloud_config.apikey = param.GetBlockValue("apikey", ""); + // 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 " @@ -62,19 +64,20 @@ soundcloud_init(const config_param ¶m) * @return Constructed URL. Must be freed with g_free. */ static char * -soundcloud_resolve(const char* uri) { +soundcloud_resolve(const char* uri) +{ char *u, *ru; - if (g_str_has_prefix(uri, "http://")) { + if (StringStartsWith(uri, "https://")) { u = g_strdup(uri); - } else if (g_str_has_prefix(uri, "soundcloud.com")) { - u = g_strconcat("http://", uri, nullptr); + } 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("http://soundcloud.com/", uri, nullptr); + u = g_strconcat("https://soundcloud.com/", uri, nullptr); } - ru = g_strconcat("http://api.soundcloud.com/resolve.json?url=", + ru = g_strconcat("https://api.soundcloud.com/resolve.json?url=", u, "&client_id=", soundcloud_config.apikey.c_str(), nullptr); g_free(u); @@ -105,15 +108,16 @@ struct parse_data { char* title; int got_url; /* nesting level of last stream_url */ - std::forward_list<SongPointer> songs; + std::forward_list<DetachedSong> songs; }; -static int handle_integer(void *ctx, - long +static int +handle_integer(void *ctx, + long #ifndef HAVE_YAJL1 - long + long #endif - intval) + intval) { struct parse_data *data = (struct parse_data *) ctx; @@ -128,26 +132,25 @@ static int handle_integer(void *ctx, return 1; } -static int handle_string(void *ctx, const unsigned char* stringval, +static int +handle_string(void *ctx, const unsigned char* stringval, #ifdef HAVE_YAJL1 - unsigned int + unsigned int #else - size_t + size_t #endif - stringlen) + 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); + g_free(data->title); data->title = g_strndup(s, stringlen); break; case Stream_URL: - if (data->stream_url != nullptr) - g_free(data->stream_url); + g_free(data->stream_url); data->stream_url = g_strndup(s, stringlen); data->got_url = 1; break; @@ -158,13 +161,14 @@ static int handle_string(void *ctx, const unsigned char* stringval, return 1; } -static int handle_mapkey(void *ctx, const unsigned char* stringval, +static int +handle_mapkey(void *ctx, const unsigned char* stringval, #ifdef HAVE_YAJL1 - unsigned int + unsigned int #else - size_t + size_t #endif - stringlen) + stringlen) { struct parse_data *data = (struct parse_data *) ctx; @@ -181,7 +185,8 @@ static int handle_mapkey(void *ctx, const unsigned char* stringval, return 1; } -static int handle_start_map(void *ctx) +static int +handle_start_map(void *ctx) { struct parse_data *data = (struct parse_data *) ctx; @@ -191,7 +196,8 @@ static int handle_start_map(void *ctx) return 1; } -static int handle_end_map(void *ctx) +static int +handle_end_map(void *ctx) { struct parse_data *data = (struct parse_data *) ctx; @@ -206,21 +212,16 @@ static int handle_end_map(void *ctx) /* got_url == 1, track finished, make it into a song */ data->got_url = 0; - Song *s; - char *u; + char *u = g_strconcat(data->stream_url, "?client_id=", + soundcloud_config.apikey.c_str(), nullptr); - 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; + TagBuilder tag; + tag.SetTime(data->duration / 1000); if (data->title != nullptr) - t->AddItem(TAG_NAME, data->title); - s->tag = t; + tag.AddItem(TAG_NAME, data->title); - data->songs.emplace_front(s); + data->songs.emplace_front(u, tag.Commit()); + g_free(u); return 1; } @@ -249,12 +250,9 @@ 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); + InputStream *input_stream = InputStream::OpenReady(url, mutex, cond, + error); if (input_stream == nullptr) { if (error.IsDefined()) LogError(error); @@ -262,12 +260,13 @@ soundcloud_parse_json(const char *url, yajl_handle hand, } mutex.lock(); - input_stream->WaitReady(); 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) { @@ -318,80 +317,62 @@ soundcloud_parse_json(const char *url, yajl_handle hand, * 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; - } + assert(memcmp(uri, "soundcloud://", 13) == 0); + uri += 13; char *u = nullptr; - if (strcmp(arg, "track") == 0) { - u = g_strconcat("http://api.soundcloud.com/tracks/", + 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 (strcmp(arg, "playlist") == 0) { - u = g_strconcat("http://api.soundcloud.com/playlists/", + } 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 (strcmp(arg, "url") == 0) { + } 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); } - 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); + yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, nullptr, + &data); #else - hand = yajl_alloc(&parse_callbacks, nullptr, (void *) &data); + 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); - if (data.title != nullptr) - g_free(data.title); - if (data.stream_url != nullptr) - g_free(data.stream_url); + g_free(data.title); + g_free(data.stream_url); if (ret == -1) return nullptr; diff --git a/src/playlist/XspfPlaylistPlugin.cxx b/src/playlist/XspfPlaylistPlugin.cxx index dcfab5a80..2157dd678 100644 --- a/src/playlist/XspfPlaylistPlugin.cxx +++ b/src/playlist/XspfPlaylistPlugin.cxx @@ -21,15 +21,14 @@ #include "XspfPlaylistPlugin.hxx" #include "PlaylistPlugin.hxx" #include "MemorySongEnumerator.hxx" +#include "DetachedSong.hxx" #include "InputStream.hxx" -#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" +#include "Expat.hxx" #include "Log.hxx" -#include <glib.h> - -#include <assert.h> #include <string.h> static constexpr Domain xspf_domain("xspf"); @@ -42,7 +41,7 @@ struct XspfParser { * The list of songs (in reverse order because that's faster * while adding). */ - std::forward_list<SongPointer> songs; + std::forward_list<DetachedSong> songs; /** * The current position in the XML file. @@ -57,24 +56,22 @@ struct XspfParser { * valid if state==TRACK. TAG_NUM_OF_ITEM_TYPES means there * is no (known) tag. */ - TagType tag; + TagType tag_type; /** - * The current song. It is allocated after the "location" - * element. + * The current song URI. It is set by the "location" element. */ - Song *song; + std::string location; + + TagBuilder tag_builder; 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) +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; @@ -94,8 +91,8 @@ xspf_start_element(gcc_unused GMarkupParseContext *context, case XspfParser::TRACKLIST: if (strcmp(element_name, "track") == 0) { parser->state = XspfParser::TRACK; - parser->song = nullptr; - parser->tag = TAG_NUM_OF_ITEM_TYPES; + parser->location.clear(); + parser->tag_type = TAG_NUM_OF_ITEM_TYPES; } break; @@ -104,17 +101,17 @@ xspf_start_element(gcc_unused GMarkupParseContext *context, if (strcmp(element_name, "location") == 0) parser->state = XspfParser::LOCATION; else if (strcmp(element_name, "title") == 0) - parser->tag = TAG_TITLE; + 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 = TAG_ARTIST; + parser->tag_type = TAG_ARTIST; else if (strcmp(element_name, "annotation") == 0) - parser->tag = TAG_COMMENT; + parser->tag_type = TAG_COMMENT; else if (strcmp(element_name, "album") == 0) - parser->tag = TAG_ALBUM; + parser->tag_type = TAG_ALBUM; else if (strcmp(element_name, "trackNum") == 0) - parser->tag = TAG_TRACK; + parser->tag_type = TAG_TRACK; break; @@ -123,10 +120,8 @@ xspf_start_element(gcc_unused GMarkupParseContext *context, } } -static void -xspf_end_element(gcc_unused GMarkupParseContext *context, - const gchar *element_name, - gpointer user_data, gcc_unused GError **error) +static void XMLCALL +xspf_end_element(void *user_data, const XML_Char *element_name) { XspfParser *parser = (XspfParser *)user_data; @@ -148,12 +143,13 @@ xspf_end_element(gcc_unused GMarkupParseContext *context, case XspfParser::TRACK: if (strcmp(element_name, "track") == 0) { - if (parser->song != nullptr) - parser->songs.emplace_front(parser->song); + if (!parser->location.empty()) + parser->songs.emplace_front(std::move(parser->location), + parser->tag_builder.Commit()); parser->state = XspfParser::TRACKLIST; } else - parser->tag = TAG_NUM_OF_ITEM_TYPES; + parser->tag_type = TAG_NUM_OF_ITEM_TYPES; break; @@ -163,10 +159,8 @@ xspf_end_element(gcc_unused GMarkupParseContext *context, } } -static void -xspf_text(gcc_unused GMarkupParseContext *context, - const gchar *text, gsize text_len, - gpointer user_data, gcc_unused GError **error) +static void XMLCALL +xspf_char_data(void *user_data, const XML_Char *s, int len) { XspfParser *parser = (XspfParser *)user_data; @@ -177,43 +171,19 @@ xspf_text(gcc_unused GMarkupParseContext *context, 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); - } + 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: - if (parser->song == nullptr) { - char *uri = g_strndup(text, text_len); - parser->song = Song::NewRemote(uri); - g_free(uri); - } + parser->location.assign(s, len); 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 * @@ -223,58 +193,21 @@ 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); + { + 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; } } - 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; + return new MemorySongEnumerator(std::move(parser.songs)); } static const char *const xspf_suffixes[] = { diff --git a/src/system/Clock.cxx b/src/system/Clock.cxx index 347997a44..4d89b9b89 100644 --- a/src/system/Clock.cxx +++ b/src/system/Clock.cxx @@ -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..1c3651a99 100644 --- a/src/system/Clock.hxx +++ b/src/system/Clock.hxx @@ -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.hxx b/src/system/EPollFD.hxx index 41f7ec377..a6ea51d18 100644 --- a/src/system/EPollFD.hxx +++ b/src/system/EPollFD.hxx @@ -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/FatalError.cxx b/src/system/FatalError.cxx index f02b4b581..b54a677f2 100644 --- a/src/system/FatalError.cxx +++ b/src/system/FatalError.cxx @@ -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..4ef290714 100644 --- a/src/system/FatalError.hxx +++ b/src/system/FatalError.hxx @@ -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..61f52933f --- /dev/null +++ b/src/system/PeriodClock.hxx @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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..0cfb754ec 100644 --- a/src/system/Resolver.cxx +++ b/src/system/Resolver.cxx @@ -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..044d3f96a 100644 --- a/src/system/Resolver.hxx +++ b/src/system/Resolver.hxx @@ -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..d5953056d 100644 --- a/src/system/SignalFD.cxx +++ b/src/system/SignalFD.cxx @@ -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/SocketError.cxx b/src/system/SocketError.cxx index 315a86e1f..bb1fa5abf 100644 --- a/src/system/SocketError.cxx +++ b/src/system/SocketError.cxx @@ -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..28e1bace0 100644 --- a/src/system/SocketError.hxx +++ b/src/system/SocketError.hxx @@ -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..ae98f33ff 100644 --- a/src/system/SocketUtil.cxx +++ b/src/system/SocketUtil.cxx @@ -22,8 +22,6 @@ #include "SocketError.hxx" #include "fd_util.h" -#include <glib.h> - #include <unistd.h> #ifndef WIN32 diff --git a/src/tag/Aiff.cxx b/src/tag/Aiff.cxx index 73e46e49f..84c386678 100644 --- a/src/tag/Aiff.cxx +++ b/src/tag/Aiff.cxx @@ -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/ApeLoader.cxx b/src/tag/ApeLoader.cxx index 8251efe10..febe4467d 100644 --- a/src/tag/ApeLoader.cxx +++ b/src/tag/ApeLoader.cxx @@ -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/ApeTag.hxx b/src/tag/ApeTag.hxx index e35edc381..d2c0365c7 100644 --- a/src/tag/ApeTag.hxx +++ b/src/tag/ApeTag.hxx @@ -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..0f8f265fc 100644 --- a/src/tag/Riff.cxx +++ b/src/tag/Riff.cxx @@ -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/Tag.cxx b/src/tag/Tag.cxx index 6bf070429..d6b3abe55 100644 --- a/src/tag/Tag.cxx +++ b/src/tag/Tag.cxx @@ -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) { 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..480115c5b 100644 --- a/src/tag/Tag.hxx +++ b/src/tag/Tag.hxx @@ -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> @@ -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..462f5c49a 100644 --- a/src/tag/TagBuilder.cxx +++ b/src/tag/TagBuilder.cxx @@ -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..984261ded 100644 --- a/src/tag/TagBuilder.hxx +++ b/src/tag/TagBuilder.hxx @@ -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 96fd1847f..5c81fa603 100644 --- a/src/tag/TagConfig.cxx +++ b/src/tag/TagConfig.cxx @@ -24,13 +24,14 @@ #include "ConfigGlobal.hxx" #include "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..3bc86cc40 100644 --- a/src/tag/TagConfig.hxx +++ b/src/tag/TagConfig.hxx @@ -20,8 +20,6 @@ #ifndef MPD_TAG_CONFIG_HXX #define MPD_TAG_CONFIG_HXX -#include "TagType.h" - void TagLoadConfig(); diff --git a/src/tag/TagId3.cxx b/src/tag/TagId3.cxx index df70a95e5..0397c0039 100644 --- a/src/tag/TagId3.cxx +++ b/src/tag/TagId3.cxx @@ -21,7 +21,6 @@ #include "TagId3.hxx" #include "TagHandler.hxx" #include "TagTable.hxx" -#include "Tag.hxx" #include "TagBuilder.hxx" #include "util/Error.hxx" #include "util/Domain.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..aa564e946 100644 --- a/src/tag/TagId3.hxx +++ b/src/tag/TagId3.hxx @@ -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/TagPool.cxx b/src/tag/TagPool.cxx index cc28ea9a6..409edb662 100644 --- a/src/tag/TagPool.cxx +++ b/src/tag/TagPool.cxx @@ -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/TagRva2.cxx b/src/tag/TagRva2.cxx index 204001aa7..442695581 100644 --- a/src/tag/TagRva2.cxx +++ b/src/tag/TagRva2.cxx @@ -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..a1d67a777 100644 --- a/src/tag/TagRva2.hxx +++ b/src/tag/TagRva2.hxx @@ -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/TagString.cxx b/src/tag/TagString.cxx index 3e8d8c1b0..9d2bd68ec 100644 --- a/src/tag/TagString.cxx +++ b/src/tag/TagString.cxx @@ -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/TagTable.cxx b/src/tag/TagTable.cxx index b51bc35ba..5224404a9 100644 --- a/src/tag/TagTable.cxx +++ b/src/tag/TagTable.cxx @@ -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..423a1e6ad 100644 --- a/src/tag/TagTable.hxx +++ b/src/tag/TagTable.hxx @@ -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/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/growing_fifo.h b/src/util/Cast.hxx index 723c3b3ff..69172e6de 100644 --- a/src/util/growing_fifo.h +++ b/src/util/Cast.hxx @@ -1,6 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org + * 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 @@ -28,46 +27,32 @@ * 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 +#ifndef CAST_HXX +#define CAST_HXX #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) + * Offset the given pointer by the specified number of bytes. */ -void * -growing_fifo_write(struct fifo_buffer **buffer_p, size_t length); +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)); +} /** - * A helper function that combines growing_fifo_write(), memcpy(), - * fifo_buffer_append(). + * Cast the given pointer to a struct member to its parent structure. */ -void -growing_fifo_append(struct fifo_buffer **buffer_p, - const void *data, size_t length); +#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..ea6d41860 100644 --- a/src/util/FormatString.cxx +++ b/src/util/FormatString.cxx @@ -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/OptionDef.hxx b/src/util/OptionDef.hxx new file mode 100644 index 000000000..ca9e6083f --- /dev/null +++ b/src/util/OptionDef.hxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_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..b8addb9a2 --- /dev/null +++ b/src/util/OptionParser.cxx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "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..74091bc29 --- /dev/null +++ b/src/util/OptionParser.hxx @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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 a3659b8f4..5e86503e8 100644 --- a/src/util/PeakBuffer.cxx +++ b/src/util/PeakBuffer.cxx @@ -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; @@ -131,13 +123,11 @@ PeakBuffer::Append(const void *data, size_t length) } if (peak_buffer == nullptr && 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..66f4c1a9f 100644 --- a/src/util/PeakBuffer.hxx +++ b/src/util/PeakBuffer.hxx @@ -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/SplitString.cxx b/src/util/SplitString.cxx new file mode 100644 index 000000000..c8314f28f --- /dev/null +++ b/src/util/SplitString.cxx @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "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..a8512b13c --- /dev/null +++ b/src/util/SplitString.hxx @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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..512a75f5c 100644 --- a/src/util/StringUtil.cxx +++ b/src/util/StringUtil.cxx @@ -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..f25160107 100644 --- a/src/util/StringUtil.hxx +++ b/src/util/StringUtil.hxx @@ -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..a57f5b20c 100644 --- a/src/util/Tokenizer.cxx +++ b/src/util/Tokenizer.cxx @@ -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/UriUtil.cxx b/src/util/UriUtil.cxx index 2609db2cf..174c977e1 100644 --- a/src/util/UriUtil.cxx +++ b/src/util/UriUtil.cxx @@ -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..20e468103 100644 --- a/src/util/UriUtil.hxx +++ b/src/util/UriUtil.hxx @@ -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/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/test/DumpDatabase.cxx b/test/DumpDatabase.cxx index 21a12d294..ff3464099 100644 --- a/test/DumpDatabase.cxx +++ b/test/DumpDatabase.cxx @@ -21,6 +21,7 @@ #include "DatabaseRegistry.hxx" #include "DatabasePlugin.hxx" #include "DatabaseSelection.hxx" +#include "DatabaseListener.hxx" #include "Directory.hxx" #include "Song.hxx" #include "PlaylistVector.hxx" @@ -28,6 +29,7 @@ #include "ConfigData.hxx" #include "tag/TagConfig.hxx" #include "fs/Path.hxx" +#include "event/Loop.hxx" #include "util/Error.hxx" #include <glib.h> @@ -39,15 +41,21 @@ 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 "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 &) @@ -59,7 +67,10 @@ DumpDirectory(const Directory &directory, Error &) static bool DumpSong(Song &song, Error &) { - cout << "S " << song.parent->path << "/" << song.uri << endl; + cout << "S "; + if (song.parent != nullptr && !song.parent->IsRoot()) + cout << song.parent->path << "/"; + cout << song.uri << endl; return true; } @@ -94,8 +105,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 +117,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..3045722cf --- /dev/null +++ b/test/FakeDecoderAPI.cxx @@ -0,0 +1,144 @@ +/* + * 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 "DecoderAPI.hxx" +#include "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/dump_playlist.cxx b/test/dump_playlist.cxx index d11562930..b0702d3be 100644 --- a/test/dump_playlist.cxx +++ b/test/dump_playlist.cxx @@ -19,12 +19,10 @@ #include "config.h" #include "TagSave.hxx" -#include "Song.hxx" +#include "DetachedSong.hxx" #include "SongEnumerator.hxx" -#include "Directory.hxx" #include "InputStream.hxx" #include "ConfigGlobal.hxx" -#include "DecoderAPI.hxx" #include "DecoderList.hxx" #include "InputInit.hxx" #include "IOThread.hxx" @@ -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, + "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..e79703e50 100644 --- a/test/dump_rva2.cxx +++ b/test/dump_rva2.cxx @@ -24,16 +24,16 @@ #include "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..764d3f24b 100644 --- a/test/dump_text_file.cxx +++ b/test/dump_text_file.cxx @@ -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..b27c70497 100644 --- a/test/read_conf.cxx +++ b/test/read_conf.cxx @@ -21,40 +21,28 @@ #include "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..95aacf6db 100644 --- a/test/read_mixer.cxx +++ b/test/read_mixer.cxx @@ -21,12 +21,13 @@ #include "MixerControl.hxx" #include "MixerList.hxx" #include "FilterRegistry.hxx" -#include "pcm/PcmVolume.hxx" +#include "pcm/Volume.hxx" #include "GlobalEvents.hxx" #include "Main.hxx" #include "event/Loop.hxx" #include "ConfigData.hxx" #include "util/Error.hxx" +#include "Log.hxx" #include <glib.h> @@ -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..49d40befb 100644 --- a/test/read_tags.cxx +++ b/test/read_tags.cxx @@ -20,7 +20,7 @@ #include "config.h" #include "IOThread.hxx" #include "DecoderList.hxx" -#include "DecoderAPI.hxx" +#include "DecoderPlugin.hxx" #include "InputInit.hxx" #include "InputStream.hxx" #include "AudioFormat.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_convert.cxx b/test/run_convert.cxx index 0e873a3b3..67783592c 100644 --- a/test/run_convert.cxx +++ b/test/run_convert.cxx @@ -30,25 +30,14 @@ #include "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..fb29bd3b3 100644 --- a/test/run_decoder.cxx +++ b/test/run_decoder.cxx @@ -29,21 +29,14 @@ #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, "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..a71b815f5 100644 --- a/test/run_encoder.cxx +++ b/test/run_encoder.cxx @@ -24,10 +24,11 @@ #include "AudioParser.hxx" #include "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..3b5e803d7 100644 --- a/test/run_filter.cxx +++ b/test/run_filter.cxx @@ -25,16 +25,19 @@ #include "AudioFormat.hxx" #include "FilterPlugin.hxx" #include "FilterInternal.hxx" -#include "pcm/PcmVolume.hxx" +#include "pcm/Volume.hxx" #include "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 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..8dcc371cc 100644 --- a/test/run_inotify.cxx +++ b/test/run_inotify.cxx @@ -24,8 +24,6 @@ #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..0c1ba1d36 100644 --- a/test/run_input.cxx +++ b/test/run_input.cxx @@ -33,21 +33,13 @@ #include "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_normalize.cxx b/test/run_normalize.cxx index 3193fefd2..091c3d61a 100644 --- a/test/run_normalize.cxx +++ b/test/run_normalize.cxx @@ -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..86c2452b9 100644 --- a/test/run_output.cxx +++ b/test/run_output.cxx @@ -36,6 +36,7 @@ #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..7a0ea3b64 100644 --- a/test/run_resolver.cxx +++ b/test/run_resolver.cxx @@ -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..b10eabeb3 100644 --- a/test/software_volume.cxx +++ b/test/software_volume.cxx @@ -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/test_pcm_channels.cxx b/test/test_pcm_channels.cxx index 85c872674..355553687 100644 --- a/test/test_pcm_channels.cxx +++ b/test/test_pcm_channels.cxx @@ -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..bf7484885 100644 --- a/test/test_pcm_dither.cxx +++ b/test/test_pcm_dither.cxx @@ -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..3b0dbb8fe 100644 --- a/test/test_pcm_format.cxx +++ b/test/test_pcm_format.cxx @@ -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_mix.cxx b/test/test_pcm_mix.cxx index 2a8a11388..542c4de80 100644 --- a/test/test_pcm_mix.cxx +++ b/test/test_pcm_mix.cxx @@ -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_util.hxx b/test/test_pcm_util.hxx index b378c75a7..216e360ce 100644 --- a/test/test_pcm_util.hxx +++ b/test/test_pcm_util.hxx @@ -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..c3a261f7a 100644 --- a/test/test_pcm_volume.cxx +++ b/test/test_pcm_volume.cxx @@ -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 a1037798c..953103f9a 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 "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 struct 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"), + }; struct 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_vorbis_encoder.cxx b/test/test_vorbis_encoder.cxx index 1d95f6deb..bb8731c2f 100644 --- a/test/test_vorbis_encoder.cxx +++ b/test/test_vorbis_encoder.cxx @@ -24,6 +24,7 @@ #include "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..bd890de4f 100644 --- a/test/visit_archive.cxx +++ b/test/visit_archive.cxx @@ -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 +} |