Compare commits
202 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
04f627c2af | ||
![]() |
a254f5a3a8 | ||
![]() |
143c735f96 | ||
![]() |
7aa2104596 | ||
![]() |
c8b93d6573 | ||
![]() |
3f5f96ac91 | ||
![]() |
7e7b403043 | ||
![]() |
c64ad78c7b | ||
![]() |
4a043a915f | ||
![]() |
38a0d15190 | ||
![]() |
ec3191f502 | ||
![]() |
32b5654a6e | ||
![]() |
674091424e | ||
![]() |
6ad336743d | ||
![]() |
c882568ccd | ||
![]() |
f6b2899dd2 | ||
![]() |
bccd4ef2f7 | ||
![]() |
94c240a026 | ||
![]() |
c50a0cf7bf | ||
![]() |
c37f7abb79 | ||
![]() |
432ce9b1de | ||
![]() |
fe45f28204 | ||
![]() |
861067412f | ||
![]() |
7eca886608 | ||
![]() |
79b6f9e89e | ||
![]() |
3d17c06777 | ||
![]() |
d6c08fb79f | ||
![]() |
ef02b20811 | ||
![]() |
8bf46a665e | ||
![]() |
c4fca2aa61 | ||
![]() |
87268c2297 | ||
![]() |
e93975cb46 | ||
![]() |
b6fa22bd84 | ||
![]() |
a0ef27a0cd | ||
![]() |
e304d0f8ee | ||
![]() |
ab7b38d4b9 | ||
![]() |
eaf675dc92 | ||
![]() |
57068e526c | ||
![]() |
c14a00eec9 | ||
![]() |
219c42522f | ||
![]() |
e3a0f15837 | ||
![]() |
a6bb27483b | ||
![]() |
7ada7def9e | ||
![]() |
421c4ae907 | ||
![]() |
4907f610d6 | ||
![]() |
f9d1bbbffb | ||
![]() |
86e8b3b4bd | ||
![]() |
a26ead035a | ||
![]() |
704be54c3a | ||
![]() |
2406152576 | ||
![]() |
af260b5a64 | ||
![]() |
4efa96df21 | ||
![]() |
8b62127770 | ||
![]() |
f06fe1ea98 | ||
![]() |
d16fb79708 | ||
![]() |
c38f29ce56 | ||
![]() |
78abcd7df7 | ||
![]() |
23dce21647 | ||
![]() |
40280fa6cf | ||
![]() |
fe9299ceff | ||
![]() |
c3f111a56c | ||
![]() |
250318329f | ||
![]() |
14c538c9c7 | ||
![]() |
abe4c57663 | ||
![]() |
a3f3c7ba24 | ||
![]() |
94efeb2845 | ||
![]() |
a73834436f | ||
![]() |
85f4aeca05 | ||
![]() |
7db84a961a | ||
![]() |
a960e2ef48 | ||
![]() |
4fe272a7fb | ||
![]() |
a7d9f248ea | ||
![]() |
06aa689383 | ||
![]() |
835b0c44cd | ||
![]() |
54b6f8a4ae | ||
![]() |
18787ebe8f | ||
![]() |
47e8fcf37e | ||
![]() |
5958b78459 | ||
![]() |
9d9697b366 | ||
![]() |
6585e18571 | ||
![]() |
6f1b4292f0 | ||
![]() |
ef9ef03b1f | ||
![]() |
ecb67a1ed1 | ||
![]() |
0ef843f138 | ||
![]() |
eb79d83051 | ||
![]() |
ca1a11493d | ||
![]() |
69bb086ba5 | ||
![]() |
11a5ee821b | ||
![]() |
a8a85143f6 | ||
![]() |
e2cc328eef | ||
![]() |
344d10a8e3 | ||
![]() |
09384df32c | ||
![]() |
20538516b9 | ||
![]() |
0759421d11 | ||
![]() |
bf7417981f | ||
![]() |
dba41e2e4a | ||
![]() |
bc6472bb9e | ||
![]() |
d4bd947bf5 | ||
![]() |
d8e8eabf60 | ||
![]() |
a70443af31 | ||
![]() |
3f221e2edb | ||
![]() |
848ed14788 | ||
![]() |
4c8a5dfb05 | ||
![]() |
4f61ba766d | ||
![]() |
8bfdb4ed0c | ||
![]() |
70bd35abe2 | ||
![]() |
0efb67b51e | ||
![]() |
54ebf2a699 | ||
![]() |
d0119548c1 | ||
![]() |
95ac6071b9 | ||
![]() |
3a4e667078 | ||
![]() |
ce18c36ed9 | ||
![]() |
8e39cf62e7 | ||
![]() |
a9e351e00d | ||
![]() |
d65841a2db | ||
![]() |
2784d65618 | ||
![]() |
47ea69233b | ||
![]() |
a884e37de1 | ||
![]() |
0102a8665a | ||
![]() |
d34ae0850c | ||
![]() |
6526de024a | ||
![]() |
5e1e92626c | ||
![]() |
7fee85c80a | ||
![]() |
5d87a274a5 | ||
![]() |
57e862712a | ||
![]() |
ddb5390d88 | ||
![]() |
fce20e514e | ||
![]() |
af66ed2505 | ||
![]() |
ea4068757d | ||
![]() |
2b10ecfa37 | ||
![]() |
f7eb2b697e | ||
![]() |
da67260c95 | ||
![]() |
ab9c9068d4 | ||
![]() |
6b4d7d7315 | ||
![]() |
313d1d5d83 | ||
![]() |
b7d6133593 | ||
![]() |
5b6bb114ad | ||
![]() |
56f082c9d4 | ||
![]() |
a1b798e555 | ||
![]() |
c91e08fbfd | ||
![]() |
f882434547 | ||
![]() |
05ad335ae9 | ||
![]() |
7faeb2ff2b | ||
![]() |
fdd76b3461 | ||
![]() |
e490e5d0ab | ||
![]() |
3a05c421e0 | ||
![]() |
afc70c120e | ||
![]() |
d7f80eab68 | ||
![]() |
e30b356eb0 | ||
![]() |
09a0803116 | ||
![]() |
20ffedc745 | ||
![]() |
0b1ad27ba8 | ||
![]() |
6a1b2f0387 | ||
![]() |
fb34519b96 | ||
![]() |
91fed47648 | ||
![]() |
c05691b546 | ||
![]() |
6b3b8c6f2e | ||
![]() |
a191db84f2 | ||
![]() |
e4d69f38b0 | ||
![]() |
97fc001180 | ||
![]() |
42a09ff17a | ||
![]() |
c170fed6f9 | ||
![]() |
8e38b4f83c | ||
![]() |
db4ae19246 | ||
![]() |
82a89c6bfe | ||
![]() |
166c70cab3 | ||
![]() |
de78fe38c8 | ||
![]() |
96fa69ff6b | ||
![]() |
39d94bd3ea | ||
![]() |
695ca29274 | ||
![]() |
02e8da6c98 | ||
![]() |
0ea5f4ac3a | ||
![]() |
47c50c079d | ||
![]() |
3b0fea5fae | ||
![]() |
443516cdda | ||
![]() |
57e0cc5442 | ||
![]() |
73f45d87d5 | ||
![]() |
ae88ba986e | ||
![]() |
d6247902ec | ||
![]() |
a566c28a49 | ||
![]() |
2eddb63a83 | ||
![]() |
fc7d5b055d | ||
![]() |
87c8953e8e | ||
![]() |
45d27a52f1 | ||
![]() |
d22acc59c9 | ||
![]() |
c064e8d62f | ||
![]() |
099a2cb586 | ||
![]() |
042fe2a9d0 | ||
![]() |
ff1c1107f3 | ||
![]() |
41a3fd0fd0 | ||
![]() |
b9169a5670 | ||
![]() |
2ecd5fa28d | ||
![]() |
e719b6cc41 | ||
![]() |
67b8124a1d | ||
![]() |
51ec499c89 | ||
![]() |
3a4df25db2 | ||
![]() |
37cf78ef45 | ||
![]() |
727c622659 | ||
![]() |
74a778e65f | ||
![]() |
1fc0c9fe8a | ||
![]() |
740d8ec5ab | ||
![]() |
0bcc477d46 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -31,6 +31,7 @@ libtool
|
|||||||
ltmain.sh
|
ltmain.sh
|
||||||
missing
|
missing
|
||||||
mkinstalldirs
|
mkinstalldirs
|
||||||
|
/test-driver
|
||||||
mpd
|
mpd
|
||||||
mpd.service
|
mpd.service
|
||||||
stamp-h1
|
stamp-h1
|
||||||
@@ -39,7 +40,7 @@ tags
|
|||||||
.#*
|
.#*
|
||||||
.stgit*
|
.stgit*
|
||||||
src/dsd2pcm/dsd2pcm
|
src/dsd2pcm/dsd2pcm
|
||||||
src/win/mpd_win32_rc.rc
|
src/win32/mpd_win32_rc.rc
|
||||||
doc/doxygen.conf
|
doc/doxygen.conf
|
||||||
doc/protocol.html
|
doc/protocol.html
|
||||||
doc/protocol
|
doc/protocol
|
||||||
@@ -62,6 +63,7 @@ test/run_normalize
|
|||||||
test/tmp
|
test/tmp
|
||||||
test/run_inotify
|
test/run_inotify
|
||||||
test/test_queue_priority
|
test/test_queue_priority
|
||||||
|
test/test_protocol
|
||||||
test/run_ntp_server
|
test/run_ntp_server
|
||||||
test/run_resolver
|
test/run_resolver
|
||||||
test/run_tcp_connect
|
test/run_tcp_connect
|
||||||
|
57
Makefile.am
57
Makefile.am
@@ -151,7 +151,7 @@ src_mpd_SOURCES = \
|
|||||||
src/IOThread.cxx src/IOThread.hxx \
|
src/IOThread.cxx src/IOThread.hxx \
|
||||||
src/Main.cxx src/Main.hxx \
|
src/Main.cxx src/Main.hxx \
|
||||||
src/Instance.cxx src/Instance.hxx \
|
src/Instance.cxx src/Instance.hxx \
|
||||||
src/Win32Main.cxx \
|
src/win32/Win32Main.cxx \
|
||||||
src/GlobalEvents.cxx src/GlobalEvents.hxx \
|
src/GlobalEvents.cxx src/GlobalEvents.hxx \
|
||||||
src/Daemon.cxx src/Daemon.hxx \
|
src/Daemon.cxx src/Daemon.hxx \
|
||||||
src/AudioCompress/compress.c \
|
src/AudioCompress/compress.c \
|
||||||
@@ -181,6 +181,7 @@ src_mpd_SOURCES = \
|
|||||||
src/PlaylistInfo.hxx \
|
src/PlaylistInfo.hxx \
|
||||||
src/PlaylistDatabase.cxx src/PlaylistDatabase.hxx \
|
src/PlaylistDatabase.cxx src/PlaylistDatabase.hxx \
|
||||||
src/PlaylistUpdate.cxx \
|
src/PlaylistUpdate.cxx \
|
||||||
|
src/BulkEdit.hxx \
|
||||||
src/IdTable.hxx \
|
src/IdTable.hxx \
|
||||||
src/Queue.cxx src/Queue.hxx \
|
src/Queue.cxx src/Queue.hxx \
|
||||||
src/QueuePrint.cxx src/QueuePrint.hxx \
|
src/QueuePrint.cxx src/QueuePrint.hxx \
|
||||||
@@ -210,14 +211,14 @@ src_mpd_SOURCES = \
|
|||||||
# Windows resource file
|
# Windows resource file
|
||||||
#
|
#
|
||||||
|
|
||||||
src/win/mpd_win32_rc.$(OBJEXT): src/win/mpd_win32_rc.rc
|
src/win32/mpd_win32_rc.$(OBJEXT): src/win32/mpd_win32_rc.rc
|
||||||
$(WINDRES) -i $< -o $@
|
$(WINDRES) -i $< -o $@
|
||||||
|
|
||||||
if HAVE_WINDOWS
|
if HAVE_WINDOWS
|
||||||
noinst_DATA = src/win/mpd_win32_rc.rc
|
noinst_DATA = src/win32/mpd_win32_rc.rc
|
||||||
|
|
||||||
src_mpd_DEPENDENCIES = src/win/mpd_win32_rc.$(OBJEXT)
|
EXTRA_src_mpd_DEPENDENCIES = src/win32/mpd_win32_rc.$(OBJEXT)
|
||||||
src_mpd_LDFLAGS = -Wl,src/win/mpd_win32_rc.$(OBJEXT)
|
src_mpd_LDFLAGS = -Wl,src/win32/mpd_win32_rc.$(OBJEXT)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if ENABLE_DESPOTIFY
|
if ENABLE_DESPOTIFY
|
||||||
@@ -475,12 +476,6 @@ endif
|
|||||||
libdecoder_plugins_a_SOURCES = \
|
libdecoder_plugins_a_SOURCES = \
|
||||||
src/decoder/PcmDecoderPlugin.cxx \
|
src/decoder/PcmDecoderPlugin.cxx \
|
||||||
src/decoder/PcmDecoderPlugin.hxx \
|
src/decoder/PcmDecoderPlugin.hxx \
|
||||||
src/decoder/DsdiffDecoderPlugin.cxx \
|
|
||||||
src/decoder/DsdiffDecoderPlugin.hxx \
|
|
||||||
src/decoder/DsfDecoderPlugin.cxx \
|
|
||||||
src/decoder/DsfDecoderPlugin.hxx \
|
|
||||||
src/decoder/DsdLib.cxx \
|
|
||||||
src/decoder/DsdLib.hxx \
|
|
||||||
src/DecoderBuffer.cxx src/DecoderBuffer.hxx \
|
src/DecoderBuffer.cxx src/DecoderBuffer.hxx \
|
||||||
src/DecoderPlugin.cxx \
|
src/DecoderPlugin.cxx \
|
||||||
src/DecoderList.cxx src/DecoderList.hxx
|
src/DecoderList.cxx src/DecoderList.hxx
|
||||||
@@ -524,6 +519,16 @@ DECODER_LIBS = \
|
|||||||
|
|
||||||
DECODER_SRC =
|
DECODER_SRC =
|
||||||
|
|
||||||
|
if ENABLE_DSD
|
||||||
|
libdecoder_plugins_a_SOURCES += \
|
||||||
|
src/decoder/DsdiffDecoderPlugin.cxx \
|
||||||
|
src/decoder/DsdiffDecoderPlugin.hxx \
|
||||||
|
src/decoder/DsfDecoderPlugin.cxx \
|
||||||
|
src/decoder/DsfDecoderPlugin.hxx \
|
||||||
|
src/decoder/DsdLib.cxx \
|
||||||
|
src/decoder/DsdLib.hxx
|
||||||
|
endif
|
||||||
|
|
||||||
if HAVE_MAD
|
if HAVE_MAD
|
||||||
libdecoder_plugins_a_SOURCES += \
|
libdecoder_plugins_a_SOURCES += \
|
||||||
src/decoder/MadDecoderPlugin.cxx \
|
src/decoder/MadDecoderPlugin.cxx \
|
||||||
@@ -1061,7 +1066,9 @@ C_TESTS = \
|
|||||||
test/test_util \
|
test/test_util \
|
||||||
test/test_byte_reverse \
|
test/test_byte_reverse \
|
||||||
test/test_mixramp \
|
test/test_mixramp \
|
||||||
|
test/test_icy_parser \
|
||||||
test/test_pcm \
|
test/test_pcm \
|
||||||
|
test/test_protocol \
|
||||||
test/test_queue_priority
|
test/test_queue_priority
|
||||||
|
|
||||||
if ENABLE_ARCHIVE
|
if ENABLE_ARCHIVE
|
||||||
@@ -1217,6 +1224,7 @@ test_dump_playlist_LDADD = \
|
|||||||
libpcm.a \
|
libpcm.a \
|
||||||
$(GLIB_LIBS)
|
$(GLIB_LIBS)
|
||||||
test_dump_playlist_SOURCES = test/dump_playlist.cxx \
|
test_dump_playlist_SOURCES = test/dump_playlist.cxx \
|
||||||
|
test/FakeDecoderAPI.cxx \
|
||||||
$(DECODER_SRC) \
|
$(DECODER_SRC) \
|
||||||
src/Log.cxx \
|
src/Log.cxx \
|
||||||
src/IOThread.cxx \
|
src/IOThread.cxx \
|
||||||
@@ -1270,6 +1278,7 @@ test_read_tags_LDADD = \
|
|||||||
libutil.a \
|
libutil.a \
|
||||||
$(GLIB_LIBS)
|
$(GLIB_LIBS)
|
||||||
test_read_tags_SOURCES = test/read_tags.cxx \
|
test_read_tags_SOURCES = test/read_tags.cxx \
|
||||||
|
test/FakeDecoderAPI.cxx \
|
||||||
src/Log.cxx \
|
src/Log.cxx \
|
||||||
src/IOThread.cxx \
|
src/IOThread.cxx \
|
||||||
src/ReplayGainInfo.cxx \
|
src/ReplayGainInfo.cxx \
|
||||||
@@ -1496,6 +1505,16 @@ test_test_mixramp_LDADD = \
|
|||||||
$(GLIB_LIBS) \
|
$(GLIB_LIBS) \
|
||||||
$(CPPUNIT_LIBS)
|
$(CPPUNIT_LIBS)
|
||||||
|
|
||||||
|
test_test_icy_parser_SOURCES = \
|
||||||
|
src/Log.cxx \
|
||||||
|
test/test_icy_parser.cxx
|
||||||
|
test_test_icy_parser_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0
|
||||||
|
test_test_icy_parser_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations
|
||||||
|
test_test_icy_parser_LDADD = \
|
||||||
|
libtag.a \
|
||||||
|
$(GLIB_LIBS) \
|
||||||
|
$(CPPUNIT_LIBS)
|
||||||
|
|
||||||
test_test_pcm_SOURCES = \
|
test_test_pcm_SOURCES = \
|
||||||
test/test_pcm_util.hxx \
|
test/test_pcm_util.hxx \
|
||||||
test/test_pcm_dither.cxx \
|
test/test_pcm_dither.cxx \
|
||||||
@@ -1524,6 +1543,16 @@ test_test_archive_LDADD = \
|
|||||||
$(GLIB_LIBS) \
|
$(GLIB_LIBS) \
|
||||||
$(CPPUNIT_LIBS)
|
$(CPPUNIT_LIBS)
|
||||||
|
|
||||||
|
test_test_protocol_SOURCES = \
|
||||||
|
src/protocol/ArgParser.cxx \
|
||||||
|
test/test_protocol.cxx
|
||||||
|
test_test_protocol_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0
|
||||||
|
test_test_protocol_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations
|
||||||
|
test_test_protocol_LDADD = \
|
||||||
|
libsystem.a \
|
||||||
|
libutil.a \
|
||||||
|
$(CPPUNIT_LIBS)
|
||||||
|
|
||||||
test_test_queue_priority_SOURCES = \
|
test_test_queue_priority_SOURCES = \
|
||||||
src/Queue.cxx \
|
src/Queue.cxx \
|
||||||
test/test_queue_priority.cxx
|
test/test_queue_priority.cxx
|
||||||
@@ -1534,6 +1563,8 @@ test_test_queue_priority_LDADD = \
|
|||||||
libutil.a \
|
libutil.a \
|
||||||
$(CPPUNIT_LIBS)
|
$(CPPUNIT_LIBS)
|
||||||
|
|
||||||
|
if ENABLE_DSD
|
||||||
|
|
||||||
noinst_PROGRAMS += src/pcm/dsd2pcm/dsd2pcm
|
noinst_PROGRAMS += src/pcm/dsd2pcm/dsd2pcm
|
||||||
|
|
||||||
src_pcm_dsd2pcm_dsd2pcm_SOURCES = \
|
src_pcm_dsd2pcm_dsd2pcm_SOURCES = \
|
||||||
@@ -1546,6 +1577,8 @@ src_pcm_dsd2pcm_dsd2pcm_LDADD = libutil.a
|
|||||||
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Documentation
|
# Documentation
|
||||||
@@ -1619,4 +1652,4 @@ EXTRA_DIST = $(doc_DATA) autogen.sh \
|
|||||||
test/test_archive_zzip.sh \
|
test/test_archive_zzip.sh \
|
||||||
$(wildcard scripts/*.sh) \
|
$(wildcard scripts/*.sh) \
|
||||||
$(man_MANS) $(DOCBOOK_FILES) doc/mpdconf.example doc/doxygen.conf \
|
$(man_MANS) $(DOCBOOK_FILES) doc/mpdconf.example doc/doxygen.conf \
|
||||||
src/win/mpd_win32_rc.rc.in src/win/mpd.ico
|
src/win32/mpd_win32_rc.rc.in src/win32/mpd.ico
|
||||||
|
139
NEWS
139
NEWS
@@ -1,3 +1,142 @@
|
|||||||
|
ver 0.18.19 (2014/11/26)
|
||||||
|
* archive
|
||||||
|
- zzip: fix crash after seeking
|
||||||
|
|
||||||
|
ver 0.18.18 (2014/11/18)
|
||||||
|
* decoder
|
||||||
|
- ffmpeg: support opus
|
||||||
|
* fix crash on failed filename charset conversion
|
||||||
|
* fix local socket detection from uid=0 (root)
|
||||||
|
|
||||||
|
ver 0.18.17 (2014/11/02)
|
||||||
|
* playlist
|
||||||
|
- don't allow empty playlist name
|
||||||
|
- m3u: recognize the file suffix ".m3u8"
|
||||||
|
* decoder
|
||||||
|
- ignore URI query string for plugin detection
|
||||||
|
- faad: remove workaround for ancient libfaad2 ABI bug
|
||||||
|
- ffmpeg: recognize MIME type audio/aacp
|
||||||
|
|
||||||
|
ver 0.18.16 (2014/09/26)
|
||||||
|
* fix DSD breakage due to typo in configure.ac
|
||||||
|
|
||||||
|
ver 0.18.15 (2014/09/26)
|
||||||
|
* command
|
||||||
|
- list: reset used size after the list has been processed
|
||||||
|
* fix MixRamp
|
||||||
|
* work around build failure on NetBSD
|
||||||
|
|
||||||
|
ver 0.18.14 (2014/09/11)
|
||||||
|
* protocol
|
||||||
|
- fix range parser bug on certain 32 bit architectures
|
||||||
|
* decoder
|
||||||
|
- audiofile: fix crash after seeking
|
||||||
|
- ffmpeg: fix crash with ffmpeg/libav version 11
|
||||||
|
- fix assertion failure after seeking
|
||||||
|
|
||||||
|
ver 0.18.13 (2014/08/31)
|
||||||
|
* protocol
|
||||||
|
- don't change song on "seekcur" in random mode
|
||||||
|
* decoder
|
||||||
|
- dsdiff, dsf: fix endless loop on malformed file
|
||||||
|
- ffmpeg: support ffmpeg/libav version 11
|
||||||
|
- gme: fix song duration
|
||||||
|
* output
|
||||||
|
- alsa: fix endless loop at end of file in dsd_usb mode
|
||||||
|
* fix state file saver
|
||||||
|
* fix build failure on Darwin
|
||||||
|
|
||||||
|
ver 0.18.12 (2014/07/30)
|
||||||
|
* database
|
||||||
|
- proxy: fix build failure with libmpdclient 2.2
|
||||||
|
- proxy: fix add/search and other commands with libmpdclient < 2.9
|
||||||
|
* decoder
|
||||||
|
- audiofile: improve responsiveness
|
||||||
|
- audiofile: fix WAV stream playback
|
||||||
|
- dsdiff, dsf: fix stream playback
|
||||||
|
- dsdiff: fix metadata parser bug (uninitialized variables)
|
||||||
|
- faad: estimate song duration for remote files
|
||||||
|
- sndfile: improve responsiveness
|
||||||
|
* randomize next song when enabling "random" mode while not playing
|
||||||
|
* randomize next song when adding to single-song queue
|
||||||
|
|
||||||
|
ver 0.18.11 (2014/05/12)
|
||||||
|
* decoder
|
||||||
|
- opus: fix missing song length on high-latency files
|
||||||
|
* fix race condition when using GLib event loop (non-Linux)
|
||||||
|
|
||||||
|
ver 0.18.10 (2014/04/10)
|
||||||
|
* decoder
|
||||||
|
- ffmpeg: fix seeking bug
|
||||||
|
- ffmpeg: handle unknown stream start time
|
||||||
|
- gme: fix memory leak
|
||||||
|
- sndfile: work around libsndfile bug on partial read
|
||||||
|
* don't interrupt playback when current song gets deleted
|
||||||
|
|
||||||
|
ver 0.18.9 (2014/03/02)
|
||||||
|
* protocol
|
||||||
|
- "findadd" requires the "add" permission
|
||||||
|
* output
|
||||||
|
- alsa: improved workaround for noise after manual song change
|
||||||
|
* decoder
|
||||||
|
- vorbis: fix linker failure when libvorbis/libogg are static
|
||||||
|
* encoder
|
||||||
|
- vorbis: fix another linker failure
|
||||||
|
* output
|
||||||
|
- pipe: fix hanging child process due to blocked signals
|
||||||
|
* fix build failure due to missing signal.h include
|
||||||
|
|
||||||
|
ver 0.18.8 (2014/02/07)
|
||||||
|
* decoder
|
||||||
|
- ffmpeg: support libav v10_alpha1
|
||||||
|
* encoder
|
||||||
|
- vorbis: fix linker failure
|
||||||
|
* output
|
||||||
|
- roar: documentation
|
||||||
|
* more robust Icy-Metadata parser
|
||||||
|
* fix Solaris build failure
|
||||||
|
|
||||||
|
ver 0.18.7 (2014/01/13)
|
||||||
|
* playlist
|
||||||
|
- pls: fix crash after parser error
|
||||||
|
- soundcloud: fix build failure with libyajl 2.0.1
|
||||||
|
* decoder
|
||||||
|
- faad: fix memory leak
|
||||||
|
- mpcdec: reject libmpcdec SV7 in configure script
|
||||||
|
* daemon: don't initialize supplementary groups when already running
|
||||||
|
as the configured user
|
||||||
|
|
||||||
|
ver 0.18.6 (2013/12/24)
|
||||||
|
* input
|
||||||
|
- cdio_paranoia: support libcdio-paranoia 0.90
|
||||||
|
* tags
|
||||||
|
- riff: recognize upper-case "ID3" chunk name
|
||||||
|
* decoder
|
||||||
|
- ffmpeg: use relative timestamps
|
||||||
|
* output
|
||||||
|
- openal: fix build failure on Mac OS X
|
||||||
|
- osx: fix build failure
|
||||||
|
* mixer
|
||||||
|
- alsa: fix build failure with uClibc
|
||||||
|
* fix replay gain during cross-fade
|
||||||
|
* accept files without metadata
|
||||||
|
|
||||||
|
ver 0.18.5 (2013/11/23)
|
||||||
|
* configuration
|
||||||
|
- fix crash when db_file is configured without music_directory
|
||||||
|
- fix crash on "stats" without db_file/music_directory
|
||||||
|
* database
|
||||||
|
- proxy: auto-reload statistics
|
||||||
|
- proxy: provide "db_update" in "stats" response
|
||||||
|
* input
|
||||||
|
- curl: work around stream resume bug (fixed in libcurl 7.32.0)
|
||||||
|
* decoder
|
||||||
|
- fluidsynth: auto-detect by default
|
||||||
|
* clip 24 bit data from libsamplerate
|
||||||
|
* fix ia64, mipsel and other little-endian architectures
|
||||||
|
* fix build failures due to missing includes
|
||||||
|
* fix build failure with static libmpdclient
|
||||||
|
|
||||||
ver 0.18.4 (2013/11/13)
|
ver 0.18.4 (2013/11/13)
|
||||||
* decoder
|
* decoder
|
||||||
- dsdiff: fix byte order bug
|
- dsdiff: fix byte order bug
|
||||||
|
79
configure.ac
79
configure.ac
@@ -1,10 +1,10 @@
|
|||||||
AC_PREREQ(2.60)
|
AC_PREREQ(2.60)
|
||||||
|
|
||||||
AC_INIT(mpd, 0.18.4, musicpd-dev-team@lists.sourceforge.net)
|
AC_INIT(mpd, 0.18.19, mpd-devel@musicpd.org)
|
||||||
|
|
||||||
VERSION_MAJOR=0
|
VERSION_MAJOR=0
|
||||||
VERSION_MINOR=18
|
VERSION_MINOR=18
|
||||||
VERSION_REVISION=0
|
VERSION_REVISION=19
|
||||||
VERSION_EXTRA=0
|
VERSION_EXTRA=0
|
||||||
|
|
||||||
AC_CONFIG_SRCDIR([src/Main.cxx])
|
AC_CONFIG_SRCDIR([src/Main.cxx])
|
||||||
@@ -70,7 +70,7 @@ host_is_darwin=no
|
|||||||
case "$host_os" in
|
case "$host_os" in
|
||||||
mingw32* | windows*)
|
mingw32* | windows*)
|
||||||
AC_CONFIG_FILES([
|
AC_CONFIG_FILES([
|
||||||
src/win/mpd_win32_rc.rc
|
src/win32/mpd_win32_rc.rc
|
||||||
])
|
])
|
||||||
AC_CHECK_TOOL(WINDRES, windres)
|
AC_CHECK_TOOL(WINDRES, windres)
|
||||||
AM_CPPFLAGS="$AM_CPPFLAGS -DWIN32_LEAN_AND_MEAN"
|
AM_CPPFLAGS="$AM_CPPFLAGS -DWIN32_LEAN_AND_MEAN"
|
||||||
@@ -214,6 +214,11 @@ AC_ARG_ENABLE(documentation,
|
|||||||
[build documentation (default: disable)]),,
|
[build documentation (default: disable)]),,
|
||||||
[enable_documentation=no])
|
[enable_documentation=no])
|
||||||
|
|
||||||
|
AC_ARG_ENABLE(dsd,
|
||||||
|
AS_HELP_STRING([--enable-dsd],
|
||||||
|
[enable DSD decoder (default: enable)]),,
|
||||||
|
[enable_dsd=yes])
|
||||||
|
|
||||||
AC_ARG_ENABLE(ffmpeg,
|
AC_ARG_ENABLE(ffmpeg,
|
||||||
AS_HELP_STRING([--enable-ffmpeg],
|
AS_HELP_STRING([--enable-ffmpeg],
|
||||||
[enable FFMPEG support]),,
|
[enable FFMPEG support]),,
|
||||||
@@ -732,7 +737,7 @@ dnl --------------------------------- Soundcloud ------------------------------
|
|||||||
if test x$enable_soundcloud != xno; then
|
if test x$enable_soundcloud != xno; then
|
||||||
PKG_CHECK_MODULES([YAJL], [yajl >= 2.0],
|
PKG_CHECK_MODULES([YAJL], [yajl >= 2.0],
|
||||||
[found_soundcloud=yes],
|
[found_soundcloud=yes],
|
||||||
AC_CHECK_LIB([yajl], [yajl_alloc],
|
AC_CHECK_LIB([yajl], [yajl_parse_complete],
|
||||||
[found_soundcloud=yes YAJL_CFLAGS=-DHAVE_YAJL1 YAJL_LIBS=-lyajl],
|
[found_soundcloud=yes YAJL_CFLAGS=-DHAVE_YAJL1 YAJL_LIBS=-lyajl],
|
||||||
[found_soundcloud=no]))
|
[found_soundcloud=no]))
|
||||||
fi
|
fi
|
||||||
@@ -749,6 +754,7 @@ MPD_AUTO_PKG(cdio_paranoia, CDIO_PARANOIA, [libcdio_paranoia],
|
|||||||
if test x$enable_cdio_paranoia = xyes; then
|
if test x$enable_cdio_paranoia = xyes; then
|
||||||
AC_DEFINE([ENABLE_CDIO_PARANOIA], 1,
|
AC_DEFINE([ENABLE_CDIO_PARANOIA], 1,
|
||||||
[Define to enable libcdio_paranoia support])
|
[Define to enable libcdio_paranoia support])
|
||||||
|
AC_CHECK_HEADERS(cdio/paranoia/paranoia.h)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
AM_CONDITIONAL(ENABLE_CDIO_PARANOIA, test x$enable_cdio_paranoia = xyes)
|
AM_CONDITIONAL(ENABLE_CDIO_PARANOIA, test x$enable_cdio_paranoia = xyes)
|
||||||
@@ -845,6 +851,14 @@ if test x$enable_audiofile = xyes; then
|
|||||||
AC_DEFINE(HAVE_AUDIOFILE, 1, [Define for audiofile support])
|
AC_DEFINE(HAVE_AUDIOFILE, 1, [Define for audiofile support])
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
dnl ----------------------------------- DSD -----------------------------------
|
||||||
|
|
||||||
|
if test x$enable_dsd = xyes; then
|
||||||
|
AC_DEFINE(ENABLE_DSD, 1, [Define for the DSD decoder])
|
||||||
|
fi
|
||||||
|
|
||||||
|
AM_CONDITIONAL(ENABLE_DSD, test x$enable_dsd = xyes)
|
||||||
|
|
||||||
dnl ----------------------------------- FAAD ----------------------------------
|
dnl ----------------------------------- FAAD ----------------------------------
|
||||||
AM_PATH_FAAD()
|
AM_PATH_FAAD()
|
||||||
|
|
||||||
@@ -874,10 +888,12 @@ AM_CONDITIONAL(HAVE_FLAC, test x$enable_flac = xyes)
|
|||||||
enable_flac_encoder=$enable_flac
|
enable_flac_encoder=$enable_flac
|
||||||
|
|
||||||
dnl -------------------------------- FluidSynth -------------------------------
|
dnl -------------------------------- FluidSynth -------------------------------
|
||||||
|
|
||||||
|
MPD_AUTO_PKG(fluidsynth, FLUIDSYNTH, [fluidsynth >= 1.1],
|
||||||
|
[fluidsynth decoder], [fluidsynth not found])
|
||||||
|
|
||||||
if test x$enable_fluidsynth = xyes; then
|
if test x$enable_fluidsynth = xyes; then
|
||||||
PKG_CHECK_MODULES(FLUIDSYNTH, [fluidsynth >= 1.1],
|
AC_DEFINE(ENABLE_FLUIDSYNTH, 1, [Define for fluidsynth support])
|
||||||
AC_DEFINE(ENABLE_FLUIDSYNTH, 1, [Define for fluidsynth support]),
|
|
||||||
enable_fluidsynth=no)
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
AM_CONDITIONAL(ENABLE_FLUIDSYNTH, test x$enable_fluidsynth = xyes)
|
AM_CONDITIONAL(ENABLE_FLUIDSYNTH, test x$enable_fluidsynth = xyes)
|
||||||
@@ -957,7 +973,7 @@ AM_CONDITIONAL(ENABLE_SNDFILE, test x$enable_sndfile = xyes)
|
|||||||
|
|
||||||
dnl --------------------------------- musepack --------------------------------
|
dnl --------------------------------- musepack --------------------------------
|
||||||
|
|
||||||
MPD_AUTO_LIB(mpc, MPCDEC, mpcdec, main, [-lmpcdec], [],
|
MPD_AUTO_LIB(mpc, MPCDEC, mpcdec, mpc_demux_init, [-lmpcdec], [],
|
||||||
[mpcdec], [libmpcdec not found])
|
[mpcdec], [libmpcdec not found])
|
||||||
if test x$enable_mpc = xyes; then
|
if test x$enable_mpc = xyes; then
|
||||||
AC_DEFINE(HAVE_MPCDEC, 1, [Define to use libmpcdec for MPC decoding])
|
AC_DEFINE(HAVE_MPCDEC, 1, [Define to use libmpcdec for MPC decoding])
|
||||||
@@ -1020,7 +1036,7 @@ if test x$enable_tremor = xyes; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
MPD_AUTO_PKG(vorbis, VORBIS, [vorbis vorbisfile ogg],
|
MPD_AUTO_PKG(vorbis, VORBIS, [vorbisfile vorbis ogg],
|
||||||
[Ogg Vorbis decoder], [libvorbis not found])
|
[Ogg Vorbis decoder], [libvorbis not found])
|
||||||
if test x$enable_vorbis = xyes; then
|
if test x$enable_vorbis = xyes; then
|
||||||
AC_DEFINE(ENABLE_VORBIS_DECODER, 1, [Define for Ogg Vorbis support])
|
AC_DEFINE(ENABLE_VORBIS_DECODER, 1, [Define for Ogg Vorbis support])
|
||||||
@@ -1078,27 +1094,6 @@ AM_CONDITIONAL(ENABLE_WILDMIDI, test x$enable_wildmidi = xyes)
|
|||||||
|
|
||||||
dnl ------------------------ Post Decoder Plugins Tests -----------------------
|
dnl ------------------------ Post Decoder Plugins Tests -----------------------
|
||||||
|
|
||||||
if
|
|
||||||
test x$enable_aac = xno &&
|
|
||||||
test x$enable_audiofile = xno &&
|
|
||||||
test x$enable_ffmpeg = xno &&
|
|
||||||
test x$enable_flac = xno &&
|
|
||||||
test x$enable_fluidsynth = xno &&
|
|
||||||
test x$enable_mad = xno &&
|
|
||||||
test x$enable_mikmod = xno; then
|
|
||||||
test x$enable_modplug = xno &&
|
|
||||||
test x$enable_mpc = xno &&
|
|
||||||
test x$enable_mpg123 = xno &&
|
|
||||||
test x$enable_opus = xno &&
|
|
||||||
test x$enable_sidplay = xno &&
|
|
||||||
test x$enable_tremor = xno &&
|
|
||||||
test x$enable_vorbis = xno &&
|
|
||||||
test x$enable_wavpack = xno &&
|
|
||||||
test x$enable_wildmidi = xno &&
|
|
||||||
|
|
||||||
AC_MSG_ERROR([No input plugins supported!])
|
|
||||||
fi
|
|
||||||
|
|
||||||
AM_CONDITIONAL(HAVE_XIPH,
|
AM_CONDITIONAL(HAVE_XIPH,
|
||||||
test x$enable_vorbis = xyes || test x$enable_tremor = xyes || test x$enable_flac = xyes || test x$enable_opus = xyes)
|
test x$enable_vorbis = xyes || test x$enable_tremor = xyes || test x$enable_flac = xyes || test x$enable_opus = xyes)
|
||||||
|
|
||||||
@@ -1136,7 +1131,7 @@ fi
|
|||||||
AM_CONDITIONAL(ENABLE_FLAC_ENCODER, test x$enable_flac_encoder = xyes)
|
AM_CONDITIONAL(ENABLE_FLAC_ENCODER, test x$enable_flac_encoder = xyes)
|
||||||
|
|
||||||
dnl ---------------------------- Ogg Vorbis Encoder ---------------------------
|
dnl ---------------------------- Ogg Vorbis Encoder ---------------------------
|
||||||
MPD_AUTO_PKG(vorbis_encoder, VORBISENC, [vorbisenc],
|
MPD_AUTO_PKG(vorbis_encoder, VORBISENC, [vorbisenc vorbis ogg],
|
||||||
[Ogg Vorbis encoder], [libvorbisenc not found])
|
[Ogg Vorbis encoder], [libvorbisenc not found])
|
||||||
|
|
||||||
if test x$enable_vorbis_encoder = xyes; then
|
if test x$enable_vorbis_encoder = xyes; then
|
||||||
@@ -1407,27 +1402,6 @@ esac
|
|||||||
|
|
||||||
AM_CONDITIONAL(ENABLE_WINMM_OUTPUT, test x$enable_winmm_output = xyes)
|
AM_CONDITIONAL(ENABLE_WINMM_OUTPUT, test x$enable_winmm_output = xyes)
|
||||||
|
|
||||||
dnl --------------------- Post Audio Output Plugins Tests ---------------------
|
|
||||||
if
|
|
||||||
test x$enable_alsa = xno &&
|
|
||||||
test x$enable_roar = xno &&
|
|
||||||
test x$enable_ao = xno &&
|
|
||||||
test x$enable_fifo = xno &&
|
|
||||||
test x$enable_httpd_output = xno &&
|
|
||||||
test x$enable_jack = xno &&
|
|
||||||
test x$enable_openal = xno &&
|
|
||||||
test x$enable_oss = xno &&
|
|
||||||
test x$enable_osx = xno &&
|
|
||||||
test x$enable_pipe_output = xno &&
|
|
||||||
test x$enable_pulse = xno &&
|
|
||||||
test x$enable_recorder_output = xno &&
|
|
||||||
test x$enable_shout = xno &&
|
|
||||||
test x$enable_solaris_output = xno &&
|
|
||||||
test x$enable_winmm_output = xno; then
|
|
||||||
|
|
||||||
AC_MSG_ERROR([No Audio Output types configured!])
|
|
||||||
fi
|
|
||||||
|
|
||||||
dnl ---------------------------------------------------------------------------
|
dnl ---------------------------------------------------------------------------
|
||||||
dnl Documentation
|
dnl Documentation
|
||||||
dnl ---------------------------------------------------------------------------
|
dnl ---------------------------------------------------------------------------
|
||||||
@@ -1549,6 +1523,7 @@ results(un,[UNIX Domain Sockets])
|
|||||||
printf '\nFile format support:\n\t'
|
printf '\nFile format support:\n\t'
|
||||||
results(aac, [AAC])
|
results(aac, [AAC])
|
||||||
results(adplug, [AdPlug])
|
results(adplug, [AdPlug])
|
||||||
|
results(dsd, [DSD])
|
||||||
results(sidplay, [C64 SID])
|
results(sidplay, [C64 SID])
|
||||||
results(ffmpeg, [FFMPEG])
|
results(ffmpeg, [FFMPEG])
|
||||||
results(flac, [FLAC])
|
results(flac, [FLAC])
|
||||||
|
@@ -155,7 +155,7 @@ foo(const char *abc, int xyz)
|
|||||||
|
|
||||||
<para>
|
<para>
|
||||||
Send your patches to the mailing list:
|
Send your patches to the mailing list:
|
||||||
musicpd-dev-team@lists.sourceforge.net
|
mpd-devel@musicpd.org
|
||||||
</para>
|
</para>
|
||||||
</chapter>
|
</chapter>
|
||||||
</book>
|
</book>
|
||||||
|
131
doc/mpd.conf.5
131
doc/mpd.conf.5
@@ -363,137 +363,6 @@ errors on bandwidth-limited devices. Some users have reported good results
|
|||||||
with this set to 50000, but not all devices support values this high. Most
|
with this set to 50000, but not all devices support values this high. Most
|
||||||
users do not need to change this. The default is 256000000 / sample_rate(kHz),
|
users do not need to change this. The default is 256000000 / sample_rate(kHz),
|
||||||
or 5804 microseconds for CD-quality audio.
|
or 5804 microseconds for CD-quality audio.
|
||||||
.SH OPTIONAL OSS OUTPUT PARAMETERS
|
|
||||||
.TP
|
|
||||||
.B device <dev>
|
|
||||||
This specifies the device to use for audio output. The default is "/dev/dsp".
|
|
||||||
.TP
|
|
||||||
.B mixer_device <mixer dev>
|
|
||||||
This specifies which mixer to use. The default is "/dev/mixer".
|
|
||||||
.TP
|
|
||||||
.B mixer_control <mixer ctrl>
|
|
||||||
This specifies which mixer control to use (sometimes referred to as the
|
|
||||||
"device"). The default is to use the main PCM mixer. An example is "Pcm".
|
|
||||||
.SH OPTIONAL PULSE OUTPUT PARAMETERS
|
|
||||||
.TP
|
|
||||||
.B server <server list>
|
|
||||||
A space separated list of servers to try to connect to. See
|
|
||||||
<\fBhttp://www.pulseaudio.org/wiki/ServerStrings\fP> for more details. The
|
|
||||||
default is to let PulseAudio choose a server.
|
|
||||||
If you specify more than one server name, MPD tries to connect to one
|
|
||||||
after another until it successfully establishes a connection.
|
|
||||||
.TP
|
|
||||||
.B sink <sink>
|
|
||||||
The sink to output to. The default is to let PulseAudio choose a sink.
|
|
||||||
.SH OPTIONAL JACK OUTPUT PARAMETERS
|
|
||||||
.TP
|
|
||||||
.B client_name <name>
|
|
||||||
The client name to use when connecting to JACK. The output ports <name>:left
|
|
||||||
and <name>:right will also be created for the left and right channels,
|
|
||||||
respectively.
|
|
||||||
.TP
|
|
||||||
.B ports <left_port,right_port>
|
|
||||||
This specifies the left and right ports to connect to for the left and right
|
|
||||||
channels, respectively. The default is to let JACK choose a pair of ports.
|
|
||||||
.TP
|
|
||||||
.B ringbuffer_size <size in bytes>
|
|
||||||
This specifies the size of the ringbuffer in bytes. The default is 32768.
|
|
||||||
.SH OPTIONAL AO OUTPUT PARAMETERS
|
|
||||||
.TP
|
|
||||||
.B driver <driver>
|
|
||||||
This specifies the libao driver to use for audio output. Possible values
|
|
||||||
depend on what libao drivers are available. See
|
|
||||||
<\fBhttp://www.xiph.org/ao/doc/drivers.html\fP> for information on some
|
|
||||||
commonly used drivers. Typical values for Linux include "oss" and "alsa09".
|
|
||||||
The default is "default", which causes libao to select an appropriate plugin.
|
|
||||||
.TP
|
|
||||||
.B options <opts>
|
|
||||||
This specifies the options to use for the selected libao driver. For oss, the
|
|
||||||
only option available is "dsp". For alsa09, the available options are: "dev",
|
|
||||||
"buf_size", and "periods". See <\fBhttp://www.xiph.org/ao/doc/drivers.html\fP>
|
|
||||||
for available options for some commonly used drivers. Options are assigned
|
|
||||||
using "=", and ";" is used to separate options. An example for oss:
|
|
||||||
"dsp=/dev/dsp". An example for alsa09: "dev=hw:0,0;buf_size=4096". The
|
|
||||||
default is "".
|
|
||||||
.TP
|
|
||||||
.B write_size <size in bytes>
|
|
||||||
This specifies how many bytes to write to the audio device at once. This
|
|
||||||
parameter is to work around a bug in older versions of libao on sound cards
|
|
||||||
with very small buffers. The default is 1024.
|
|
||||||
.SH REQUIRED FIFO OUTPUT PARAMETERS
|
|
||||||
.TP
|
|
||||||
.B path <path>
|
|
||||||
This specifies the path of the FIFO to output to. Must be an absolute path.
|
|
||||||
If the path does not exist it will be created when mpd is started, and removed
|
|
||||||
when mpd is stopped. The FIFO will be created with the same user and group as
|
|
||||||
mpd is running as. Default permissions can be modified by using the builtin
|
|
||||||
shell command "umask". If a FIFO already exists at the specified path it will
|
|
||||||
be reused, and will \fBnot\fP be removed when mpd is stopped. You can use the
|
|
||||||
"mkfifo" command to create this, and then you may modify the permissions to
|
|
||||||
your liking.
|
|
||||||
.SH REQUIRED SHOUT OUTPUT PARAMETERS
|
|
||||||
.TP
|
|
||||||
.B name <name>
|
|
||||||
This specifies not only the unique audio output name, but also the stream
|
|
||||||
title.
|
|
||||||
.TP
|
|
||||||
.B host <hostname>
|
|
||||||
This specifies the hostname of the icecast server to connect to.
|
|
||||||
.TP
|
|
||||||
.B port <port>
|
|
||||||
This specifies the port of the icecast server to connect to.
|
|
||||||
.TP
|
|
||||||
.B mount <mountpoint>
|
|
||||||
This specifies the icecast mountpoint to use.
|
|
||||||
.TP
|
|
||||||
.B password <password>
|
|
||||||
This specifies the password to use when logging in to the icecast server.
|
|
||||||
.TP
|
|
||||||
.B quality <quality>
|
|
||||||
This specifies the encoding quality to use. The value must be between 0
|
|
||||||
and 10. Fractional values, such as 2.5, are permitted. Either the quality or
|
|
||||||
the bitrate parameter must be specified, but not both. For Ogg, a
|
|
||||||
higher quality number produces higher quality output. For MP3, it's
|
|
||||||
just the opposite, with lower numbers producing higher quality output.
|
|
||||||
.TP
|
|
||||||
.B bitrate <kbps>
|
|
||||||
This specifies the bitrate to use for encoding. Either the quality or the
|
|
||||||
bitrate parameter must be specified, but not both.
|
|
||||||
.TP
|
|
||||||
.B format <sample_rate:bits:channels>
|
|
||||||
This specifies the sample rate, bits per sample, and number of channels to use
|
|
||||||
for encoding.
|
|
||||||
.SH OPTIONAL SHOUT OUTPUT PARAMETERS
|
|
||||||
.TP
|
|
||||||
.B encoding <encoding>
|
|
||||||
This specifies which output encoding to use. Should be either "ogg"
|
|
||||||
or "mp3", "mp3" is needed for shoutcast streaming. The default is "ogg".
|
|
||||||
.TP
|
|
||||||
.B protocol <protocol>
|
|
||||||
This specifies the protocol that wil be used to connect to the
|
|
||||||
icecast/shoutcast server. The options are "shoutcast", "icecast1" and
|
|
||||||
"icecast2". The default is "icecast2".
|
|
||||||
.TP
|
|
||||||
.B user <username>
|
|
||||||
This specifies the username to use when logging in to the icecast server. The
|
|
||||||
default is "source".
|
|
||||||
.TP
|
|
||||||
.B public <yes or no>
|
|
||||||
This specifies whether to request that the stream be listed in all public
|
|
||||||
stream directories that the icecast server knows about. The default is no.
|
|
||||||
.TP
|
|
||||||
.B timeout <seconds>
|
|
||||||
This specifies the number of seconds to wait before giving up on trying to
|
|
||||||
connect to the icecast server. The default is 2 seconds.
|
|
||||||
.TP
|
|
||||||
.B description <description>
|
|
||||||
This specifies a description of the stream.
|
|
||||||
.TP
|
|
||||||
.B url <url>
|
|
||||||
This specifies a URL associated with the stream.
|
|
||||||
.TP
|
|
||||||
.B genre <genre>
|
|
||||||
This specifies the genre(s) of the stream.
|
|
||||||
.SH FILES
|
.SH FILES
|
||||||
.TP
|
.TP
|
||||||
.BI ~/.mpdconf
|
.BI ~/.mpdconf
|
||||||
|
@@ -576,7 +576,12 @@
|
|||||||
</listitem>
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
<varname>songs</varname>: number of albums
|
<varname>albums</varname>: number of albums
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<varname>songs</varname>: number of songs
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
|
144
doc/user.xml
144
doc/user.xml
@@ -829,7 +829,7 @@ systemctl start mpd.socket</programlisting>
|
|||||||
<tbody>
|
<tbody>
|
||||||
<row>
|
<row>
|
||||||
<entry>
|
<entry>
|
||||||
<varname>default_bute_order</varname>
|
<varname>default_byte_order</varname>
|
||||||
<parameter>little_endian|big_endian</parameter>
|
<parameter>little_endian|big_endian</parameter>
|
||||||
</entry>
|
</entry>
|
||||||
<entry>
|
<entry>
|
||||||
@@ -1398,6 +1398,59 @@ systemctl start mpd.socket</programlisting>
|
|||||||
The <varname>ao</varname> plugin uses the portable
|
The <varname>ao</varname> plugin uses the portable
|
||||||
<filename>libao</filename> library.
|
<filename>libao</filename> library.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<informaltable>
|
||||||
|
<tgroup cols="2">
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>Setting</entry>
|
||||||
|
<entry>Description</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
<varname>driver</varname>
|
||||||
|
<parameter>D</parameter>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
The <filename>libao</filename> driver to use for
|
||||||
|
audio output. Possible values depend on what libao
|
||||||
|
drivers are available. See <ulink
|
||||||
|
url="http://www.xiph.org/ao/doc/drivers.html">http://www.xiph.org/ao/doc/drivers.html</ulink>
|
||||||
|
for information on some commonly used drivers.
|
||||||
|
Typical values for Linux include "oss" and "alsa09".
|
||||||
|
The default is "default", which causes libao to
|
||||||
|
select an appropriate plugin.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
<varname>options</varname>
|
||||||
|
<parameter>O</parameter>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
Options to pass to the selected
|
||||||
|
<filename>libao</filename> driver.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
<varname>write_size</varname>
|
||||||
|
<parameter>O</parameter>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
This specifies how many bytes to write to the audio
|
||||||
|
device at once. This parameter is to work around a
|
||||||
|
bug in older versions of libao on sound cards with
|
||||||
|
very small buffers. The default is 1024.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</informaltable>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
@@ -1408,6 +1461,38 @@ systemctl start mpd.socket</programlisting>
|
|||||||
FIFO (First In, First Out) file. The data can be read by
|
FIFO (First In, First Out) file. The data can be read by
|
||||||
another program.
|
another program.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<informaltable>
|
||||||
|
<tgroup cols="2">
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>Setting</entry>
|
||||||
|
<entry>Description</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
<varname>path</varname>
|
||||||
|
<parameter>P</parameter>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
This specifies the path of the FIFO to write to.
|
||||||
|
Must be an absolute path. If the path does not
|
||||||
|
exist, it will be created when MPD is started, and
|
||||||
|
removed when MPD is stopped. The FIFO will be
|
||||||
|
created with the same user and group as MPD is
|
||||||
|
running as. Default permissions can be modified by
|
||||||
|
using the builtin shell command "umask". If a FIFO
|
||||||
|
already exists at the specified path it will be
|
||||||
|
reused, and will not be removed when MPD is stopped.
|
||||||
|
You can use the "mkfifo" command to create this, and
|
||||||
|
then you may modify the permissions to your liking.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</informaltable>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
@@ -1767,6 +1852,51 @@ systemctl start mpd.socket</programlisting>
|
|||||||
</informaltable>
|
</informaltable>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<title><varname>roar</varname></title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <varname>roar</varname> plugin connects to a <ulink
|
||||||
|
url="http://roaraudio.keep-cool.org/">RoarAudio</ulink>
|
||||||
|
server.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<informaltable>
|
||||||
|
<tgroup cols="2">
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>Setting</entry>
|
||||||
|
<entry>Description</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
<varname>server</varname>
|
||||||
|
<parameter>HOSTNAME</parameter>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
The host name of the RoarAudio server. If not
|
||||||
|
specified, then MPD will connect to the default
|
||||||
|
locations.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
<varname>role</varname>
|
||||||
|
<parameter>ROLE</parameter>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
The "role" that MPD registers itself as in the
|
||||||
|
RoarAudio server. The default is "music".
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</informaltable>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<title><varname>recorder</varname></title>
|
<title><varname>recorder</varname></title>
|
||||||
|
|
||||||
@@ -1883,6 +2013,18 @@ systemctl start mpd.socket</programlisting>
|
|||||||
Defaults to 2 seconds.
|
Defaults to 2 seconds.
|
||||||
</entry>
|
</entry>
|
||||||
</row>
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
<varname>protocol</varname>
|
||||||
|
<parameter>icecast2|icecast1|shoutcast</parameter>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
Specifies the protocol that wil be used to connect
|
||||||
|
to the icecast/shoutcast server. The default
|
||||||
|
is "<parameter>icecast2</parameter>".
|
||||||
|
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
<row>
|
<row>
|
||||||
<entry>
|
<entry>
|
||||||
<varname>mount</varname>
|
<varname>mount</varname>
|
||||||
|
31
m4/faad.m4
31
m4/faad.m4
@@ -62,36 +62,7 @@ int main() {
|
|||||||
CPPFLAGS=$oldcppflags
|
CPPFLAGS=$oldcppflags
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if test x$enable_aac = xyes; then
|
if test x$enable_aac = xno; then
|
||||||
oldcflags=$CFLAGS
|
|
||||||
oldlibs=$LIBS
|
|
||||||
oldcppflags=$CPPFLAGS
|
|
||||||
CFLAGS="$CFLAGS $FAAD_CFLAGS -Werror"
|
|
||||||
LIBS="$LIBS $FAAD_LIBS"
|
|
||||||
CPPFLAGS=$CFLAGS
|
|
||||||
|
|
||||||
AC_MSG_CHECKING(for broken libfaad headers)
|
|
||||||
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
|
|
||||||
#include <faad.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
unsigned char channels;
|
|
||||||
uint32_t sample_rate;
|
|
||||||
|
|
||||||
NeAACDecInit2(NULL, NULL, 0, &sample_rate, &channels);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
])],
|
|
||||||
[AC_MSG_RESULT(correct)],
|
|
||||||
[AC_MSG_RESULT(broken);
|
|
||||||
AC_DEFINE(HAVE_FAAD_LONG, 1, [Define if faad.h uses the broken "unsigned long" pointers])])
|
|
||||||
|
|
||||||
CFLAGS=$oldcflags
|
|
||||||
LIBS=$oldlibs
|
|
||||||
CPPFLAGS=$oldcppflags
|
|
||||||
else
|
|
||||||
FAAD_LIBS=""
|
FAAD_LIBS=""
|
||||||
FAAD_CFLAGS=""
|
FAAD_CFLAGS=""
|
||||||
fi
|
fi
|
||||||
|
41
src/BulkEdit.hxx
Normal file
41
src/BulkEdit.hxx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_BULK_EDIT_HXX
|
||||||
|
#define MPD_BULK_EDIT_HXX
|
||||||
|
|
||||||
|
#include "Partition.hxx"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin a "bulk edit" and commit it automatically.
|
||||||
|
*/
|
||||||
|
class ScopeBulkEdit {
|
||||||
|
Partition &partition;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ScopeBulkEdit(Partition &_partition):partition(_partition) {
|
||||||
|
partition.playlist.BeginBulk();
|
||||||
|
}
|
||||||
|
|
||||||
|
~ScopeBulkEdit() {
|
||||||
|
partition.playlist.CommitBulk(partition.pc);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@@ -109,7 +109,7 @@ public:
|
|||||||
* a local (UNIX domain) socket?
|
* a local (UNIX domain) socket?
|
||||||
*/
|
*/
|
||||||
bool IsLocal() const {
|
bool IsLocal() const {
|
||||||
return uid > 0;
|
return uid >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned GetPermission() const {
|
unsigned GetPermission() const {
|
||||||
|
@@ -47,7 +47,7 @@ client_allow_file(const Client &client, Path path_fs, Error &error)
|
|||||||
instance */
|
instance */
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (uid <= 0) {
|
if (uid < 0) {
|
||||||
/* unauthenticated client */
|
/* unauthenticated client */
|
||||||
error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied");
|
error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied");
|
||||||
return false;
|
return false;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2013 The Music Player Daemon Project
|
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -69,7 +69,7 @@ static void version(void)
|
|||||||
puts("Music Player Daemon " VERSION "\n"
|
puts("Music Player Daemon " VERSION "\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
|
"Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
|
||||||
"Copyright (C) 2008-2013 Max Kellermann <max@duempel.org>\n"
|
"Copyright (C) 2008-2014 Max Kellermann <max@duempel.org>\n"
|
||||||
"This is free software; see the source for copying conditions. There is NO\n"
|
"This is free software; see the source for copying conditions. There is NO\n"
|
||||||
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
|
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
@@ -52,7 +52,7 @@ static char *user_name;
|
|||||||
static uid_t user_uid = (uid_t)-1;
|
static uid_t user_uid = (uid_t)-1;
|
||||||
|
|
||||||
/** the Unix group id which MPD runs as */
|
/** the Unix group id which MPD runs as */
|
||||||
static gid_t user_gid = (pid_t)-1;
|
static gid_t user_gid = (gid_t)-1;
|
||||||
|
|
||||||
/** the absolute path of the pidfile */
|
/** the absolute path of the pidfile */
|
||||||
static AllocatedPath pidfile = AllocatedPath::Null();
|
static AllocatedPath pidfile = AllocatedPath::Null();
|
||||||
@@ -106,18 +106,21 @@ daemonize_set_user(void)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
/* set gid */
|
/* set gid */
|
||||||
if (user_gid != (gid_t)-1 && user_gid != getgid()) {
|
if (user_gid != (gid_t)-1 && user_gid != getgid() &&
|
||||||
if (setgid(user_gid) == -1) {
|
setgid(user_gid) == -1) {
|
||||||
FormatFatalSystemError("Failed to set group %d",
|
FormatFatalSystemError("Failed to set group %d",
|
||||||
(int)user_gid);
|
(int)user_gid);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _BSD_SOURCE
|
#ifdef _BSD_SOURCE
|
||||||
/* init suplementary groups
|
/* init supplementary groups
|
||||||
* (must be done before we change our uid)
|
* (must be done before we change our uid)
|
||||||
*/
|
*/
|
||||||
if (!had_group && initgroups(user_name, user_gid) == -1) {
|
if (!had_group &&
|
||||||
|
/* no need to set the new user's supplementary groups if
|
||||||
|
we are already this user */
|
||||||
|
user_uid != getuid() &&
|
||||||
|
initgroups(user_name, user_gid) == -1) {
|
||||||
FormatFatalSystemError("Failed to set supplementary groups "
|
FormatFatalSystemError("Failed to set supplementary groups "
|
||||||
"of user \"%s\"",
|
"of user \"%s\"",
|
||||||
user_name);
|
user_name);
|
||||||
|
@@ -148,12 +148,12 @@ DatabaseGlobalOpen(Error &error)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
time_t
|
bool
|
||||||
db_get_mtime(void)
|
db_exists()
|
||||||
{
|
{
|
||||||
assert(db != nullptr);
|
assert(db != nullptr);
|
||||||
assert(db_is_open);
|
assert(db_is_open);
|
||||||
assert(db_is_simple());
|
assert(db_is_simple());
|
||||||
|
|
||||||
return ((SimpleDatabase *)db)->GetLastModified();
|
return ((SimpleDatabase *)db)->GetUpdateStamp() > 0;
|
||||||
}
|
}
|
||||||
|
@@ -30,6 +30,8 @@
|
|||||||
#include "tag/TagType.h"
|
#include "tag/TagType.h"
|
||||||
#include "Compiler.h"
|
#include "Compiler.h"
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
struct config_param;
|
struct config_param;
|
||||||
struct DatabaseSelection;
|
struct DatabaseSelection;
|
||||||
struct db_visitor;
|
struct db_visitor;
|
||||||
@@ -132,6 +134,13 @@ public:
|
|||||||
virtual bool GetStats(const DatabaseSelection &selection,
|
virtual bool GetStats(const DatabaseSelection &selection,
|
||||||
DatabaseStats &stats,
|
DatabaseStats &stats,
|
||||||
Error &error) const = 0;
|
Error &error) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the time stamp of the last database update.
|
||||||
|
* Returns 0 if that is not not known/available.
|
||||||
|
*/
|
||||||
|
gcc_pure
|
||||||
|
virtual time_t GetUpdateStamp() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DatabasePlugin {
|
struct DatabasePlugin {
|
||||||
|
@@ -30,6 +30,18 @@ DatabaseSelection::DatabaseSelection(const char *_uri, bool _recursive,
|
|||||||
uri = filter->GetBase();
|
uri = filter->GetBase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
DatabaseSelection::IsEmpty() const
|
||||||
|
{
|
||||||
|
return uri.empty() && (filter == nullptr || filter->IsEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
DatabaseSelection::HasOtherThanBase() const
|
||||||
|
{
|
||||||
|
return filter != nullptr && filter->HasOtherThanBase();
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
DatabaseSelection::Match(const Song &song) const
|
DatabaseSelection::Match(const Song &song) const
|
||||||
{
|
{
|
||||||
|
@@ -44,6 +44,15 @@ struct DatabaseSelection {
|
|||||||
DatabaseSelection(const char *_uri, bool _recursive,
|
DatabaseSelection(const char *_uri, bool _recursive,
|
||||||
const SongFilter *_filter=nullptr);
|
const SongFilter *_filter=nullptr);
|
||||||
|
|
||||||
|
gcc_pure
|
||||||
|
bool IsEmpty() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this selection contain constraints other than "base"?
|
||||||
|
*/
|
||||||
|
gcc_pure
|
||||||
|
bool HasOtherThanBase() const;
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
bool Match(const Song &song) const;
|
bool Match(const Song &song) const;
|
||||||
};
|
};
|
||||||
|
@@ -62,25 +62,13 @@ db_get_directory(const char *name);
|
|||||||
bool
|
bool
|
||||||
db_save(Error &error);
|
db_save(Error &error);
|
||||||
|
|
||||||
/**
|
|
||||||
* May only be used if db_is_simple() returns true.
|
|
||||||
*/
|
|
||||||
gcc_pure
|
|
||||||
time_t
|
|
||||||
db_get_mtime(void);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if there is a valid database file on the disk.
|
* Returns true if there is a valid database file on the disk.
|
||||||
*
|
*
|
||||||
* May only be used if db_is_simple() returns true.
|
* May only be used if db_is_simple() returns true.
|
||||||
*/
|
*/
|
||||||
gcc_pure
|
gcc_pure
|
||||||
static inline bool
|
bool
|
||||||
db_exists(void)
|
db_exists();
|
||||||
{
|
|
||||||
/* mtime is set only if the database file was loaded or saved
|
|
||||||
successfully */
|
|
||||||
return db_get_mtime() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -47,6 +47,7 @@ decoder_initialized(Decoder &decoder,
|
|||||||
|
|
||||||
assert(dc.state == DecoderState::START);
|
assert(dc.state == DecoderState::START);
|
||||||
assert(dc.pipe != nullptr);
|
assert(dc.pipe != nullptr);
|
||||||
|
assert(dc.pipe->IsEmpty());
|
||||||
assert(decoder.stream_tag == nullptr);
|
assert(decoder.stream_tag == nullptr);
|
||||||
assert(decoder.decoder_tag == nullptr);
|
assert(decoder.decoder_tag == nullptr);
|
||||||
assert(!decoder.seeking);
|
assert(!decoder.seeking);
|
||||||
@@ -292,6 +293,40 @@ decoder_read(Decoder *decoder,
|
|||||||
return nbytes;
|
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
|
void
|
||||||
decoder_timestamp(Decoder &decoder, double t)
|
decoder_timestamp(Decoder &decoder, double t)
|
||||||
{
|
{
|
||||||
@@ -371,6 +406,9 @@ decoder_data(Decoder &decoder,
|
|||||||
length == 0)
|
length == 0)
|
||||||
return cmd;
|
return cmd;
|
||||||
|
|
||||||
|
assert(!decoder.initial_seek_pending);
|
||||||
|
assert(!decoder.initial_seek_running);
|
||||||
|
|
||||||
/* send stream tags */
|
/* send stream tags */
|
||||||
|
|
||||||
if (update_stream_tag(decoder, is)) {
|
if (update_stream_tag(decoder, is)) {
|
||||||
|
@@ -112,6 +112,25 @@ decoder_read(Decoder &decoder, InputStream &is,
|
|||||||
return decoder_read(&decoder, is, buffer, length);
|
return decoder_read(&decoder, is, buffer, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocking read from the input stream. Attempts to fill the buffer
|
||||||
|
* completely; there is no partial result.
|
||||||
|
*
|
||||||
|
* @return true on success, false on error or command or not enough
|
||||||
|
* data
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
decoder_read_full(Decoder *decoder, InputStream &is,
|
||||||
|
void *buffer, size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skip data on the #InputStream.
|
||||||
|
*
|
||||||
|
* @return true on success, false on error or command
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
decoder_skip(Decoder *decoder, InputStream &is, size_t size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the time stamp for the next data chunk [seconds]. The MPD
|
* Sets the time stamp for the next data chunk [seconds]. The MPD
|
||||||
* core automatically counts it up, and a decoder plugin only needs to
|
* core automatically counts it up, and a decoder plugin only needs to
|
||||||
|
@@ -70,6 +70,12 @@ decoder_buffer_free(DecoderBuffer *buffer)
|
|||||||
g_free(buffer);
|
g_free(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const InputStream &
|
||||||
|
decoder_buffer_get_stream(const DecoderBuffer *buffer)
|
||||||
|
{
|
||||||
|
return *buffer->is;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
decoder_buffer_is_empty(const DecoderBuffer *buffer)
|
decoder_buffer_is_empty(const DecoderBuffer *buffer)
|
||||||
{
|
{
|
||||||
@@ -82,6 +88,12 @@ decoder_buffer_is_full(const DecoderBuffer *buffer)
|
|||||||
return buffer->consumed == 0 && buffer->length == buffer->size;
|
return buffer->consumed == 0 && buffer->length == buffer->size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
decoder_buffer_clear(DecoderBuffer *buffer)
|
||||||
|
{
|
||||||
|
buffer->length = buffer->consumed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
decoder_buffer_shift(DecoderBuffer *buffer)
|
decoder_buffer_shift(DecoderBuffer *buffer)
|
||||||
{
|
{
|
||||||
@@ -118,6 +130,12 @@ decoder_buffer_fill(DecoderBuffer *buffer)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
decoder_buffer_available(const DecoderBuffer *buffer)
|
||||||
|
{
|
||||||
|
return buffer->length - buffer->consumed;;
|
||||||
|
}
|
||||||
|
|
||||||
const void *
|
const void *
|
||||||
decoder_buffer_read(const DecoderBuffer *buffer, size_t *length_r)
|
decoder_buffer_read(const DecoderBuffer *buffer, size_t *length_r)
|
||||||
{
|
{
|
||||||
|
@@ -20,6 +20,8 @@
|
|||||||
#ifndef MPD_DECODER_BUFFER_HXX
|
#ifndef MPD_DECODER_BUFFER_HXX
|
||||||
#define MPD_DECODER_BUFFER_HXX
|
#define MPD_DECODER_BUFFER_HXX
|
||||||
|
|
||||||
|
#include "Compiler.h"
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,12 +52,21 @@ decoder_buffer_new(Decoder *decoder, InputStream &is,
|
|||||||
void
|
void
|
||||||
decoder_buffer_free(DecoderBuffer *buffer);
|
decoder_buffer_free(DecoderBuffer *buffer);
|
||||||
|
|
||||||
|
gcc_pure
|
||||||
|
const InputStream &
|
||||||
|
decoder_buffer_get_stream(const DecoderBuffer *buffer);
|
||||||
|
|
||||||
|
gcc_pure
|
||||||
bool
|
bool
|
||||||
decoder_buffer_is_empty(const DecoderBuffer *buffer);
|
decoder_buffer_is_empty(const DecoderBuffer *buffer);
|
||||||
|
|
||||||
|
gcc_pure
|
||||||
bool
|
bool
|
||||||
decoder_buffer_is_full(const DecoderBuffer *buffer);
|
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.
|
* Read data from the input_stream and append it to the buffer.
|
||||||
*
|
*
|
||||||
@@ -66,6 +77,13 @@ decoder_buffer_is_full(const DecoderBuffer *buffer);
|
|||||||
bool
|
bool
|
||||||
decoder_buffer_fill(DecoderBuffer *buffer);
|
decoder_buffer_fill(DecoderBuffer *buffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many bytes are stored in the buffer?
|
||||||
|
*/
|
||||||
|
gcc_pure
|
||||||
|
size_t
|
||||||
|
decoder_buffer_available(const DecoderBuffer *buffer);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads data from the buffer. This data is not yet consumed, you
|
* Reads data from the buffer. This data is not yet consumed, you
|
||||||
* have to call decoder_buffer_consume() to do that. The returned
|
* have to call decoder_buffer_consume() to do that. The returned
|
||||||
|
@@ -83,6 +83,9 @@ void
|
|||||||
decoder_flush_chunk(Decoder &decoder)
|
decoder_flush_chunk(Decoder &decoder)
|
||||||
{
|
{
|
||||||
DecoderControl &dc = decoder.dc;
|
DecoderControl &dc = decoder.dc;
|
||||||
|
assert(!decoder.seeking);
|
||||||
|
assert(!decoder.initial_seek_running);
|
||||||
|
assert(!decoder.initial_seek_pending);
|
||||||
|
|
||||||
assert(decoder.chunk != nullptr);
|
assert(decoder.chunk != nullptr);
|
||||||
|
|
||||||
|
@@ -73,8 +73,10 @@ const struct DecoderPlugin *const decoder_plugins[] = {
|
|||||||
#ifdef HAVE_AUDIOFILE
|
#ifdef HAVE_AUDIOFILE
|
||||||
&audiofile_decoder_plugin,
|
&audiofile_decoder_plugin,
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef ENABLE_DSD
|
||||||
&dsdiff_decoder_plugin,
|
&dsdiff_decoder_plugin,
|
||||||
&dsf_decoder_plugin,
|
&dsf_decoder_plugin,
|
||||||
|
#endif
|
||||||
#ifdef HAVE_FAAD
|
#ifdef HAVE_FAAD
|
||||||
&faad_decoder_plugin,
|
&faad_decoder_plugin,
|
||||||
#endif
|
#endif
|
||||||
|
@@ -26,6 +26,7 @@
|
|||||||
#include "Song.hxx"
|
#include "Song.hxx"
|
||||||
#include "system/FatalError.hxx"
|
#include "system/FatalError.hxx"
|
||||||
#include "Mapper.hxx"
|
#include "Mapper.hxx"
|
||||||
|
#include "MusicPipe.hxx"
|
||||||
#include "fs/Traits.hxx"
|
#include "fs/Traits.hxx"
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
#include "DecoderAPI.hxx"
|
#include "DecoderAPI.hxx"
|
||||||
@@ -211,7 +212,8 @@ static bool
|
|||||||
decoder_run_stream_locked(Decoder &decoder, InputStream &is,
|
decoder_run_stream_locked(Decoder &decoder, InputStream &is,
|
||||||
const char *uri, bool &tried_r)
|
const char *uri, bool &tried_r)
|
||||||
{
|
{
|
||||||
const char *const suffix = uri_get_suffix(uri);
|
UriSuffixBuffer suffix_buffer;
|
||||||
|
const char *const suffix = uri_get_suffix(uri, suffix_buffer);
|
||||||
|
|
||||||
using namespace std::placeholders;
|
using namespace std::placeholders;
|
||||||
const auto f = std::bind(decoder_run_stream_plugin,
|
const auto f = std::bind(decoder_run_stream_plugin,
|
||||||
@@ -418,9 +420,18 @@ decoder_task(void *arg)
|
|||||||
dc.replay_gain_prev_db = dc.replay_gain_db;
|
dc.replay_gain_prev_db = dc.replay_gain_db;
|
||||||
dc.replay_gain_db = 0;
|
dc.replay_gain_db = 0;
|
||||||
|
|
||||||
/* fall through */
|
decoder_run(dc);
|
||||||
|
break;
|
||||||
|
|
||||||
case DecoderCommand::SEEK:
|
case DecoderCommand::SEEK:
|
||||||
|
/* this seek was too late, and the decoder had
|
||||||
|
already finished; start a new decoder */
|
||||||
|
|
||||||
|
/* we need to clear the pipe here; usually the
|
||||||
|
PlayerThread is responsible, but it is not
|
||||||
|
aware that the decoder has finished */
|
||||||
|
dc.pipe->Clear(*dc.buffer);
|
||||||
|
|
||||||
decoder_run(dc);
|
decoder_run(dc);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@@ -81,31 +81,85 @@ icy_add_item(Tag &tag, TagType type, const char *value)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
icy_parse_tag_item(Tag &tag, const char *item)
|
icy_parse_tag_item(Tag &tag, const char *name, const char *value)
|
||||||
{
|
{
|
||||||
gchar **p = g_strsplit(item, "=", 0);
|
if (strcmp(name, "StreamTitle") == 0)
|
||||||
|
icy_add_item(tag, TAG_TITLE, value);
|
||||||
|
else
|
||||||
|
FormatDebug(icy_metadata_domain,
|
||||||
|
"unknown icy-tag: '%s'", name);
|
||||||
|
}
|
||||||
|
|
||||||
if (p[0] != nullptr && p[1] != nullptr) {
|
/**
|
||||||
if (strcmp(p[0], "StreamTitle") == 0)
|
* Find a single quote that is followed by a semicolon (or by the end
|
||||||
icy_add_item(tag, TAG_TITLE, p[1]);
|
* of the string). If that fails, return the first single quote. If
|
||||||
else
|
* that also fails, return #end.
|
||||||
FormatDebug(icy_metadata_domain,
|
*/
|
||||||
"unknown icy-tag: '%s'", p[0]);
|
static char *
|
||||||
|
find_end_quote(char *p, char *const end)
|
||||||
|
{
|
||||||
|
char *fallback = std::find(p, end, '\'');
|
||||||
|
if (fallback >= end - 1 || fallback[1] == ';')
|
||||||
|
return fallback;
|
||||||
|
|
||||||
|
p = fallback + 1;
|
||||||
|
while (true) {
|
||||||
|
p = std::find(p, end, '\'');
|
||||||
|
if (p == end)
|
||||||
|
return fallback;
|
||||||
|
|
||||||
|
if (p == end - 1 || p[1] == ';')
|
||||||
|
return p;
|
||||||
|
|
||||||
|
++p;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_strfreev(p);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Tag *
|
static Tag *
|
||||||
icy_parse_tag(const char *p)
|
icy_parse_tag(char *p, char *const end)
|
||||||
{
|
{
|
||||||
|
assert(p != nullptr);
|
||||||
|
assert(end != nullptr);
|
||||||
|
assert(p <= end);
|
||||||
|
|
||||||
Tag *tag = new Tag();
|
Tag *tag = new Tag();
|
||||||
gchar **items = g_strsplit(p, ";", 0);
|
|
||||||
|
|
||||||
for (unsigned i = 0; items[i] != nullptr; ++i)
|
while (p != end) {
|
||||||
icy_parse_tag_item(*tag, items[i]);
|
const char *const name = p;
|
||||||
|
char *eq = std::find(p, end, '=');
|
||||||
|
if (eq == end)
|
||||||
|
break;
|
||||||
|
|
||||||
g_strfreev(items);
|
*eq = 0;
|
||||||
|
p = eq + 1;
|
||||||
|
|
||||||
|
if (*p != '\'') {
|
||||||
|
/* syntax error; skip to the next semicolon,
|
||||||
|
try to recover */
|
||||||
|
char *semicolon = std::find(p, end, ';');
|
||||||
|
if (semicolon == end)
|
||||||
|
break;
|
||||||
|
p = semicolon + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
++p;
|
||||||
|
|
||||||
|
const char *const value = p;
|
||||||
|
char *quote = find_end_quote(p, end);
|
||||||
|
if (quote == end)
|
||||||
|
break;
|
||||||
|
|
||||||
|
*quote = 0;
|
||||||
|
p = quote + 1;
|
||||||
|
|
||||||
|
icy_parse_tag_item(*tag, name, value);
|
||||||
|
|
||||||
|
char *semicolon = std::find(p, end, ';');
|
||||||
|
if (semicolon == end)
|
||||||
|
break;
|
||||||
|
p = semicolon + 1;
|
||||||
|
}
|
||||||
|
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
@@ -152,15 +206,11 @@ IcyMetaDataParser::Meta(const void *data, size_t length)
|
|||||||
++length;
|
++length;
|
||||||
|
|
||||||
if (meta_position == meta_size) {
|
if (meta_position == meta_size) {
|
||||||
/* null-terminate the string */
|
|
||||||
|
|
||||||
meta_data[meta_size] = 0;
|
|
||||||
|
|
||||||
/* parse */
|
/* parse */
|
||||||
|
|
||||||
delete tag;
|
delete tag;
|
||||||
|
|
||||||
tag = icy_parse_tag(meta_data);
|
tag = icy_parse_tag(meta_data, meta_data + meta_size);
|
||||||
g_free(meta_data);
|
g_free(meta_data);
|
||||||
|
|
||||||
/* change back to normal data mode */
|
/* change back to normal data mode */
|
||||||
|
@@ -133,7 +133,9 @@ void mapper_finish(void)
|
|||||||
const char *
|
const char *
|
||||||
mapper_get_music_directory_utf8(void)
|
mapper_get_music_directory_utf8(void)
|
||||||
{
|
{
|
||||||
return music_dir_utf8.c_str();
|
return music_dir_utf8.empty()
|
||||||
|
? nullptr
|
||||||
|
: music_dir_utf8.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
const AllocatedPath &
|
const AllocatedPath &
|
||||||
|
@@ -41,7 +41,8 @@ mapper_init(AllocatedPath &&music_dir, AllocatedPath &&playlist_dir);
|
|||||||
void mapper_finish(void);
|
void mapper_finish(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the absolute path of the music directory encoded in UTF-8.
|
* Return the absolute path of the music directory encoded in UTF-8 or
|
||||||
|
* nullptr if no music directory was configured.
|
||||||
*/
|
*/
|
||||||
gcc_const
|
gcc_const
|
||||||
const char *
|
const char *
|
||||||
|
@@ -18,7 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef MPD_OUTPUT_API_HXX
|
#ifndef MPD_OUTPUT_API_HXX
|
||||||
#define MPD_OUTPUT_API_HxX
|
#define MPD_OUTPUT_API_HXX
|
||||||
|
|
||||||
#include "OutputPlugin.hxx"
|
#include "OutputPlugin.hxx"
|
||||||
#include "OutputInternal.hxx"
|
#include "OutputInternal.hxx"
|
||||||
|
@@ -101,6 +101,8 @@ audio_output_set_replay_gain_mode(struct audio_output *ao,
|
|||||||
{
|
{
|
||||||
if (ao->replay_gain_filter != nullptr)
|
if (ao->replay_gain_filter != nullptr)
|
||||||
replay_gain_filter_set_mode(ao->replay_gain_filter, mode);
|
replay_gain_filter_set_mode(ao->replay_gain_filter, mode);
|
||||||
|
if (ao->other_replay_gain_filter != nullptr)
|
||||||
|
replay_gain_filter_set_mode(ao->other_replay_gain_filter, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@@ -385,11 +385,20 @@ ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk,
|
|||||||
if (length > other_length)
|
if (length > other_length)
|
||||||
length = other_length;
|
length = other_length;
|
||||||
|
|
||||||
|
float mix_ratio = chunk->mix_ratio;
|
||||||
|
if (mix_ratio >= 0)
|
||||||
|
/* reverse the mix ratio (because the
|
||||||
|
arguments to pcm_mix() are reversed), but
|
||||||
|
only if the mix ratio is non-negative; a
|
||||||
|
negative mix ratio is a MixRamp special
|
||||||
|
case */
|
||||||
|
mix_ratio = 1.0 - mix_ratio;
|
||||||
|
|
||||||
void *dest = ao->cross_fade_buffer.Get(other_length);
|
void *dest = ao->cross_fade_buffer.Get(other_length);
|
||||||
memcpy(dest, other_data, other_length);
|
memcpy(dest, other_data, other_length);
|
||||||
if (!pcm_mix(dest, data, length,
|
if (!pcm_mix(dest, data, length,
|
||||||
ao->in_audio_format.format,
|
ao->in_audio_format.format,
|
||||||
1.0 - chunk->mix_ratio)) {
|
mix_ratio)) {
|
||||||
FormatError(output_domain,
|
FormatError(output_domain,
|
||||||
"Cannot cross-fade format %s",
|
"Cannot cross-fade format %s",
|
||||||
sample_format_to_string(ao->in_audio_format.format));
|
sample_format_to_string(ao->in_audio_format.format));
|
||||||
|
@@ -1081,8 +1081,11 @@ Player::Run()
|
|||||||
|
|
||||||
delete cross_fade_tag;
|
delete cross_fade_tag;
|
||||||
|
|
||||||
if (song != nullptr)
|
if (song != nullptr) {
|
||||||
|
const auto uri = song->GetURI();
|
||||||
|
FormatDefault(player_domain, "played \"%s\"", uri.c_str());
|
||||||
song->Free();
|
song->Free();
|
||||||
|
}
|
||||||
|
|
||||||
pc.Lock();
|
pc.Lock();
|
||||||
|
|
||||||
|
@@ -103,6 +103,12 @@ playlist::UpdateQueuedSong(PlayerControl &pc, const Song *prev)
|
|||||||
if (!playing)
|
if (!playing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (prev == nullptr && bulk_edit)
|
||||||
|
/* postponed until CommitBulk() to avoid always
|
||||||
|
queueing the first song that is being added (in
|
||||||
|
random mode) */
|
||||||
|
return;
|
||||||
|
|
||||||
assert(!queue.IsEmpty());
|
assert(!queue.IsEmpty());
|
||||||
assert((queued < 0) == (prev == nullptr));
|
assert((queued < 0) == (prev == nullptr));
|
||||||
|
|
||||||
@@ -294,7 +300,9 @@ playlist::SetRandom(PlayerControl &pc, bool status)
|
|||||||
if (queue.random) {
|
if (queue.random) {
|
||||||
/* shuffle the queue order, but preserve current */
|
/* shuffle the queue order, but preserve current */
|
||||||
|
|
||||||
const int current_position = GetCurrentPosition();
|
const int current_position = playing
|
||||||
|
? GetCurrentPosition()
|
||||||
|
: -1;
|
||||||
|
|
||||||
queue.ShuffleOrder();
|
queue.ShuffleOrder();
|
||||||
|
|
||||||
|
@@ -30,7 +30,7 @@ struct playlist {
|
|||||||
/**
|
/**
|
||||||
* The song queue - it contains the "real" playlist.
|
* The song queue - it contains the "real" playlist.
|
||||||
*/
|
*/
|
||||||
struct queue queue;
|
struct Queue queue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This value is true if the player is currently playing (or
|
* This value is true if the player is currently playing (or
|
||||||
@@ -45,6 +45,18 @@ struct playlist {
|
|||||||
*/
|
*/
|
||||||
bool stop_on_error;
|
bool stop_on_error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, then a bulk edit has been initiated by
|
||||||
|
* BeginBulk(), and UpdateQueuedSong() and OnModified() will
|
||||||
|
* be postponed until CommitBulk()
|
||||||
|
*/
|
||||||
|
bool bulk_edit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has the queue been modified during bulk edit mode?
|
||||||
|
*/
|
||||||
|
bool bulk_modified;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of errors since playback was started. If this
|
* Number of errors since playback was started. If this
|
||||||
* number exceeds the length of the playlist, MPD gives up,
|
* number exceeds the length of the playlist, MPD gives up,
|
||||||
@@ -69,7 +81,9 @@ struct playlist {
|
|||||||
int queued;
|
int queued;
|
||||||
|
|
||||||
playlist(unsigned max_length)
|
playlist(unsigned max_length)
|
||||||
:queue(max_length), playing(false), current(-1), queued(-1) {
|
:queue(max_length), playing(false),
|
||||||
|
bulk_edit(false),
|
||||||
|
current(-1), queued(-1) {
|
||||||
}
|
}
|
||||||
|
|
||||||
~playlist() {
|
~playlist() {
|
||||||
@@ -126,6 +140,9 @@ protected:
|
|||||||
void UpdateQueuedSong(PlayerControl &pc, const Song *prev);
|
void UpdateQueuedSong(PlayerControl &pc, const Song *prev);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
void BeginBulk();
|
||||||
|
void CommitBulk(PlayerControl &pc);
|
||||||
|
|
||||||
void Clear(PlayerControl &pc);
|
void Clear(PlayerControl &pc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -217,6 +234,10 @@ public:
|
|||||||
|
|
||||||
void PlayPrevious(PlayerControl &pc);
|
void PlayPrevious(PlayerControl &pc);
|
||||||
|
|
||||||
|
PlaylistResult SeekSongOrder(PlayerControl &pc,
|
||||||
|
unsigned song_order,
|
||||||
|
float seek_time);
|
||||||
|
|
||||||
PlaylistResult SeekSongPosition(PlayerControl &pc,
|
PlaylistResult SeekSongPosition(PlayerControl &pc,
|
||||||
unsigned song_position,
|
unsigned song_position,
|
||||||
float seek_time);
|
float seek_time);
|
||||||
|
@@ -153,7 +153,7 @@ playlist::PlayNext(PlayerControl &pc)
|
|||||||
queue.ShuffleOrder();
|
queue.ShuffleOrder();
|
||||||
|
|
||||||
/* note that current and queued are
|
/* note that current and queued are
|
||||||
now invalid, but playlist_play_order() will
|
now invalid, but PlayOrder() will
|
||||||
discard them anyway */
|
discard them anyway */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,17 +190,12 @@ playlist::PlayPrevious(PlayerControl &pc)
|
|||||||
}
|
}
|
||||||
|
|
||||||
PlaylistResult
|
PlaylistResult
|
||||||
playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time)
|
playlist::SeekSongOrder(PlayerControl &pc, unsigned i, float seek_time)
|
||||||
{
|
{
|
||||||
if (!queue.IsValidPosition(song))
|
assert(queue.IsValidOrder(i));
|
||||||
return PlaylistResult::BAD_RANGE;
|
|
||||||
|
|
||||||
const Song *queued_song = GetQueuedSong();
|
const Song *queued_song = GetQueuedSong();
|
||||||
|
|
||||||
unsigned i = queue.random
|
|
||||||
? queue.PositionToOrder(song)
|
|
||||||
: song;
|
|
||||||
|
|
||||||
pc.ClearError();
|
pc.ClearError();
|
||||||
stop_on_error = true;
|
stop_on_error = true;
|
||||||
error_count = 0;
|
error_count = 0;
|
||||||
@@ -228,6 +223,19 @@ playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time)
|
|||||||
return PlaylistResult::SUCCESS;
|
return PlaylistResult::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PlaylistResult
|
||||||
|
playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time)
|
||||||
|
{
|
||||||
|
if (!queue.IsValidPosition(song))
|
||||||
|
return PlaylistResult::BAD_RANGE;
|
||||||
|
|
||||||
|
unsigned i = queue.random
|
||||||
|
? queue.PositionToOrder(song)
|
||||||
|
: song;
|
||||||
|
|
||||||
|
return SeekSongOrder(pc, i, seek_time);
|
||||||
|
}
|
||||||
|
|
||||||
PlaylistResult
|
PlaylistResult
|
||||||
playlist::SeekSongId(PlayerControl &pc, unsigned id, float seek_time)
|
playlist::SeekSongId(PlayerControl &pc, unsigned id, float seek_time)
|
||||||
{
|
{
|
||||||
@@ -257,5 +265,5 @@ playlist::SeekCurrent(PlayerControl &pc, float seek_time, bool relative)
|
|||||||
if (seek_time < 0)
|
if (seek_time < 0)
|
||||||
seek_time = 0;
|
seek_time = 0;
|
||||||
|
|
||||||
return SeekSongPosition(pc, current, seek_time);
|
return SeekSongOrder(pc, current, seek_time);
|
||||||
}
|
}
|
||||||
|
@@ -40,6 +40,12 @@
|
|||||||
void
|
void
|
||||||
playlist::OnModified()
|
playlist::OnModified()
|
||||||
{
|
{
|
||||||
|
if (bulk_edit) {
|
||||||
|
/* postponed to CommitBulk() */
|
||||||
|
bulk_modified = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
queue.IncrementVersion();
|
queue.IncrementVersion();
|
||||||
|
|
||||||
idle_add(IDLE_PLAYLIST);
|
idle_add(IDLE_PLAYLIST);
|
||||||
@@ -56,6 +62,35 @@ playlist::Clear(PlayerControl &pc)
|
|||||||
OnModified();
|
OnModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
playlist::BeginBulk()
|
||||||
|
{
|
||||||
|
assert(!bulk_edit);
|
||||||
|
|
||||||
|
bulk_edit = true;
|
||||||
|
bulk_modified = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
playlist::CommitBulk(PlayerControl &pc)
|
||||||
|
{
|
||||||
|
assert(bulk_edit);
|
||||||
|
|
||||||
|
bulk_edit = false;
|
||||||
|
if (!bulk_modified)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (queued < 0)
|
||||||
|
/* if no song was queued, UpdateQueuedSong() is being
|
||||||
|
ignored in "bulk" edit mode; now that we have
|
||||||
|
shuffled all new songs, we can pick a random one
|
||||||
|
(instead of always picking the first one that was
|
||||||
|
added) */
|
||||||
|
UpdateQueuedSong(pc, nullptr);
|
||||||
|
|
||||||
|
OnModified();
|
||||||
|
}
|
||||||
|
|
||||||
PlaylistResult
|
PlaylistResult
|
||||||
playlist::AppendFile(PlayerControl &pc,
|
playlist::AppendFile(PlayerControl &pc,
|
||||||
const char *path_utf8, unsigned *added_id)
|
const char *path_utf8, unsigned *added_id)
|
||||||
@@ -234,12 +269,8 @@ playlist::DeleteInternal(PlayerControl &pc,
|
|||||||
if (playing && current == (int)songOrder) {
|
if (playing && current == (int)songOrder) {
|
||||||
const bool paused = pc.GetState() == PlayerState::PAUSE;
|
const bool paused = pc.GetState() == PlayerState::PAUSE;
|
||||||
|
|
||||||
/* the current song is going to be deleted: stop the player */
|
/* the current song is going to be deleted: see which
|
||||||
|
song is going to be played instead */
|
||||||
pc.Stop();
|
|
||||||
playing = false;
|
|
||||||
|
|
||||||
/* see which song is going to be played instead */
|
|
||||||
|
|
||||||
current = queue.GetNextOrder(current);
|
current = queue.GetNextOrder(current);
|
||||||
if (current == (int)songOrder)
|
if (current == (int)songOrder)
|
||||||
@@ -248,10 +279,12 @@ playlist::DeleteInternal(PlayerControl &pc,
|
|||||||
if (current >= 0 && !paused)
|
if (current >= 0 && !paused)
|
||||||
/* play the song after the deleted one */
|
/* play the song after the deleted one */
|
||||||
PlayOrder(pc, current);
|
PlayOrder(pc, current);
|
||||||
else
|
else {
|
||||||
/* no songs left to play, stop playback
|
/* stop the player */
|
||||||
completely */
|
|
||||||
Stop(pc);
|
pc.Stop();
|
||||||
|
playing = false;
|
||||||
|
}
|
||||||
|
|
||||||
*queued_p = nullptr;
|
*queued_p = nullptr;
|
||||||
} else if (current == (int)songOrder)
|
} else if (current == (int)songOrder)
|
||||||
|
@@ -69,6 +69,10 @@ spl_global_init(void)
|
|||||||
bool
|
bool
|
||||||
spl_valid_name(const char *name_utf8)
|
spl_valid_name(const char *name_utf8)
|
||||||
{
|
{
|
||||||
|
if (*name_utf8 == 0)
|
||||||
|
/* empty name not allowed */
|
||||||
|
return false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Not supporting '/' was done out of laziness, and we should
|
* Not supporting '/' was done out of laziness, and we should
|
||||||
* really strive to support it in the future.
|
* really strive to support it in the future.
|
||||||
|
@@ -40,7 +40,7 @@
|
|||||||
void
|
void
|
||||||
playlist_print_uris(Client &client, const playlist &playlist)
|
playlist_print_uris(Client &client, const playlist &playlist)
|
||||||
{
|
{
|
||||||
const queue &queue = playlist.queue;
|
const Queue &queue = playlist.queue;
|
||||||
|
|
||||||
queue_print_uris(client, queue, 0, queue.GetLength());
|
queue_print_uris(client, queue, 0, queue.GetLength());
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ bool
|
|||||||
playlist_print_info(Client &client, const playlist &playlist,
|
playlist_print_info(Client &client, const playlist &playlist,
|
||||||
unsigned start, unsigned end)
|
unsigned start, unsigned end)
|
||||||
{
|
{
|
||||||
const queue &queue = playlist.queue;
|
const Queue &queue = playlist.queue;
|
||||||
|
|
||||||
if (end > queue.GetLength())
|
if (end > queue.GetLength())
|
||||||
/* correct the "end" offset */
|
/* correct the "end" offset */
|
||||||
|
@@ -164,12 +164,12 @@ static SongEnumerator *
|
|||||||
playlist_list_open_uri_suffix(const char *uri, Mutex &mutex, Cond &cond,
|
playlist_list_open_uri_suffix(const char *uri, Mutex &mutex, Cond &cond,
|
||||||
const bool *tried)
|
const bool *tried)
|
||||||
{
|
{
|
||||||
const char *suffix;
|
|
||||||
SongEnumerator *playlist = nullptr;
|
SongEnumerator *playlist = nullptr;
|
||||||
|
|
||||||
assert(uri != nullptr);
|
assert(uri != nullptr);
|
||||||
|
|
||||||
suffix = uri_get_suffix(uri);
|
UriSuffixBuffer suffix_buffer;
|
||||||
|
const char *const suffix = uri_get_suffix(uri, suffix_buffer);
|
||||||
if (suffix == nullptr)
|
if (suffix == nullptr)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
@@ -273,8 +273,6 @@ playlist_list_open_stream_suffix(InputStream &is, const char *suffix)
|
|||||||
SongEnumerator *
|
SongEnumerator *
|
||||||
playlist_list_open_stream(InputStream &is, const char *uri)
|
playlist_list_open_stream(InputStream &is, const char *uri)
|
||||||
{
|
{
|
||||||
const char *suffix;
|
|
||||||
|
|
||||||
is.LockWaitReady();
|
is.LockWaitReady();
|
||||||
|
|
||||||
const char *const mime = is.GetMimeType();
|
const char *const mime = is.GetMimeType();
|
||||||
@@ -284,7 +282,10 @@ playlist_list_open_stream(InputStream &is, const char *uri)
|
|||||||
return playlist;
|
return playlist;
|
||||||
}
|
}
|
||||||
|
|
||||||
suffix = uri != nullptr ? uri_get_suffix(uri) : nullptr;
|
UriSuffixBuffer suffix_buffer;
|
||||||
|
const char *suffix = uri != nullptr
|
||||||
|
? uri_get_suffix(uri, suffix_buffer)
|
||||||
|
: nullptr;
|
||||||
if (suffix != nullptr) {
|
if (suffix != nullptr) {
|
||||||
auto playlist = playlist_list_open_stream_suffix(is, suffix);
|
auto playlist = playlist_list_open_stream_suffix(is, suffix);
|
||||||
if (playlist != nullptr)
|
if (playlist != nullptr)
|
||||||
|
@@ -65,7 +65,7 @@ playlist_print_uri(FILE *file, const char *uri)
|
|||||||
}
|
}
|
||||||
|
|
||||||
PlaylistResult
|
PlaylistResult
|
||||||
spl_save_queue(const char *name_utf8, const queue &queue)
|
spl_save_queue(const char *name_utf8, const Queue &queue)
|
||||||
{
|
{
|
||||||
if (map_spl_path().IsNull())
|
if (map_spl_path().IsNull())
|
||||||
return PlaylistResult::DISABLED;
|
return PlaylistResult::DISABLED;
|
||||||
|
@@ -25,7 +25,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
struct Song;
|
struct Song;
|
||||||
struct queue;
|
struct Queue;
|
||||||
struct playlist;
|
struct playlist;
|
||||||
struct PlayerControl;
|
struct PlayerControl;
|
||||||
class Error;
|
class Error;
|
||||||
@@ -40,7 +40,7 @@ playlist_print_uri(FILE *fp, const char *uri);
|
|||||||
* Saves a queue object into a stored playlist file.
|
* Saves a queue object into a stored playlist file.
|
||||||
*/
|
*/
|
||||||
PlaylistResult
|
PlaylistResult
|
||||||
spl_save_queue(const char *name_utf8, const queue &queue);
|
spl_save_queue(const char *name_utf8, const Queue &queue);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves a playlist object into a stored playlist file.
|
* Saves a playlist object into a stored playlist file.
|
||||||
|
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
queue::queue(unsigned _max_length)
|
Queue::Queue(unsigned _max_length)
|
||||||
:max_length(_max_length), length(0),
|
:max_length(_max_length), length(0),
|
||||||
version(1),
|
version(1),
|
||||||
items(new Item[max_length]),
|
items(new Item[max_length]),
|
||||||
@@ -36,7 +36,7 @@ queue::queue(unsigned _max_length)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
queue::~queue()
|
Queue::~Queue()
|
||||||
{
|
{
|
||||||
Clear();
|
Clear();
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ queue::~queue()
|
|||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
queue::GetNextOrder(unsigned _order) const
|
Queue::GetNextOrder(unsigned _order) const
|
||||||
{
|
{
|
||||||
assert(_order < length);
|
assert(_order < length);
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ queue::GetNextOrder(unsigned _order) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
queue::IncrementVersion()
|
Queue::IncrementVersion()
|
||||||
{
|
{
|
||||||
static unsigned long max = ((uint32_t) 1 << 31) - 1;
|
static unsigned long max = ((uint32_t) 1 << 31) - 1;
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ queue::IncrementVersion()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
queue::ModifyAtOrder(unsigned _order)
|
Queue::ModifyAtOrder(unsigned _order)
|
||||||
{
|
{
|
||||||
assert(_order < length);
|
assert(_order < length);
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ queue::ModifyAtOrder(unsigned _order)
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsigned
|
unsigned
|
||||||
queue::Append(Song *song, uint8_t priority)
|
Queue::Append(Song *song, uint8_t priority)
|
||||||
{
|
{
|
||||||
assert(!IsFull());
|
assert(!IsFull());
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ queue::Append(Song *song, uint8_t priority)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
queue::SwapPositions(unsigned position1, unsigned position2)
|
Queue::SwapPositions(unsigned position1, unsigned position2)
|
||||||
{
|
{
|
||||||
unsigned id1 = items[position1].id;
|
unsigned id1 = items[position1].id;
|
||||||
unsigned id2 = items[position2].id;
|
unsigned id2 = items[position2].id;
|
||||||
@@ -120,7 +120,7 @@ queue::SwapPositions(unsigned position1, unsigned position2)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
queue::MovePostion(unsigned from, unsigned to)
|
Queue::MovePostion(unsigned from, unsigned to)
|
||||||
{
|
{
|
||||||
const Item tmp = items[from];
|
const Item tmp = items[from];
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ queue::MovePostion(unsigned from, unsigned to)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
queue::MoveRange(unsigned start, unsigned end, unsigned to)
|
Queue::MoveRange(unsigned start, unsigned end, unsigned to)
|
||||||
{
|
{
|
||||||
Item tmp[end - start];
|
Item tmp[end - start];
|
||||||
// Copy the original block [start,end-1]
|
// Copy the original block [start,end-1]
|
||||||
@@ -198,7 +198,7 @@ queue::MoveRange(unsigned start, unsigned end, unsigned to)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
queue::MoveOrder(unsigned from_order, unsigned to_order)
|
Queue::MoveOrder(unsigned from_order, unsigned to_order)
|
||||||
{
|
{
|
||||||
assert(from_order < length);
|
assert(from_order < length);
|
||||||
assert(to_order <= length);
|
assert(to_order <= length);
|
||||||
@@ -217,7 +217,7 @@ queue::MoveOrder(unsigned from_order, unsigned to_order)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
queue::DeletePosition(unsigned position)
|
Queue::DeletePosition(unsigned position)
|
||||||
{
|
{
|
||||||
assert(position < length);
|
assert(position < length);
|
||||||
|
|
||||||
@@ -254,7 +254,7 @@ queue::DeletePosition(unsigned position)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
queue::Clear()
|
Queue::Clear()
|
||||||
{
|
{
|
||||||
for (unsigned i = 0; i < length; i++) {
|
for (unsigned i = 0; i < length; i++) {
|
||||||
Item *item = &items[i];
|
Item *item = &items[i];
|
||||||
@@ -270,7 +270,7 @@ queue::Clear()
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end)
|
queue_sort_order_by_priority(Queue *queue, unsigned start, unsigned end)
|
||||||
{
|
{
|
||||||
assert(queue != nullptr);
|
assert(queue != nullptr);
|
||||||
assert(queue->random);
|
assert(queue->random);
|
||||||
@@ -278,8 +278,8 @@ queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end)
|
|||||||
assert(end <= queue->length);
|
assert(end <= queue->length);
|
||||||
|
|
||||||
auto cmp = [queue](unsigned a_pos, unsigned b_pos){
|
auto cmp = [queue](unsigned a_pos, unsigned b_pos){
|
||||||
const queue::Item &a = queue->items[a_pos];
|
const Queue::Item &a = queue->items[a_pos];
|
||||||
const queue::Item &b = queue->items[b_pos];
|
const Queue::Item &b = queue->items[b_pos];
|
||||||
|
|
||||||
return a.priority > b.priority;
|
return a.priority > b.priority;
|
||||||
};
|
};
|
||||||
@@ -288,7 +288,7 @@ queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
queue::ShuffleOrderRange(unsigned start, unsigned end)
|
Queue::ShuffleOrderRange(unsigned start, unsigned end)
|
||||||
{
|
{
|
||||||
assert(random);
|
assert(random);
|
||||||
assert(start <= end);
|
assert(start <= end);
|
||||||
@@ -303,7 +303,7 @@ queue::ShuffleOrderRange(unsigned start, unsigned end)
|
|||||||
* priority group.
|
* priority group.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
queue::ShuffleOrderRangeWithPriority(unsigned start, unsigned end)
|
Queue::ShuffleOrderRangeWithPriority(unsigned start, unsigned end)
|
||||||
{
|
{
|
||||||
assert(random);
|
assert(random);
|
||||||
assert(start <= end);
|
assert(start <= end);
|
||||||
@@ -337,13 +337,13 @@ queue::ShuffleOrderRangeWithPriority(unsigned start, unsigned end)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
queue::ShuffleOrder()
|
Queue::ShuffleOrder()
|
||||||
{
|
{
|
||||||
ShuffleOrderRangeWithPriority(0, length);
|
ShuffleOrderRangeWithPriority(0, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
queue::ShuffleOrderFirst(unsigned start, unsigned end)
|
Queue::ShuffleOrderFirst(unsigned start, unsigned end)
|
||||||
{
|
{
|
||||||
rand.AutoCreate();
|
rand.AutoCreate();
|
||||||
|
|
||||||
@@ -352,7 +352,7 @@ queue::ShuffleOrderFirst(unsigned start, unsigned end)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
queue::ShuffleOrderLast(unsigned start, unsigned end)
|
Queue::ShuffleOrderLast(unsigned start, unsigned end)
|
||||||
{
|
{
|
||||||
rand.AutoCreate();
|
rand.AutoCreate();
|
||||||
|
|
||||||
@@ -361,7 +361,7 @@ queue::ShuffleOrderLast(unsigned start, unsigned end)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
queue::ShuffleRange(unsigned start, unsigned end)
|
Queue::ShuffleRange(unsigned start, unsigned end)
|
||||||
{
|
{
|
||||||
assert(start <= end);
|
assert(start <= end);
|
||||||
assert(end <= length);
|
assert(end <= length);
|
||||||
@@ -377,7 +377,7 @@ queue::ShuffleRange(unsigned start, unsigned end)
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsigned
|
unsigned
|
||||||
queue::FindPriorityOrder(unsigned start_order, uint8_t priority,
|
Queue::FindPriorityOrder(unsigned start_order, uint8_t priority,
|
||||||
unsigned exclude_order) const
|
unsigned exclude_order) const
|
||||||
{
|
{
|
||||||
assert(random);
|
assert(random);
|
||||||
@@ -394,7 +394,7 @@ queue::FindPriorityOrder(unsigned start_order, uint8_t priority,
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsigned
|
unsigned
|
||||||
queue::CountSamePriority(unsigned start_order, uint8_t priority) const
|
Queue::CountSamePriority(unsigned start_order, uint8_t priority) const
|
||||||
{
|
{
|
||||||
assert(random);
|
assert(random);
|
||||||
assert(start_order <= length);
|
assert(start_order <= length);
|
||||||
@@ -410,7 +410,7 @@ queue::CountSamePriority(unsigned start_order, uint8_t priority) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
queue::SetPriority(unsigned position, uint8_t priority, int after_order)
|
Queue::SetPriority(unsigned position, uint8_t priority, int after_order)
|
||||||
{
|
{
|
||||||
assert(position < length);
|
assert(position < length);
|
||||||
|
|
||||||
@@ -468,7 +468,7 @@ queue::SetPriority(unsigned position, uint8_t priority, int after_order)
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
queue::SetPriorityRange(unsigned start_position, unsigned end_position,
|
Queue::SetPriorityRange(unsigned start_position, unsigned end_position,
|
||||||
uint8_t priority, int after_order)
|
uint8_t priority, int after_order)
|
||||||
{
|
{
|
||||||
assert(start_position <= end_position);
|
assert(start_position <= end_position);
|
||||||
|
@@ -41,7 +41,7 @@ struct Song;
|
|||||||
* - the unique id (which stays the same, regardless of moves)
|
* - the unique id (which stays the same, regardless of moves)
|
||||||
* - the order number (which only differs from "position" in random mode)
|
* - the order number (which only differs from "position" in random mode)
|
||||||
*/
|
*/
|
||||||
struct queue {
|
struct Queue {
|
||||||
/**
|
/**
|
||||||
* reserve max_length * HASH_MULT elements in the id
|
* reserve max_length * HASH_MULT elements in the id
|
||||||
* number space
|
* number space
|
||||||
@@ -103,16 +103,16 @@ struct queue {
|
|||||||
/** random number generator for shuffle and random mode */
|
/** random number generator for shuffle and random mode */
|
||||||
LazyRandomEngine rand;
|
LazyRandomEngine rand;
|
||||||
|
|
||||||
queue(unsigned max_length);
|
explicit Queue(unsigned max_length);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deinitializes a queue object. It does not free the queue
|
* Deinitializes a queue object. It does not free the queue
|
||||||
* pointer itself.
|
* pointer itself.
|
||||||
*/
|
*/
|
||||||
~queue();
|
~Queue();
|
||||||
|
|
||||||
queue(const queue &other) = delete;
|
Queue(const Queue &) = delete;
|
||||||
queue &operator=(const queue &other) = delete;
|
Queue &operator=(const Queue &) = delete;
|
||||||
|
|
||||||
unsigned GetLength() const {
|
unsigned GetLength() const {
|
||||||
assert(length <= max_length);
|
assert(length <= max_length);
|
||||||
|
@@ -38,7 +38,7 @@ extern "C" {
|
|||||||
* @param end the index of the last song (excluding)
|
* @param end the index of the last song (excluding)
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
queue_print_song_info(Client &client, const queue &queue,
|
queue_print_song_info(Client &client, const Queue &queue,
|
||||||
unsigned position)
|
unsigned position)
|
||||||
{
|
{
|
||||||
song_print_info(client, queue.Get(position));
|
song_print_info(client, queue.Get(position));
|
||||||
@@ -51,7 +51,7 @@ queue_print_song_info(Client &client, const queue &queue,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
queue_print_info(Client &client, const queue &queue,
|
queue_print_info(Client &client, const Queue &queue,
|
||||||
unsigned start, unsigned end)
|
unsigned start, unsigned end)
|
||||||
{
|
{
|
||||||
assert(start <= end);
|
assert(start <= end);
|
||||||
@@ -62,7 +62,7 @@ queue_print_info(Client &client, const queue &queue,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
queue_print_uris(Client &client, const queue &queue,
|
queue_print_uris(Client &client, const Queue &queue,
|
||||||
unsigned start, unsigned end)
|
unsigned start, unsigned end)
|
||||||
{
|
{
|
||||||
assert(start <= end);
|
assert(start <= end);
|
||||||
@@ -75,7 +75,7 @@ queue_print_uris(Client &client, const queue &queue,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
queue_print_changes_info(Client &client, const queue &queue,
|
queue_print_changes_info(Client &client, const Queue &queue,
|
||||||
uint32_t version)
|
uint32_t version)
|
||||||
{
|
{
|
||||||
for (unsigned i = 0; i < queue.GetLength(); i++) {
|
for (unsigned i = 0; i < queue.GetLength(); i++) {
|
||||||
@@ -85,7 +85,7 @@ queue_print_changes_info(Client &client, const queue &queue,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
queue_print_changes_position(Client &client, const queue &queue,
|
queue_print_changes_position(Client &client, const Queue &queue,
|
||||||
uint32_t version)
|
uint32_t version)
|
||||||
{
|
{
|
||||||
for (unsigned i = 0; i < queue.GetLength(); i++)
|
for (unsigned i = 0; i < queue.GetLength(); i++)
|
||||||
@@ -95,7 +95,7 @@ queue_print_changes_position(Client &client, const queue &queue,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
queue_find(Client &client, const queue &queue,
|
queue_find(Client &client, const Queue &queue,
|
||||||
const SongFilter &filter)
|
const SongFilter &filter)
|
||||||
{
|
{
|
||||||
for (unsigned i = 0; i < queue.GetLength(); i++) {
|
for (unsigned i = 0; i < queue.GetLength(); i++) {
|
||||||
|
@@ -27,28 +27,28 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
struct queue;
|
struct Queue;
|
||||||
class SongFilter;
|
class SongFilter;
|
||||||
class Client;
|
class Client;
|
||||||
|
|
||||||
void
|
void
|
||||||
queue_print_info(Client &client, const queue &queue,
|
queue_print_info(Client &client, const Queue &queue,
|
||||||
unsigned start, unsigned end);
|
unsigned start, unsigned end);
|
||||||
|
|
||||||
void
|
void
|
||||||
queue_print_uris(Client &client, const queue &queue,
|
queue_print_uris(Client &client, const Queue &queue,
|
||||||
unsigned start, unsigned end);
|
unsigned start, unsigned end);
|
||||||
|
|
||||||
void
|
void
|
||||||
queue_print_changes_info(Client &client, const queue &queue,
|
queue_print_changes_info(Client &client, const Queue &queue,
|
||||||
uint32_t version);
|
uint32_t version);
|
||||||
|
|
||||||
void
|
void
|
||||||
queue_print_changes_position(Client &client, const queue &queue,
|
queue_print_changes_position(Client &client, const Queue &queue,
|
||||||
uint32_t version);
|
uint32_t version);
|
||||||
|
|
||||||
void
|
void
|
||||||
queue_find(Client &client, const queue &queue,
|
queue_find(Client &client, const Queue &queue,
|
||||||
const SongFilter &filter);
|
const SongFilter &filter);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -60,7 +60,7 @@ queue_save_song(FILE *fp, int idx, const Song &song)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
queue_save(FILE *fp, const queue &queue)
|
queue_save(FILE *fp, const Queue &queue)
|
||||||
{
|
{
|
||||||
for (unsigned i = 0; i < queue.GetLength(); i++) {
|
for (unsigned i = 0; i < queue.GetLength(); i++) {
|
||||||
uint8_t prio = queue.GetPriorityAtPosition(i);
|
uint8_t prio = queue.GetPriorityAtPosition(i);
|
||||||
@@ -72,7 +72,7 @@ queue_save(FILE *fp, const queue &queue)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
queue_load_song(TextFile &file, const char *line, queue &queue)
|
queue_load_song(TextFile &file, const char *line, Queue &queue)
|
||||||
{
|
{
|
||||||
if (queue.IsFull())
|
if (queue.IsFull())
|
||||||
return;
|
return;
|
||||||
|
@@ -27,16 +27,16 @@
|
|||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
struct queue;
|
struct Queue;
|
||||||
class TextFile;
|
class TextFile;
|
||||||
|
|
||||||
void
|
void
|
||||||
queue_save(FILE *fp, const queue &queue);
|
queue_save(FILE *fp, const Queue &queue);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads one song from the state file and appends it to the queue.
|
* Loads one song from the state file and appends it to the queue.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
queue_load_song(TextFile &file, const char *line, queue &queue);
|
queue_load_song(TextFile &file, const char *line, Queue &queue);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -101,7 +101,7 @@ bool
|
|||||||
SongFilter::Item::Match(const Tag &_tag) const
|
SongFilter::Item::Match(const Tag &_tag) const
|
||||||
{
|
{
|
||||||
bool visited_types[TAG_NUM_OF_ITEM_TYPES];
|
bool visited_types[TAG_NUM_OF_ITEM_TYPES];
|
||||||
std::fill_n(visited_types, TAG_NUM_OF_ITEM_TYPES, false);
|
std::fill_n(visited_types, size_t(TAG_NUM_OF_ITEM_TYPES), false);
|
||||||
|
|
||||||
for (unsigned i = 0; i < _tag.num_items; i++) {
|
for (unsigned i = 0; i < _tag.num_items; i++) {
|
||||||
visited_types[_tag.items[i]->type] = true;
|
visited_types[_tag.items[i]->type] = true;
|
||||||
@@ -203,6 +203,16 @@ SongFilter::Match(const Song &song) const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
SongFilter::HasOtherThanBase() const
|
||||||
|
{
|
||||||
|
for (const auto &i : items)
|
||||||
|
if (i.GetTag() != LOCATE_TAG_BASE_TYPE)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
SongFilter::GetBase() const
|
SongFilter::GetBase() const
|
||||||
{
|
{
|
||||||
|
@@ -109,6 +109,11 @@ public:
|
|||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gcc_pure
|
||||||
|
bool IsEmpty() const {
|
||||||
|
return items.empty();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is there at least one item with "fold case" enabled?
|
* Is there at least one item with "fold case" enabled?
|
||||||
*/
|
*/
|
||||||
@@ -121,6 +126,12 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this filter contain constraints other than "base"?
|
||||||
|
*/
|
||||||
|
gcc_pure
|
||||||
|
bool HasOtherThanBase() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the "base" specification (if there is one) or an
|
* Returns the "base" specification (if there is one) or an
|
||||||
* empty string.
|
* empty string.
|
||||||
|
@@ -95,8 +95,7 @@ Song::UpdateFile()
|
|||||||
|
|
||||||
TagBuilder tag_builder;
|
TagBuilder tag_builder;
|
||||||
if (!tag_file_scan(path_fs,
|
if (!tag_file_scan(path_fs,
|
||||||
&full_tag_handler, &tag_builder) ||
|
&full_tag_handler, &tag_builder))
|
||||||
!tag_builder.IsDefined())
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (tag_builder.IsEmpty())
|
if (tag_builder.IsEmpty())
|
||||||
|
@@ -30,59 +30,75 @@
|
|||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
||||||
struct stats stats;
|
static GTimer *uptime;
|
||||||
|
static DatabaseStats stats;
|
||||||
|
|
||||||
void stats_global_init(void)
|
void stats_global_init(void)
|
||||||
{
|
{
|
||||||
stats.timer = g_timer_new();
|
uptime = g_timer_new();
|
||||||
}
|
}
|
||||||
|
|
||||||
void stats_global_finish(void)
|
void stats_global_finish(void)
|
||||||
{
|
{
|
||||||
g_timer_destroy(stats.timer);
|
g_timer_destroy(uptime);
|
||||||
}
|
}
|
||||||
|
|
||||||
void stats_update(void)
|
void stats_update(void)
|
||||||
{
|
{
|
||||||
|
assert(GetDatabase() != nullptr);
|
||||||
|
|
||||||
Error error;
|
Error error;
|
||||||
|
|
||||||
DatabaseStats stats2;
|
DatabaseStats stats2;
|
||||||
|
|
||||||
const DatabaseSelection selection("", true);
|
const DatabaseSelection selection("", true);
|
||||||
if (GetDatabase()->GetStats(selection, stats2, error)) {
|
if (GetDatabase()->GetStats(selection, stats2, error)) {
|
||||||
stats.song_count = stats2.song_count;
|
stats = stats2;
|
||||||
stats.song_duration = stats2.total_duration;
|
|
||||||
stats.artist_count = stats2.artist_count;
|
|
||||||
stats.album_count = stats2.album_count;
|
|
||||||
} else {
|
} else {
|
||||||
LogError(error);
|
LogError(error);
|
||||||
|
|
||||||
stats.song_count = 0;
|
stats.Clear();
|
||||||
stats.song_duration = 0;
|
|
||||||
stats.artist_count = 0;
|
|
||||||
stats.album_count = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
db_stats_print(Client &client)
|
||||||
|
{
|
||||||
|
assert(GetDatabase() != nullptr);
|
||||||
|
|
||||||
|
if (!db_is_simple())
|
||||||
|
/* reload statistics if we're using the "proxy"
|
||||||
|
database plugin */
|
||||||
|
/* TODO: move this into the "proxy" database plugin as
|
||||||
|
an "idle" handler */
|
||||||
|
stats_update();
|
||||||
|
|
||||||
|
client_printf(client,
|
||||||
|
"artists: %u\n"
|
||||||
|
"albums: %u\n"
|
||||||
|
"songs: %u\n"
|
||||||
|
"db_playtime: %lu\n",
|
||||||
|
stats.artist_count,
|
||||||
|
stats.album_count,
|
||||||
|
stats.song_count,
|
||||||
|
stats.total_duration);
|
||||||
|
|
||||||
|
const time_t update_stamp = GetDatabase()->GetUpdateStamp();
|
||||||
|
if (update_stamp > 0)
|
||||||
|
client_printf(client,
|
||||||
|
"db_update: %lu\n",
|
||||||
|
(unsigned long)update_stamp);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
stats_print(Client &client)
|
stats_print(Client &client)
|
||||||
{
|
{
|
||||||
client_printf(client,
|
client_printf(client,
|
||||||
"artists: %u\n"
|
"uptime: %lu\n"
|
||||||
"albums: %u\n"
|
"playtime: %lu\n",
|
||||||
"songs: %i\n"
|
(unsigned long)g_timer_elapsed(uptime, NULL),
|
||||||
"uptime: %li\n"
|
(unsigned long)(client.player_control.GetTotalPlayTime() + 0.5));
|
||||||
"playtime: %li\n"
|
|
||||||
"db_playtime: %li\n",
|
|
||||||
stats.artist_count,
|
|
||||||
stats.album_count,
|
|
||||||
stats.song_count,
|
|
||||||
(long)g_timer_elapsed(stats.timer, NULL),
|
|
||||||
(long)(client.player_control.GetTotalPlayTime() + 0.5),
|
|
||||||
stats.song_duration);
|
|
||||||
|
|
||||||
if (db_is_simple())
|
if (GetDatabase() != nullptr)
|
||||||
client_printf(client,
|
db_stats_print(client);
|
||||||
"db_update: %li\n",
|
|
||||||
(long)db_get_mtime());
|
|
||||||
}
|
}
|
||||||
|
@@ -21,26 +21,6 @@
|
|||||||
#define MPD_STATS_HXX
|
#define MPD_STATS_HXX
|
||||||
|
|
||||||
class Client;
|
class Client;
|
||||||
typedef struct _GTimer GTimer;
|
|
||||||
|
|
||||||
struct stats {
|
|
||||||
GTimer *timer;
|
|
||||||
|
|
||||||
/** number of song files in the music directory */
|
|
||||||
unsigned song_count;
|
|
||||||
|
|
||||||
/** sum of all song durations in the music directory (in
|
|
||||||
seconds) */
|
|
||||||
unsigned long song_duration;
|
|
||||||
|
|
||||||
/** number of distinct artist names in the music directory */
|
|
||||||
unsigned artist_count;
|
|
||||||
|
|
||||||
/** number of distinct album names in the music directory */
|
|
||||||
unsigned album_count;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern struct stats stats;
|
|
||||||
|
|
||||||
void stats_global_init(void);
|
void stats_global_init(void);
|
||||||
|
|
||||||
|
@@ -28,6 +28,9 @@ struct tag_handler;
|
|||||||
/**
|
/**
|
||||||
* Scan the tags of a song file. Invokes matching decoder plugins,
|
* Scan the tags of a song file. Invokes matching decoder plugins,
|
||||||
* but does not invoke the special "APE" and "ID3" scanners.
|
* but does not invoke the special "APE" and "ID3" scanners.
|
||||||
|
*
|
||||||
|
* @return true if the file was recognized (even if no metadata was
|
||||||
|
* found)
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
tag_file_scan(Path path,
|
tag_file_scan(Path path,
|
||||||
|
@@ -190,18 +190,16 @@ iso9660_input_read(InputStream *is, void *ptr, size_t size,
|
|||||||
Error &error)
|
Error &error)
|
||||||
{
|
{
|
||||||
Iso9660InputStream *iis = (Iso9660InputStream *)is;
|
Iso9660InputStream *iis = (Iso9660InputStream *)is;
|
||||||
int toread, readed = 0;
|
int readed = 0;
|
||||||
int no_blocks, cur_block;
|
int no_blocks, cur_block;
|
||||||
size_t left_bytes = iis->statbuf->size - is->offset;
|
size_t left_bytes = iis->statbuf->size - is->offset;
|
||||||
|
|
||||||
size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE;
|
size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE;
|
||||||
|
|
||||||
if (left_bytes < size) {
|
if (left_bytes < size) {
|
||||||
toread = left_bytes;
|
|
||||||
no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE);
|
no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE);
|
||||||
} else {
|
} else {
|
||||||
toread = size;
|
no_blocks = size / ISO_BLOCKSIZE;
|
||||||
no_blocks = toread / ISO_BLOCKSIZE;
|
|
||||||
}
|
}
|
||||||
if (no_blocks > 0) {
|
if (no_blocks > 0) {
|
||||||
|
|
||||||
|
@@ -186,12 +186,13 @@ zzip_input_seek(InputStream *is, InputPlugin::offset_type offset,
|
|||||||
{
|
{
|
||||||
ZzipInputStream *zis = (ZzipInputStream *)is;
|
ZzipInputStream *zis = (ZzipInputStream *)is;
|
||||||
zzip_off_t ofs = zzip_seek(zis->file, offset, whence);
|
zzip_off_t ofs = zzip_seek(zis->file, offset, whence);
|
||||||
if (ofs != -1) {
|
if (ofs < 0) {
|
||||||
error.Set(zzip_domain, "zzip_seek() has failed");
|
error.Set(zzip_domain, "zzip_seek() has failed");
|
||||||
is->offset = ofs;
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
is->offset = ofs;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* exported structures */
|
/* exported structures */
|
||||||
|
@@ -90,7 +90,7 @@ static const struct command commands[] = {
|
|||||||
{ "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput },
|
{ "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput },
|
||||||
{ "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput },
|
{ "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput },
|
||||||
{ "find", PERMISSION_READ, 2, -1, handle_find },
|
{ "find", PERMISSION_READ, 2, -1, handle_find },
|
||||||
{ "findadd", PERMISSION_READ, 2, -1, handle_findadd},
|
{ "findadd", PERMISSION_ADD, 2, -1, handle_findadd},
|
||||||
{ "idle", PERMISSION_READ, 0, -1, handle_idle },
|
{ "idle", PERMISSION_READ, 0, -1, handle_idle },
|
||||||
{ "kill", PERMISSION_ADMIN, -1, -1, handle_kill },
|
{ "kill", PERMISSION_ADMIN, -1, -1, handle_kill },
|
||||||
{ "list", PERMISSION_READ, 1, -1, handle_list },
|
{ "list", PERMISSION_READ, 1, -1, handle_list },
|
||||||
|
@@ -27,6 +27,7 @@ void
|
|||||||
CommandListBuilder::Reset()
|
CommandListBuilder::Reset()
|
||||||
{
|
{
|
||||||
list.clear();
|
list.clear();
|
||||||
|
size = 0;
|
||||||
mode = Mode::DISABLED;
|
mode = Mode::DISABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -30,6 +30,7 @@
|
|||||||
#include "util/Error.hxx"
|
#include "util/Error.hxx"
|
||||||
#include "SongFilter.hxx"
|
#include "SongFilter.hxx"
|
||||||
#include "protocol/Result.hxx"
|
#include "protocol/Result.hxx"
|
||||||
|
#include "BulkEdit.hxx"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -92,6 +93,8 @@ handle_match_add(Client &client, int argc, char *argv[], bool fold_case)
|
|||||||
return CommandResult::ERROR;
|
return CommandResult::ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ScopeBulkEdit bulk_edit(client.partition);
|
||||||
|
|
||||||
const DatabaseSelection selection("", true, &filter);
|
const DatabaseSelection selection("", true, &filter);
|
||||||
Error error;
|
Error error;
|
||||||
return AddFromDatabase(client.partition, selection, error)
|
return AddFromDatabase(client.partition, selection, error)
|
||||||
|
@@ -26,6 +26,7 @@
|
|||||||
#include "PlaylistFile.hxx"
|
#include "PlaylistFile.hxx"
|
||||||
#include "PlaylistVector.hxx"
|
#include "PlaylistVector.hxx"
|
||||||
#include "PlaylistQueue.hxx"
|
#include "PlaylistQueue.hxx"
|
||||||
|
#include "BulkEdit.hxx"
|
||||||
#include "TimePrint.hxx"
|
#include "TimePrint.hxx"
|
||||||
#include "Client.hxx"
|
#include "Client.hxx"
|
||||||
#include "protocol/ArgParser.hxx"
|
#include "protocol/ArgParser.hxx"
|
||||||
@@ -67,6 +68,8 @@ handle_load(Client &client, int argc, char *argv[])
|
|||||||
} else if (!check_range(client, &start_index, &end_index, argv[2]))
|
} else if (!check_range(client, &start_index, &end_index, argv[2]))
|
||||||
return CommandResult::ERROR;
|
return CommandResult::ERROR;
|
||||||
|
|
||||||
|
const ScopeBulkEdit bulk_edit(client.partition);
|
||||||
|
|
||||||
const PlaylistResult result =
|
const PlaylistResult result =
|
||||||
playlist_open_into_queue(argv[1],
|
playlist_open_into_queue(argv[1],
|
||||||
start_index, end_index,
|
start_index, end_index,
|
||||||
|
@@ -28,6 +28,7 @@
|
|||||||
#include "ClientFile.hxx"
|
#include "ClientFile.hxx"
|
||||||
#include "Client.hxx"
|
#include "Client.hxx"
|
||||||
#include "Partition.hxx"
|
#include "Partition.hxx"
|
||||||
|
#include "BulkEdit.hxx"
|
||||||
#include "protocol/ArgParser.hxx"
|
#include "protocol/ArgParser.hxx"
|
||||||
#include "protocol/Result.hxx"
|
#include "protocol/Result.hxx"
|
||||||
#include "ls.hxx"
|
#include "ls.hxx"
|
||||||
@@ -43,7 +44,6 @@ CommandResult
|
|||||||
handle_add(Client &client, gcc_unused int argc, char *argv[])
|
handle_add(Client &client, gcc_unused int argc, char *argv[])
|
||||||
{
|
{
|
||||||
char *uri = argv[1];
|
char *uri = argv[1];
|
||||||
PlaylistResult result;
|
|
||||||
|
|
||||||
if (memcmp(uri, "file:///", 8) == 0) {
|
if (memcmp(uri, "file:///", 8) == 0) {
|
||||||
const char *path_utf8 = uri + 7;
|
const char *path_utf8 = uri + 7;
|
||||||
@@ -59,7 +59,7 @@ handle_add(Client &client, gcc_unused int argc, char *argv[])
|
|||||||
if (!client_allow_file(client, path_fs, error))
|
if (!client_allow_file(client, path_fs, error))
|
||||||
return print_error(client, error);
|
return print_error(client, error);
|
||||||
|
|
||||||
result = client.partition.AppendFile(path_utf8);
|
auto result = client.partition.AppendFile(path_utf8);
|
||||||
return print_playlist_result(client, result);
|
return print_playlist_result(client, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,10 +70,12 @@ handle_add(Client &client, gcc_unused int argc, char *argv[])
|
|||||||
return CommandResult::ERROR;
|
return CommandResult::ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = client.partition.AppendURI(uri);
|
auto result = client.partition.AppendURI(uri);
|
||||||
return print_playlist_result(client, result);
|
return print_playlist_result(client, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ScopeBulkEdit bulk_edit(client.partition);
|
||||||
|
|
||||||
const DatabaseSelection selection(uri, true);
|
const DatabaseSelection selection(uri, true);
|
||||||
Error error;
|
Error error;
|
||||||
return AddFromDatabase(client.partition, selection, error)
|
return AddFromDatabase(client.partition, selection, error)
|
||||||
|
@@ -46,6 +46,9 @@ class ProxyDatabase : public Database {
|
|||||||
struct mpd_connection *connection;
|
struct mpd_connection *connection;
|
||||||
Directory *root;
|
Directory *root;
|
||||||
|
|
||||||
|
/* this is mutable because GetStats() must be "const" */
|
||||||
|
mutable time_t update_stamp;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static Database *Create(const config_param ¶m,
|
static Database *Create(const config_param ¶m,
|
||||||
Error &error);
|
Error &error);
|
||||||
@@ -71,6 +74,10 @@ public:
|
|||||||
DatabaseStats &stats,
|
DatabaseStats &stats,
|
||||||
Error &error) const override;
|
Error &error) const override;
|
||||||
|
|
||||||
|
virtual time_t GetUpdateStamp() const override {
|
||||||
|
return update_stamp;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool Configure(const config_param ¶m, Error &error);
|
bool Configure(const config_param ¶m, Error &error);
|
||||||
|
|
||||||
@@ -237,6 +244,7 @@ ProxyDatabase::Open(Error &error)
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
root = Directory::NewRoot();
|
root = Directory::NewRoot();
|
||||||
|
update_stamp = 0;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -390,8 +398,13 @@ Convert(const struct mpd_song *song)
|
|||||||
Song *s = Song::NewDetached(mpd_song_get_uri(song));
|
Song *s = Song::NewDetached(mpd_song_get_uri(song));
|
||||||
|
|
||||||
s->mtime = mpd_song_get_last_modified(song);
|
s->mtime = mpd_song_get_last_modified(song);
|
||||||
|
|
||||||
|
#if LIBMPDCLIENT_CHECK_VERSION(2,3,0)
|
||||||
s->start_ms = mpd_song_get_start(song) * 1000;
|
s->start_ms = mpd_song_get_start(song) * 1000;
|
||||||
s->end_ms = mpd_song_get_end(song) * 1000;
|
s->end_ms = mpd_song_get_end(song) * 1000;
|
||||||
|
#else
|
||||||
|
s->start_ms = s->end_ms = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
TagBuilder tag;
|
TagBuilder tag;
|
||||||
tag.SetTime(mpd_song_get_duration(song));
|
tag.SetTime(mpd_song_get_duration(song));
|
||||||
@@ -553,6 +566,23 @@ SearchSongs(struct mpd_connection *connection,
|
|||||||
return result && CheckError(connection, error);
|
return result && CheckError(connection, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether we can use the "base" constraint. Requires
|
||||||
|
* libmpdclient 2.9 and MPD 0.18.
|
||||||
|
*/
|
||||||
|
gcc_pure
|
||||||
|
static bool
|
||||||
|
ServerSupportsSearchBase(const struct mpd_connection *connection)
|
||||||
|
{
|
||||||
|
#if LIBMPDCLIENT_CHECK_VERSION(2,9,0)
|
||||||
|
return mpd_connection_cmp_server_version(connection, 0, 18, 0) >= 0;
|
||||||
|
#else
|
||||||
|
(void)connection;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
ProxyDatabase::Visit(const DatabaseSelection &selection,
|
ProxyDatabase::Visit(const DatabaseSelection &selection,
|
||||||
VisitDirectory visit_directory,
|
VisitDirectory visit_directory,
|
||||||
@@ -564,7 +594,10 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
|
|||||||
if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
|
if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
if (!visit_directory && !visit_playlist && selection.recursive)
|
if (!visit_directory && !visit_playlist && selection.recursive &&
|
||||||
|
(ServerSupportsSearchBase(connection)
|
||||||
|
? !selection.IsEmpty()
|
||||||
|
: selection.HasOtherThanBase()))
|
||||||
/* this optimized code path can only be used under
|
/* this optimized code path can only be used under
|
||||||
certain conditions */
|
certain conditions */
|
||||||
return ::SearchSongs(connection, selection, visit_song, error);
|
return ::SearchSongs(connection, selection, visit_song, error);
|
||||||
@@ -631,6 +664,8 @@ ProxyDatabase::GetStats(const DatabaseSelection &selection,
|
|||||||
if (stats2 == nullptr)
|
if (stats2 == nullptr)
|
||||||
return CheckError(connection, error);
|
return CheckError(connection, error);
|
||||||
|
|
||||||
|
update_stamp = (time_t)mpd_stats_get_db_update_time(stats2);
|
||||||
|
|
||||||
stats.song_count = mpd_stats_get_number_of_songs(stats2);
|
stats.song_count = mpd_stats_get_number_of_songs(stats2);
|
||||||
stats.total_duration = mpd_stats_get_db_play_time(stats2);
|
stats.total_duration = mpd_stats_get_db_play_time(stats2);
|
||||||
stats.artist_count = mpd_stats_get_number_of_artists(stats2);
|
stats.artist_count = mpd_stats_get_number_of_artists(stats2);
|
||||||
|
@@ -26,8 +26,6 @@
|
|||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
struct Directory;
|
struct Directory;
|
||||||
|
|
||||||
class SimpleDatabase : public Database {
|
class SimpleDatabase : public Database {
|
||||||
@@ -55,11 +53,6 @@ public:
|
|||||||
|
|
||||||
bool Save(Error &error);
|
bool Save(Error &error);
|
||||||
|
|
||||||
gcc_pure
|
|
||||||
time_t GetLastModified() const {
|
|
||||||
return mtime;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Database *Create(const config_param ¶m,
|
static Database *Create(const config_param ¶m,
|
||||||
Error &error);
|
Error &error);
|
||||||
|
|
||||||
@@ -85,6 +78,10 @@ public:
|
|||||||
DatabaseStats &stats,
|
DatabaseStats &stats,
|
||||||
Error &error) const override;
|
Error &error) const override;
|
||||||
|
|
||||||
|
virtual time_t GetUpdateStamp() const override {
|
||||||
|
return mtime;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool Configure(const config_param ¶m, Error &error);
|
bool Configure(const config_param ¶m, Error &error);
|
||||||
|
|
||||||
|
@@ -31,12 +31,27 @@
|
|||||||
#include <af_vfs.h>
|
#include <af_vfs.h>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */
|
/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */
|
||||||
#define CHUNK_SIZE 1020
|
#define CHUNK_SIZE 1020
|
||||||
|
|
||||||
static constexpr Domain audiofile_domain("audiofile");
|
static constexpr Domain audiofile_domain("audiofile");
|
||||||
|
|
||||||
|
struct AudioFileInputStream {
|
||||||
|
Decoder *const decoder;
|
||||||
|
InputStream &is;
|
||||||
|
|
||||||
|
size_t Read(void *buffer, size_t size) {
|
||||||
|
/* libaudiofile does not like partial reads at all,
|
||||||
|
and will abort playback; therefore always force full
|
||||||
|
reads */
|
||||||
|
return decoder_read_full(decoder, is, buffer, size)
|
||||||
|
? size
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
static int audiofile_get_duration(const char *file)
|
static int audiofile_get_duration(const char *file)
|
||||||
{
|
{
|
||||||
int total_time;
|
int total_time;
|
||||||
@@ -54,29 +69,26 @@ static int audiofile_get_duration(const char *file)
|
|||||||
static ssize_t
|
static ssize_t
|
||||||
audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length)
|
audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length)
|
||||||
{
|
{
|
||||||
InputStream &is = *(InputStream *)vfile->closure;
|
AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
|
||||||
|
|
||||||
Error error;
|
return afis.Read(data, length);
|
||||||
size_t nbytes = is.LockRead(data, length, error);
|
|
||||||
if (nbytes == 0 && error.IsDefined()) {
|
|
||||||
LogError(error);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nbytes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static AFfileoffset
|
static AFfileoffset
|
||||||
audiofile_file_length(AFvirtualfile *vfile)
|
audiofile_file_length(AFvirtualfile *vfile)
|
||||||
{
|
{
|
||||||
InputStream &is = *(InputStream *)vfile->closure;
|
AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
|
||||||
|
InputStream &is = afis.is;
|
||||||
|
|
||||||
return is.GetSize();
|
return is.GetSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
static AFfileoffset
|
static AFfileoffset
|
||||||
audiofile_file_tell(AFvirtualfile *vfile)
|
audiofile_file_tell(AFvirtualfile *vfile)
|
||||||
{
|
{
|
||||||
InputStream &is = *(InputStream *)vfile->closure;
|
AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
|
||||||
|
InputStream &is = afis.is;
|
||||||
|
|
||||||
return is.GetOffset();
|
return is.GetOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,22 +103,25 @@ audiofile_file_destroy(AFvirtualfile *vfile)
|
|||||||
static AFfileoffset
|
static AFfileoffset
|
||||||
audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative)
|
audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative)
|
||||||
{
|
{
|
||||||
InputStream &is = *(InputStream *)vfile->closure;
|
AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
|
||||||
|
InputStream &is = afis.is;
|
||||||
|
|
||||||
int whence = (is_relative ? SEEK_CUR : SEEK_SET);
|
int whence = (is_relative ? SEEK_CUR : SEEK_SET);
|
||||||
|
|
||||||
Error error;
|
Error error;
|
||||||
if (is.LockSeek(offset, whence, error)) {
|
if (is.LockSeek(offset, whence, error)) {
|
||||||
return is.GetOffset();
|
return is.GetOffset();
|
||||||
} else {
|
} else {
|
||||||
|
LogError(error, "Seek failed");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static AFvirtualfile *
|
static AFvirtualfile *
|
||||||
setup_virtual_fops(InputStream &stream)
|
setup_virtual_fops(AudioFileInputStream &afis)
|
||||||
{
|
{
|
||||||
AFvirtualfile *vf = new AFvirtualfile();
|
AFvirtualfile *vf = new AFvirtualfile();
|
||||||
vf->closure = &stream;
|
vf->closure = &afis;
|
||||||
vf->write = nullptr;
|
vf->write = nullptr;
|
||||||
vf->read = audiofile_file_read;
|
vf->read = audiofile_file_read;
|
||||||
vf->length = audiofile_file_length;
|
vf->length = audiofile_file_length;
|
||||||
@@ -173,7 +188,8 @@ audiofile_stream_decode(Decoder &decoder, InputStream &is)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
vf = setup_virtual_fops(is);
|
AudioFileInputStream afis{&decoder, is};
|
||||||
|
vf = setup_virtual_fops(afis);
|
||||||
|
|
||||||
af_fp = afOpenVirtualFile(vf, "r", nullptr);
|
af_fp = afOpenVirtualFile(vf, "r", nullptr);
|
||||||
if (af_fp == AF_NULL_FILEHANDLE) {
|
if (af_fp == AF_NULL_FILEHANDLE) {
|
||||||
|
@@ -49,14 +49,6 @@ DsdId::Equals(const char *s) const
|
|||||||
return memcmp(value, s, sizeof(value)) == 0;
|
return memcmp(value, s, sizeof(value)) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
dsdlib_read(Decoder *decoder, InputStream &is,
|
|
||||||
void *data, size_t length)
|
|
||||||
{
|
|
||||||
size_t nbytes = decoder_read(decoder, is, data, length);
|
|
||||||
return nbytes == length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skip the #input_stream to the specified offset.
|
* Skip the #input_stream to the specified offset.
|
||||||
*/
|
*/
|
||||||
@@ -149,7 +141,7 @@ dsdlib_tag_id3(InputStream &is,
|
|||||||
id3_byte_t *dsdid3data;
|
id3_byte_t *dsdid3data;
|
||||||
dsdid3data = dsdid3;
|
dsdid3data = dsdid3;
|
||||||
|
|
||||||
if (!dsdlib_read(nullptr, is, dsdid3data, count))
|
if (!decoder_read_full(nullptr, is, dsdid3data, count))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
id3_tag = id3_tag_parse(dsdid3data, count);
|
id3_tag = id3_tag_parse(dsdid3data, count);
|
||||||
|
@@ -58,10 +58,6 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
|
||||||
dsdlib_read(Decoder *decoder, InputStream &is,
|
|
||||||
void *data, size_t length);
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
dsdlib_skip_to(Decoder *decoder, InputStream &is,
|
dsdlib_skip_to(Decoder *decoder, InputStream &is,
|
||||||
int64_t offset);
|
int64_t offset);
|
||||||
|
@@ -93,14 +93,14 @@ static bool
|
|||||||
dsdiff_read_id(Decoder *decoder, InputStream &is,
|
dsdiff_read_id(Decoder *decoder, InputStream &is,
|
||||||
DsdId *id)
|
DsdId *id)
|
||||||
{
|
{
|
||||||
return dsdlib_read(decoder, is, id, sizeof(*id));
|
return decoder_read_full(decoder, is, id, sizeof(*id));
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
dsdiff_read_chunk_header(Decoder *decoder, InputStream &is,
|
dsdiff_read_chunk_header(Decoder *decoder, InputStream &is,
|
||||||
DsdiffChunkHeader *header)
|
DsdiffChunkHeader *header)
|
||||||
{
|
{
|
||||||
return dsdlib_read(decoder, is, header, sizeof(*header));
|
return decoder_read_full(decoder, is, header, sizeof(*header));
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@@ -112,8 +112,7 @@ dsdiff_read_payload(Decoder *decoder, InputStream &is,
|
|||||||
if (size != (uint64_t)length)
|
if (size != (uint64_t)length)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
size_t nbytes = decoder_read(decoder, is, data, length);
|
return decoder_read_full(decoder, is, data, length);
|
||||||
return nbytes == length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -145,8 +144,8 @@ dsdiff_read_prop_snd(Decoder *decoder, InputStream &is,
|
|||||||
} else if (header.id.Equals("CHNL")) {
|
} else if (header.id.Equals("CHNL")) {
|
||||||
uint16_t channels;
|
uint16_t channels;
|
||||||
if (header.GetSize() < sizeof(channels) ||
|
if (header.GetSize() < sizeof(channels) ||
|
||||||
!dsdlib_read(decoder, is,
|
!decoder_read_full(decoder, is,
|
||||||
&channels, sizeof(channels)) ||
|
&channels, sizeof(channels)) ||
|
||||||
!dsdlib_skip_to(decoder, is, chunk_end_offset))
|
!dsdlib_skip_to(decoder, is, chunk_end_offset))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -154,8 +153,8 @@ dsdiff_read_prop_snd(Decoder *decoder, InputStream &is,
|
|||||||
} else if (header.id.Equals("CMPR")) {
|
} else if (header.id.Equals("CMPR")) {
|
||||||
DsdId type;
|
DsdId type;
|
||||||
if (header.GetSize() < sizeof(type) ||
|
if (header.GetSize() < sizeof(type) ||
|
||||||
!dsdlib_read(decoder, is,
|
!decoder_read_full(decoder, is,
|
||||||
&type, sizeof(type)) ||
|
&type, sizeof(type)) ||
|
||||||
!dsdlib_skip_to(decoder, is, chunk_end_offset))
|
!dsdlib_skip_to(decoder, is, chunk_end_offset))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -208,7 +207,7 @@ dsdiff_handle_native_tag(InputStream &is,
|
|||||||
|
|
||||||
struct dsdiff_native_tag metatag;
|
struct dsdiff_native_tag metatag;
|
||||||
|
|
||||||
if (!dsdlib_read(nullptr, is, &metatag, sizeof(metatag)))
|
if (!decoder_read_full(nullptr, is, &metatag, sizeof(metatag)))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
uint32_t length = FromBE32(metatag.size);
|
uint32_t length = FromBE32(metatag.size);
|
||||||
@@ -221,7 +220,7 @@ dsdiff_handle_native_tag(InputStream &is,
|
|||||||
char *label;
|
char *label;
|
||||||
label = string;
|
label = string;
|
||||||
|
|
||||||
if (!dsdlib_read(nullptr, is, label, (size_t)length))
|
if (!decoder_read_full(nullptr, is, label, (size_t)length))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
string[length] = '\0';
|
string[length] = '\0';
|
||||||
@@ -251,15 +250,17 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is,
|
|||||||
if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
|
if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
metadata->diar_offset = 0;
|
||||||
|
metadata->diti_offset = 0;
|
||||||
|
|
||||||
#ifdef HAVE_ID3TAG
|
#ifdef HAVE_ID3TAG
|
||||||
metadata->id3_size = 0;
|
metadata->id3_offset = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Now process all the remaining chunk headers in the stream
|
/* Now process all the remaining chunk headers in the stream
|
||||||
and record their position and size */
|
and record their position and size */
|
||||||
|
|
||||||
const auto size = is.GetSize();
|
do {
|
||||||
while (is.GetOffset() < size) {
|
|
||||||
uint64_t chunk_size = chunk_header->GetSize();
|
uint64_t chunk_size = chunk_header->GetSize();
|
||||||
|
|
||||||
/* DIIN chunk, is directly followed by other chunks */
|
/* DIIN chunk, is directly followed by other chunks */
|
||||||
@@ -285,17 +286,11 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is,
|
|||||||
metadata->id3_size = chunk_size;
|
metadata->id3_size = chunk_size;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (chunk_size != 0) {
|
|
||||||
if (!dsdlib_skip(decoder, is, chunk_size))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is.GetOffset() < size) {
|
if (!dsdlib_skip(decoder, is, chunk_size))
|
||||||
if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
|
break;
|
||||||
return false;
|
} while (dsdiff_read_chunk_header(decoder, is, chunk_header));
|
||||||
}
|
|
||||||
chunk_size = 0;
|
|
||||||
}
|
|
||||||
/* done processing chunk headers, process tags if any */
|
/* done processing chunk headers, process tags if any */
|
||||||
|
|
||||||
#ifdef HAVE_ID3TAG
|
#ifdef HAVE_ID3TAG
|
||||||
@@ -329,7 +324,7 @@ dsdiff_read_metadata(Decoder *decoder, InputStream &is,
|
|||||||
DsdiffChunkHeader *chunk_header)
|
DsdiffChunkHeader *chunk_header)
|
||||||
{
|
{
|
||||||
DsdiffHeader header;
|
DsdiffHeader header;
|
||||||
if (!dsdlib_read(decoder, is, &header, sizeof(header)) ||
|
if (!decoder_read_full(decoder, is, &header, sizeof(header)) ||
|
||||||
!header.id.Equals("FRM8") ||
|
!header.id.Equals("FRM8") ||
|
||||||
!header.format.Equals("DSD "))
|
!header.format.Equals("DSD "))
|
||||||
return false;
|
return false;
|
||||||
@@ -382,20 +377,20 @@ dsdiff_decode_chunk(Decoder &decoder, InputStream &is,
|
|||||||
const unsigned buffer_samples = buffer_frames * frame_size;
|
const unsigned buffer_samples = buffer_frames * frame_size;
|
||||||
const size_t buffer_size = buffer_samples * sample_size;
|
const size_t buffer_size = buffer_samples * sample_size;
|
||||||
|
|
||||||
while (chunk_size > 0) {
|
while (chunk_size >= frame_size) {
|
||||||
/* see how much aligned data from the remaining chunk
|
/* see how much aligned data from the remaining chunk
|
||||||
fits into the local buffer */
|
fits into the local buffer */
|
||||||
unsigned now_frames = buffer_frames;
|
|
||||||
size_t now_size = buffer_size;
|
size_t now_size = buffer_size;
|
||||||
if (chunk_size < (uint64_t)now_size) {
|
if (chunk_size < (uint64_t)now_size) {
|
||||||
now_frames = (unsigned)chunk_size / frame_size;
|
unsigned now_frames =
|
||||||
|
(unsigned)chunk_size / frame_size;
|
||||||
now_size = now_frames * frame_size;
|
now_size = now_frames * frame_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t nbytes = decoder_read(decoder, is, buffer, now_size);
|
if (!decoder_read_full(&decoder, is, buffer, now_size))
|
||||||
if (nbytes != now_size)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
const size_t nbytes = now_size;
|
||||||
chunk_size -= nbytes;
|
chunk_size -= nbytes;
|
||||||
|
|
||||||
if (lsbitfirst)
|
if (lsbitfirst)
|
||||||
|
@@ -103,7 +103,7 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
|
|||||||
DsfMetaData *metadata)
|
DsfMetaData *metadata)
|
||||||
{
|
{
|
||||||
DsfHeader dsf_header;
|
DsfHeader dsf_header;
|
||||||
if (!dsdlib_read(decoder, is, &dsf_header, sizeof(dsf_header)) ||
|
if (!decoder_read_full(decoder, is, &dsf_header, sizeof(dsf_header)) ||
|
||||||
!dsf_header.id.Equals("DSD "))
|
!dsf_header.id.Equals("DSD "))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -117,7 +117,8 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
|
|||||||
|
|
||||||
/* read the 'fmt ' chunk of the DSF file */
|
/* read the 'fmt ' chunk of the DSF file */
|
||||||
DsfFmtChunk dsf_fmt_chunk;
|
DsfFmtChunk dsf_fmt_chunk;
|
||||||
if (!dsdlib_read(decoder, is, &dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) ||
|
if (!decoder_read_full(decoder, is,
|
||||||
|
&dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) ||
|
||||||
!dsf_fmt_chunk.id.Equals("fmt "))
|
!dsf_fmt_chunk.id.Equals("fmt "))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -143,7 +144,7 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
|
|||||||
|
|
||||||
/* read the 'data' chunk of the DSF file */
|
/* read the 'data' chunk of the DSF file */
|
||||||
DsfDataChunk data_chunk;
|
DsfDataChunk data_chunk;
|
||||||
if (!dsdlib_read(decoder, is, &data_chunk, sizeof(data_chunk)) ||
|
if (!decoder_read_full(decoder, is, &data_chunk, sizeof(data_chunk)) ||
|
||||||
!data_chunk.id.Equals("data"))
|
!data_chunk.id.Equals("data"))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -237,20 +238,20 @@ dsf_decode_chunk(Decoder &decoder, InputStream &is,
|
|||||||
const unsigned buffer_samples = buffer_frames * frame_size;
|
const unsigned buffer_samples = buffer_frames * frame_size;
|
||||||
const size_t buffer_size = buffer_samples * sample_size;
|
const size_t buffer_size = buffer_samples * sample_size;
|
||||||
|
|
||||||
while (chunk_size > 0) {
|
while (chunk_size >= frame_size) {
|
||||||
/* see how much aligned data from the remaining chunk
|
/* see how much aligned data from the remaining chunk
|
||||||
fits into the local buffer */
|
fits into the local buffer */
|
||||||
unsigned now_frames = buffer_frames;
|
|
||||||
size_t now_size = buffer_size;
|
size_t now_size = buffer_size;
|
||||||
if (chunk_size < (uint64_t)now_size) {
|
if (chunk_size < (uint64_t)now_size) {
|
||||||
now_frames = (unsigned)chunk_size / frame_size;
|
unsigned now_frames =
|
||||||
|
(unsigned)chunk_size / frame_size;
|
||||||
now_size = now_frames * frame_size;
|
now_size = now_frames * frame_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t nbytes = decoder_read(&decoder, is, buffer, now_size);
|
if (!decoder_read_full(&decoder, is, buffer, now_size))
|
||||||
if (nbytes != now_size)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
const size_t nbytes = now_size;
|
||||||
chunk_size -= nbytes;
|
chunk_size -= nbytes;
|
||||||
|
|
||||||
if (bitreverse)
|
if (bitreverse)
|
||||||
|
@@ -34,8 +34,6 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#define AAC_MAX_CHANNELS 6
|
|
||||||
|
|
||||||
static const unsigned adts_sample_rates[] =
|
static const unsigned adts_sample_rates[] =
|
||||||
{ 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
|
{ 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
|
||||||
16000, 12000, 11025, 8000, 7350, 0, 0, 0
|
16000, 12000, 11025, 8000, 7350, 0, 0, 0
|
||||||
@@ -66,16 +64,13 @@ adts_check_frame(const unsigned char *data)
|
|||||||
static size_t
|
static size_t
|
||||||
adts_find_frame(DecoderBuffer *buffer)
|
adts_find_frame(DecoderBuffer *buffer)
|
||||||
{
|
{
|
||||||
size_t length, frame_length;
|
|
||||||
bool ret;
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
size_t length;
|
||||||
const uint8_t *data = (const uint8_t *)
|
const uint8_t *data = (const uint8_t *)
|
||||||
decoder_buffer_read(buffer, &length);
|
decoder_buffer_read(buffer, &length);
|
||||||
if (data == nullptr || length < 8) {
|
if (data == nullptr || length < 8) {
|
||||||
/* not enough data yet */
|
/* not enough data yet */
|
||||||
ret = decoder_buffer_fill(buffer);
|
if (!decoder_buffer_fill(buffer))
|
||||||
if (!ret)
|
|
||||||
/* failed */
|
/* failed */
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
@@ -86,7 +81,7 @@ adts_find_frame(DecoderBuffer *buffer)
|
|||||||
const uint8_t *p = (const uint8_t *)memchr(data, 0xff, length);
|
const uint8_t *p = (const uint8_t *)memchr(data, 0xff, length);
|
||||||
if (p == nullptr) {
|
if (p == nullptr) {
|
||||||
/* no marker - discard the buffer */
|
/* no marker - discard the buffer */
|
||||||
decoder_buffer_consume(buffer, length);
|
decoder_buffer_clear(buffer);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +92,7 @@ adts_find_frame(DecoderBuffer *buffer)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* is it a frame? */
|
/* is it a frame? */
|
||||||
frame_length = adts_check_frame(data);
|
const size_t frame_length = adts_check_frame(data);
|
||||||
if (frame_length == 0) {
|
if (frame_length == 0) {
|
||||||
/* it's just some random 0xff byte; discard it
|
/* it's just some random 0xff byte; discard it
|
||||||
and continue searching */
|
and continue searching */
|
||||||
@@ -109,15 +104,11 @@ adts_find_frame(DecoderBuffer *buffer)
|
|||||||
/* available buffer size is smaller than the
|
/* available buffer size is smaller than the
|
||||||
frame will be - attempt to read more
|
frame will be - attempt to read more
|
||||||
data */
|
data */
|
||||||
ret = decoder_buffer_fill(buffer);
|
if (!decoder_buffer_fill(buffer)) {
|
||||||
if (!ret) {
|
|
||||||
/* not enough data; discard this frame
|
/* not enough data; discard this frame
|
||||||
to prevent a possible buffer
|
to prevent a possible buffer
|
||||||
overflow */
|
overflow */
|
||||||
data = (const uint8_t *)
|
decoder_buffer_clear(buffer);
|
||||||
decoder_buffer_read(buffer, &length);
|
|
||||||
if (data != nullptr)
|
|
||||||
decoder_buffer_consume(buffer, length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -131,13 +122,18 @@ adts_find_frame(DecoderBuffer *buffer)
|
|||||||
static float
|
static float
|
||||||
adts_song_duration(DecoderBuffer *buffer)
|
adts_song_duration(DecoderBuffer *buffer)
|
||||||
{
|
{
|
||||||
unsigned int frames, frame_length;
|
const InputStream &is = decoder_buffer_get_stream(buffer);
|
||||||
|
const bool estimate = !is.CheapSeeking();
|
||||||
|
const auto file_size = is.GetSize();
|
||||||
|
if (estimate && file_size <= 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
unsigned sample_rate = 0;
|
unsigned sample_rate = 0;
|
||||||
float frames_per_second;
|
|
||||||
|
|
||||||
/* Read all frames to ensure correct time and bitrate */
|
/* Read all frames to ensure correct time and bitrate */
|
||||||
|
unsigned frames;
|
||||||
for (frames = 0;; frames++) {
|
for (frames = 0;; frames++) {
|
||||||
frame_length = adts_find_frame(buffer);
|
const unsigned frame_length = adts_find_frame(buffer);
|
||||||
if (frame_length == 0)
|
if (frame_length == 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -150,36 +146,52 @@ adts_song_duration(DecoderBuffer *buffer)
|
|||||||
assert(frame_length <= buffer_length);
|
assert(frame_length <= buffer_length);
|
||||||
|
|
||||||
sample_rate = adts_sample_rates[(data[2] & 0x3c) >> 2];
|
sample_rate = adts_sample_rates[(data[2] & 0x3c) >> 2];
|
||||||
|
if (sample_rate == 0)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
decoder_buffer_consume(buffer, frame_length);
|
decoder_buffer_consume(buffer, frame_length);
|
||||||
|
|
||||||
|
if (estimate && frames == 128) {
|
||||||
|
/* if this is a remote file, don't slurp the
|
||||||
|
whole file just for checking the song
|
||||||
|
duration; instead, stop after some time and
|
||||||
|
extrapolate the song duration from what we
|
||||||
|
have until now */
|
||||||
|
|
||||||
|
const auto offset = is.GetOffset()
|
||||||
|
- decoder_buffer_available(buffer);
|
||||||
|
if (offset <= 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
frames = (frames * file_size) / offset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
frames_per_second = (float)sample_rate / 1024.0;
|
if (sample_rate == 0)
|
||||||
if (frames_per_second <= 0)
|
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
float frames_per_second = (float)sample_rate / 1024.0;
|
||||||
|
assert(frames_per_second > 0);
|
||||||
|
|
||||||
return (float)frames / frames_per_second;
|
return (float)frames / frames_per_second;
|
||||||
}
|
}
|
||||||
|
|
||||||
static float
|
static float
|
||||||
faad_song_duration(DecoderBuffer *buffer, InputStream &is)
|
faad_song_duration(DecoderBuffer *buffer, InputStream &is)
|
||||||
{
|
{
|
||||||
size_t fileread;
|
|
||||||
size_t tagsize;
|
|
||||||
size_t length;
|
|
||||||
bool success;
|
|
||||||
|
|
||||||
const auto size = is.GetSize();
|
const auto size = is.GetSize();
|
||||||
fileread = size >= 0 ? size : 0;
|
const size_t fileread = size >= 0 ? size : 0;
|
||||||
|
|
||||||
decoder_buffer_fill(buffer);
|
decoder_buffer_fill(buffer);
|
||||||
|
size_t length;
|
||||||
const uint8_t *data = (const uint8_t *)
|
const uint8_t *data = (const uint8_t *)
|
||||||
decoder_buffer_read(buffer, &length);
|
decoder_buffer_read(buffer, &length);
|
||||||
if (data == nullptr)
|
if (data == nullptr)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
tagsize = 0;
|
size_t tagsize = 0;
|
||||||
if (length >= 10 && !memcmp(data, "ID3", 3)) {
|
if (length >= 10 && !memcmp(data, "ID3", 3)) {
|
||||||
/* skip the ID3 tag */
|
/* skip the ID3 tag */
|
||||||
|
|
||||||
@@ -188,7 +200,7 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is)
|
|||||||
|
|
||||||
tagsize += 10;
|
tagsize += 10;
|
||||||
|
|
||||||
success = decoder_buffer_skip(buffer, tagsize) &&
|
const bool success = decoder_buffer_skip(buffer, tagsize) &&
|
||||||
decoder_buffer_fill(buffer);
|
decoder_buffer_fill(buffer);
|
||||||
if (!success)
|
if (!success)
|
||||||
return -1;
|
return -1;
|
||||||
@@ -198,22 +210,20 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is.IsSeekable() && length >= 2 &&
|
if (length >= 8 && adts_check_frame(data) > 0) {
|
||||||
data[0] == 0xFF && ((data[1] & 0xF6) == 0xF0)) {
|
|
||||||
/* obtain the duration from the ADTS header */
|
/* obtain the duration from the ADTS header */
|
||||||
|
|
||||||
|
if (!is.IsSeekable())
|
||||||
|
return -1;
|
||||||
|
|
||||||
float song_length = adts_song_duration(buffer);
|
float song_length = adts_song_duration(buffer);
|
||||||
|
|
||||||
is.LockSeek(tagsize, SEEK_SET, IgnoreError());
|
is.LockSeek(tagsize, SEEK_SET, IgnoreError());
|
||||||
|
decoder_buffer_clear(buffer);
|
||||||
data = (const uint8_t *)decoder_buffer_read(buffer, &length);
|
|
||||||
if (data != nullptr)
|
|
||||||
decoder_buffer_consume(buffer, length);
|
|
||||||
decoder_buffer_fill(buffer);
|
|
||||||
|
|
||||||
return song_length;
|
return song_length;
|
||||||
} else if (length >= 5 && memcmp(data, "ADIF", 4) == 0) {
|
} else if (length >= 5 && memcmp(data, "ADIF", 4) == 0) {
|
||||||
/* obtain the duration from the ADIF header */
|
/* obtain the duration from the ADIF header */
|
||||||
unsigned bit_rate;
|
|
||||||
size_t skip_size = (data[4] & 0x80) ? 9 : 0;
|
size_t skip_size = (data[4] & 0x80) ? 9 : 0;
|
||||||
|
|
||||||
if (8 + skip_size > length)
|
if (8 + skip_size > length)
|
||||||
@@ -221,7 +231,7 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is)
|
|||||||
header */
|
header */
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
bit_rate = ((data[4 + skip_size] & 0x0F) << 19) |
|
unsigned bit_rate = ((data[4 + skip_size] & 0x0F) << 19) |
|
||||||
(data[5 + skip_size] << 11) |
|
(data[5 + skip_size] << 11) |
|
||||||
(data[6 + skip_size] << 3) |
|
(data[6 + skip_size] << 3) |
|
||||||
(data[7 + skip_size] & 0xE0);
|
(data[7 + skip_size] & 0xE0);
|
||||||
@@ -234,6 +244,21 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static NeAACDecHandle
|
||||||
|
faad_decoder_new()
|
||||||
|
{
|
||||||
|
const NeAACDecHandle decoder = NeAACDecOpen();
|
||||||
|
|
||||||
|
NeAACDecConfigurationPtr config =
|
||||||
|
NeAACDecGetCurrentConfiguration(decoder);
|
||||||
|
config->outputFormat = FAAD_FMT_16BIT;
|
||||||
|
config->downMatrix = 1;
|
||||||
|
config->dontUpSampleImplicitSBR = 0;
|
||||||
|
NeAACDecSetConfiguration(decoder, config);
|
||||||
|
|
||||||
|
return decoder;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper for NeAACDecInit() which works around some API
|
* Wrapper for NeAACDecInit() which works around some API
|
||||||
* inconsistencies in libfaad.
|
* inconsistencies in libfaad.
|
||||||
@@ -242,17 +267,6 @@ static bool
|
|||||||
faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
|
faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
|
||||||
AudioFormat &audio_format, Error &error)
|
AudioFormat &audio_format, Error &error)
|
||||||
{
|
{
|
||||||
int32_t nbytes;
|
|
||||||
uint32_t sample_rate;
|
|
||||||
uint8_t channels;
|
|
||||||
#ifdef HAVE_FAAD_LONG
|
|
||||||
/* neaacdec.h declares all arguments as "unsigned long", but
|
|
||||||
internally expects uint32_t pointers. To avoid gcc
|
|
||||||
warnings, use this workaround. */
|
|
||||||
unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
|
|
||||||
#else
|
|
||||||
uint32_t *sample_rate_p = &sample_rate;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
size_t length;
|
size_t length;
|
||||||
const unsigned char *data = (const unsigned char *)
|
const unsigned char *data = (const unsigned char *)
|
||||||
@@ -262,11 +276,13 @@ faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
nbytes = NeAACDecInit(decoder,
|
uint8_t channels;
|
||||||
/* deconst hack, libfaad requires this */
|
unsigned long sample_rate;
|
||||||
const_cast<unsigned char *>(data),
|
long nbytes = NeAACDecInit(decoder,
|
||||||
length,
|
/* deconst hack, libfaad requires this */
|
||||||
sample_rate_p, &channels);
|
const_cast<unsigned char *>(data),
|
||||||
|
length,
|
||||||
|
&sample_rate, &channels);
|
||||||
if (nbytes < 0) {
|
if (nbytes < 0) {
|
||||||
error.Set(faad_decoder_domain, "Not an AAC stream");
|
error.Set(faad_decoder_domain, "Not an AAC stream");
|
||||||
return false;
|
return false;
|
||||||
@@ -306,29 +322,21 @@ faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer *buffer,
|
|||||||
static float
|
static float
|
||||||
faad_get_file_time_float(InputStream &is)
|
faad_get_file_time_float(InputStream &is)
|
||||||
{
|
{
|
||||||
DecoderBuffer *buffer;
|
DecoderBuffer *const buffer =
|
||||||
float length;
|
decoder_buffer_new(nullptr, is,
|
||||||
|
FAAD_MIN_STREAMSIZE * MAX_CHANNELS);
|
||||||
|
|
||||||
buffer = decoder_buffer_new(nullptr, is,
|
float length = faad_song_duration(buffer, is);
|
||||||
FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
|
|
||||||
length = faad_song_duration(buffer, is);
|
|
||||||
|
|
||||||
if (length < 0) {
|
if (length < 0) {
|
||||||
bool ret;
|
|
||||||
AudioFormat audio_format;
|
AudioFormat audio_format;
|
||||||
|
|
||||||
NeAACDecHandle decoder = NeAACDecOpen();
|
NeAACDecHandle decoder = faad_decoder_new();
|
||||||
|
|
||||||
NeAACDecConfigurationPtr config =
|
|
||||||
NeAACDecGetCurrentConfiguration(decoder);
|
|
||||||
config->outputFormat = FAAD_FMT_16BIT;
|
|
||||||
NeAACDecSetConfiguration(decoder, config);
|
|
||||||
|
|
||||||
decoder_buffer_fill(buffer);
|
decoder_buffer_fill(buffer);
|
||||||
|
|
||||||
ret = faad_decoder_init(decoder, buffer, audio_format,
|
if (faad_decoder_init(decoder, buffer, audio_format,
|
||||||
IgnoreError());
|
IgnoreError()))
|
||||||
if (ret)
|
|
||||||
length = 0;
|
length = 0;
|
||||||
|
|
||||||
NeAACDecClose(decoder);
|
NeAACDecClose(decoder);
|
||||||
@@ -347,38 +355,25 @@ faad_get_file_time_float(InputStream &is)
|
|||||||
static int
|
static int
|
||||||
faad_get_file_time(InputStream &is)
|
faad_get_file_time(InputStream &is)
|
||||||
{
|
{
|
||||||
int file_time = -1;
|
float length = faad_get_file_time_float(is);
|
||||||
float length;
|
if (length < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
if ((length = faad_get_file_time_float(is)) >= 0)
|
return int(length + 0.5);
|
||||||
file_time = length + 0.5;
|
|
||||||
|
|
||||||
return file_time;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
|
faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
|
||||||
{
|
{
|
||||||
float total_time = 0;
|
DecoderBuffer *const buffer =
|
||||||
AudioFormat audio_format;
|
decoder_buffer_new(&mpd_decoder, is,
|
||||||
bool ret;
|
FAAD_MIN_STREAMSIZE * MAX_CHANNELS);
|
||||||
uint16_t bit_rate = 0;
|
|
||||||
DecoderBuffer *buffer;
|
|
||||||
|
|
||||||
buffer = decoder_buffer_new(&mpd_decoder, is,
|
const float total_time = faad_song_duration(buffer, is);
|
||||||
FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
|
|
||||||
total_time = faad_song_duration(buffer, is);
|
|
||||||
|
|
||||||
/* create the libfaad decoder */
|
/* create the libfaad decoder */
|
||||||
|
|
||||||
NeAACDecHandle decoder = NeAACDecOpen();
|
const NeAACDecHandle decoder = faad_decoder_new();
|
||||||
|
|
||||||
NeAACDecConfigurationPtr config =
|
|
||||||
NeAACDecGetCurrentConfiguration(decoder);
|
|
||||||
config->outputFormat = FAAD_FMT_16BIT;
|
|
||||||
config->downMatrix = 1;
|
|
||||||
config->dontUpSampleImplicitSBR = 0;
|
|
||||||
NeAACDecSetConfiguration(decoder, config);
|
|
||||||
|
|
||||||
while (!decoder_buffer_is_full(buffer) && !is.LockIsEOF() &&
|
while (!decoder_buffer_is_full(buffer) && !is.LockIsEOF() &&
|
||||||
decoder_get_command(mpd_decoder) == DecoderCommand::NONE) {
|
decoder_get_command(mpd_decoder) == DecoderCommand::NONE) {
|
||||||
@@ -389,10 +384,11 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
|
|||||||
/* initialize it */
|
/* initialize it */
|
||||||
|
|
||||||
Error error;
|
Error error;
|
||||||
ret = faad_decoder_init(decoder, buffer, audio_format, error);
|
AudioFormat audio_format;
|
||||||
if (!ret) {
|
if (!faad_decoder_init(decoder, buffer, audio_format, error)) {
|
||||||
LogError(error);
|
LogError(error);
|
||||||
NeAACDecClose(decoder);
|
NeAACDecClose(decoder);
|
||||||
|
decoder_buffer_free(buffer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,21 +399,20 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
|
|||||||
/* the decoder loop */
|
/* the decoder loop */
|
||||||
|
|
||||||
DecoderCommand cmd;
|
DecoderCommand cmd;
|
||||||
|
uint16_t bit_rate = 0;
|
||||||
do {
|
do {
|
||||||
size_t frame_size;
|
|
||||||
const void *decoded;
|
|
||||||
NeAACDecFrameInfo frame_info;
|
|
||||||
|
|
||||||
/* find the next frame */
|
/* find the next frame */
|
||||||
|
|
||||||
frame_size = adts_find_frame(buffer);
|
const size_t frame_size = adts_find_frame(buffer);
|
||||||
if (frame_size == 0)
|
if (frame_size == 0)
|
||||||
/* end of file */
|
/* end of file */
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* decode it */
|
/* decode it */
|
||||||
|
|
||||||
decoded = faad_decoder_decode(decoder, buffer, &frame_info);
|
NeAACDecFrameInfo frame_info;
|
||||||
|
const void *const decoded =
|
||||||
|
faad_decoder_decode(decoder, buffer, &frame_info);
|
||||||
|
|
||||||
if (frame_info.error > 0) {
|
if (frame_info.error > 0) {
|
||||||
FormatWarning(faad_decoder_domain,
|
FormatWarning(faad_decoder_domain,
|
||||||
@@ -461,6 +456,7 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
|
|||||||
/* cleanup */
|
/* cleanup */
|
||||||
|
|
||||||
NeAACDecClose(decoder);
|
NeAACDecClose(decoder);
|
||||||
|
decoder_buffer_free(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@@ -468,7 +464,6 @@ faad_scan_stream(InputStream &is,
|
|||||||
const struct tag_handler *handler, void *handler_ctx)
|
const struct tag_handler *handler, void *handler_ctx)
|
||||||
{
|
{
|
||||||
int file_time = faad_get_file_time(is);
|
int file_time = faad_get_file_time(is);
|
||||||
|
|
||||||
if (file_time < 0)
|
if (file_time < 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@@ -38,7 +38,10 @@ extern "C" {
|
|||||||
#include <libavutil/avutil.h>
|
#include <libavutil/avutil.h>
|
||||||
#include <libavutil/log.h>
|
#include <libavutil/log.h>
|
||||||
#include <libavutil/mathematics.h>
|
#include <libavutil/mathematics.h>
|
||||||
#include <libavutil/dict.h>
|
|
||||||
|
#if LIBAVUTIL_VERSION_MAJOR >= 53
|
||||||
|
#include <libavutil/frame.h>
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@@ -120,8 +123,7 @@ mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
|
|||||||
if (whence == AVSEEK_SIZE)
|
if (whence == AVSEEK_SIZE)
|
||||||
return stream->input.size;
|
return stream->input.size;
|
||||||
|
|
||||||
Error error;
|
if (!stream->input.LockSeek(pos, whence, IgnoreError()))
|
||||||
if (!stream->input.LockSeek(pos, whence, error))
|
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
return stream->input.offset;
|
return stream->input.offset;
|
||||||
@@ -195,6 +197,29 @@ time_to_ffmpeg(double t, const AVRational time_base)
|
|||||||
time_base);
|
time_base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace #AV_NOPTS_VALUE with the given fallback.
|
||||||
|
*/
|
||||||
|
static constexpr int64_t
|
||||||
|
timestamp_fallback(int64_t t, int64_t fallback)
|
||||||
|
{
|
||||||
|
return gcc_likely(t != int64_t(AV_NOPTS_VALUE))
|
||||||
|
? t
|
||||||
|
: fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accessor for AVStream::start_time that replaces AV_NOPTS_VALUE with
|
||||||
|
* zero. We can't use AV_NOPTS_VALUE in calculations, and we simply
|
||||||
|
* assume that the stream's start time is zero, which appears to be
|
||||||
|
* the best way out of that situation.
|
||||||
|
*/
|
||||||
|
static int64_t
|
||||||
|
start_time_fallback(const AVStream &stream)
|
||||||
|
{
|
||||||
|
return timestamp_fallback(stream.start_time, 0);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
copy_interleave_frame2(uint8_t *dest, uint8_t **src,
|
copy_interleave_frame2(uint8_t *dest, uint8_t **src,
|
||||||
unsigned nframes, unsigned nchannels,
|
unsigned nframes, unsigned nchannels,
|
||||||
@@ -224,6 +249,9 @@ copy_interleave_frame(const AVCodecContext *codec_context,
|
|||||||
codec_context->channels,
|
codec_context->channels,
|
||||||
frame->nb_samples,
|
frame->nb_samples,
|
||||||
codec_context->sample_fmt, 1);
|
codec_context->sample_fmt, 1);
|
||||||
|
if (data_size <= 0)
|
||||||
|
return data_size;
|
||||||
|
|
||||||
if (av_sample_fmt_is_planar(codec_context->sample_fmt) &&
|
if (av_sample_fmt_is_planar(codec_context->sample_fmt) &&
|
||||||
codec_context->channels > 1) {
|
codec_context->channels > 1) {
|
||||||
if(*global_buffer_size < data_size) {
|
if(*global_buffer_size < data_size) {
|
||||||
@@ -252,13 +280,14 @@ static DecoderCommand
|
|||||||
ffmpeg_send_packet(Decoder &decoder, InputStream &is,
|
ffmpeg_send_packet(Decoder &decoder, InputStream &is,
|
||||||
const AVPacket *packet,
|
const AVPacket *packet,
|
||||||
AVCodecContext *codec_context,
|
AVCodecContext *codec_context,
|
||||||
const AVRational *time_base,
|
const AVStream *stream,
|
||||||
AVFrame *frame,
|
AVFrame *frame,
|
||||||
uint8_t **buffer, int *buffer_size)
|
uint8_t **buffer, int *buffer_size)
|
||||||
{
|
{
|
||||||
if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE)
|
if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE)
|
||||||
decoder_timestamp(decoder,
|
decoder_timestamp(decoder,
|
||||||
time_from_ffmpeg(packet->pts, *time_base));
|
time_from_ffmpeg(packet->pts - start_time_fallback(*stream),
|
||||||
|
stream->time_base));
|
||||||
|
|
||||||
AVPacket packet2 = *packet;
|
AVPacket packet2 = *packet;
|
||||||
|
|
||||||
@@ -342,11 +371,9 @@ ffmpeg_probe(Decoder *decoder, InputStream &is)
|
|||||||
PADDING = 16,
|
PADDING = 16,
|
||||||
};
|
};
|
||||||
|
|
||||||
Error error;
|
|
||||||
|
|
||||||
unsigned char buffer[BUFFER_SIZE];
|
unsigned char buffer[BUFFER_SIZE];
|
||||||
size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE);
|
size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE);
|
||||||
if (nbytes <= PADDING || !is.LockRewind(error))
|
if (nbytes <= PADDING || !is.LockRewind(IgnoreError()))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
/* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes
|
/* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes
|
||||||
@@ -356,10 +383,23 @@ ffmpeg_probe(Decoder *decoder, InputStream &is)
|
|||||||
nbytes -= PADDING;
|
nbytes -= PADDING;
|
||||||
|
|
||||||
AVProbeData avpd;
|
AVProbeData avpd;
|
||||||
|
|
||||||
|
/* new versions of ffmpeg may add new attributes, and leaving
|
||||||
|
them uninitialized may crash; hopefully, zero-initializing
|
||||||
|
everything we don't know is ok */
|
||||||
|
memset(&avpd, 0, sizeof(avpd));
|
||||||
|
|
||||||
avpd.buf = buffer;
|
avpd.buf = buffer;
|
||||||
avpd.buf_size = nbytes;
|
avpd.buf_size = nbytes;
|
||||||
avpd.filename = is.uri.c_str();
|
avpd.filename = is.uri.c_str();
|
||||||
|
|
||||||
|
#ifdef AVPROBE_SCORE_MIME
|
||||||
|
/* this attribute was added in libav/ffmpeg version 11, but
|
||||||
|
unfortunately it's "uint8_t" instead of "char", and it's
|
||||||
|
not "const" - wtf? */
|
||||||
|
avpd.mime_type = (uint8_t *)const_cast<char *>(is.GetMimeType());
|
||||||
|
#endif
|
||||||
|
|
||||||
return av_probe_input_format(&avpd, true);
|
return av_probe_input_format(&avpd, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,9 +446,18 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
|
|||||||
AVStream *av_stream = format_context->streams[audio_stream];
|
AVStream *av_stream = format_context->streams[audio_stream];
|
||||||
|
|
||||||
AVCodecContext *codec_context = av_stream->codec;
|
AVCodecContext *codec_context = av_stream->codec;
|
||||||
|
|
||||||
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 25, 0)
|
||||||
|
const AVCodecDescriptor *codec_descriptor =
|
||||||
|
avcodec_descriptor_get(codec_context->codec_id);
|
||||||
|
if (codec_descriptor != nullptr)
|
||||||
|
FormatDebug(ffmpeg_domain, "codec '%s'",
|
||||||
|
codec_descriptor->name);
|
||||||
|
#else
|
||||||
if (codec_context->codec_name[0] != 0)
|
if (codec_context->codec_name[0] != 0)
|
||||||
FormatDebug(ffmpeg_domain, "codec '%s'",
|
FormatDebug(ffmpeg_domain, "codec '%s'",
|
||||||
codec_context->codec_name);
|
codec_context->codec_name);
|
||||||
|
#endif
|
||||||
|
|
||||||
AVCodec *codec = avcodec_find_decoder(codec_context->codec_id);
|
AVCodec *codec = avcodec_find_decoder(codec_context->codec_id);
|
||||||
|
|
||||||
@@ -453,7 +502,11 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
|
|||||||
decoder_initialized(decoder, audio_format,
|
decoder_initialized(decoder, audio_format,
|
||||||
input.seekable, total_time);
|
input.seekable, total_time);
|
||||||
|
|
||||||
|
#if LIBAVUTIL_VERSION_MAJOR >= 53
|
||||||
|
AVFrame *frame = av_frame_alloc();
|
||||||
|
#else
|
||||||
AVFrame *frame = avcodec_alloc_frame();
|
AVFrame *frame = avcodec_alloc_frame();
|
||||||
|
#endif
|
||||||
if (!frame) {
|
if (!frame) {
|
||||||
LogError(ffmpeg_domain, "Could not allocate frame");
|
LogError(ffmpeg_domain, "Could not allocate frame");
|
||||||
avformat_close_input(&format_context);
|
avformat_close_input(&format_context);
|
||||||
@@ -473,7 +526,7 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
|
|||||||
if (packet.stream_index == audio_stream)
|
if (packet.stream_index == audio_stream)
|
||||||
cmd = ffmpeg_send_packet(decoder, input,
|
cmd = ffmpeg_send_packet(decoder, input,
|
||||||
&packet, codec_context,
|
&packet, codec_context,
|
||||||
&av_stream->time_base,
|
av_stream,
|
||||||
frame,
|
frame,
|
||||||
&interleaved_buffer, &interleaved_buffer_size);
|
&interleaved_buffer, &interleaved_buffer_size);
|
||||||
else
|
else
|
||||||
@@ -484,10 +537,11 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
|
|||||||
if (cmd == DecoderCommand::SEEK) {
|
if (cmd == DecoderCommand::SEEK) {
|
||||||
int64_t where =
|
int64_t where =
|
||||||
time_to_ffmpeg(decoder_seek_where(decoder),
|
time_to_ffmpeg(decoder_seek_where(decoder),
|
||||||
av_stream->time_base);
|
av_stream->time_base) +
|
||||||
|
start_time_fallback(*av_stream);
|
||||||
|
|
||||||
if (av_seek_frame(format_context, audio_stream, where,
|
if (av_seek_frame(format_context, audio_stream, where,
|
||||||
AV_TIME_BASE) < 0)
|
AVSEEK_FLAG_ANY) < 0)
|
||||||
decoder_seek_error(decoder);
|
decoder_seek_error(decoder);
|
||||||
else {
|
else {
|
||||||
avcodec_flush_buffers(codec_context);
|
avcodec_flush_buffers(codec_context);
|
||||||
@@ -496,7 +550,9 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
|
|||||||
}
|
}
|
||||||
} while (cmd != DecoderCommand::STOP);
|
} while (cmd != DecoderCommand::STOP);
|
||||||
|
|
||||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0)
|
#if LIBAVUTIL_VERSION_MAJOR >= 53
|
||||||
|
av_frame_free(&frame);
|
||||||
|
#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0)
|
||||||
avcodec_free_frame(&frame);
|
avcodec_free_frame(&frame);
|
||||||
#else
|
#else
|
||||||
av_freep(&frame);
|
av_freep(&frame);
|
||||||
@@ -564,7 +620,7 @@ static const char *const ffmpeg_suffixes[] = {
|
|||||||
"mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+",
|
"mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+",
|
||||||
"mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpg", "mpga", "mpp", "mpu",
|
"mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpg", "mpga", "mpp", "mpu",
|
||||||
"mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv",
|
"mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv",
|
||||||
"ogx", "oma", "ogg", "omg", "psp", "pva", "qcp", "qt", "r3d", "ra",
|
"ogx", "oma", "ogg", "omg", "opus", "psp", "pva", "qcp", "qt", "r3d", "ra",
|
||||||
"ram", "rl2", "rm", "rmvb", "roq", "rpl", "rvc", "shn", "smk", "snd",
|
"ram", "rl2", "rm", "rmvb", "roq", "rpl", "rvc", "shn", "smk", "snd",
|
||||||
"sol", "son", "spx", "str", "swf", "tgi", "tgq", "tgv", "thp", "ts",
|
"sol", "son", "spx", "str", "swf", "tgi", "tgq", "tgv", "thp", "ts",
|
||||||
"tsp", "tta", "xa", "xvid", "uv", "uv2", "vb", "vid", "vob", "voc",
|
"tsp", "tta", "xa", "xvid", "uv", "uv2", "vb", "vid", "vob", "voc",
|
||||||
@@ -587,6 +643,7 @@ static const char *const ffmpeg_mime_types[] = {
|
|||||||
"audio/8svx",
|
"audio/8svx",
|
||||||
"audio/16sv",
|
"audio/16sv",
|
||||||
"audio/aac",
|
"audio/aac",
|
||||||
|
"audio/aacp",
|
||||||
"audio/ac3",
|
"audio/ac3",
|
||||||
"audio/aiff"
|
"audio/aiff"
|
||||||
"audio/amr",
|
"audio/amr",
|
||||||
@@ -597,6 +654,7 @@ static const char *const ffmpeg_mime_types[] = {
|
|||||||
"audio/mpeg",
|
"audio/mpeg",
|
||||||
"audio/musepack",
|
"audio/musepack",
|
||||||
"audio/ogg",
|
"audio/ogg",
|
||||||
|
"audio/opus",
|
||||||
"audio/qcelp",
|
"audio/qcelp",
|
||||||
"audio/vorbis",
|
"audio/vorbis",
|
||||||
"audio/vorbis+ogg",
|
"audio/vorbis+ogg",
|
||||||
|
@@ -21,8 +21,6 @@
|
|||||||
#define MPD_FFMPEG_METADATA_HXX
|
#define MPD_FFMPEG_METADATA_HXX
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <libavformat/avformat.h>
|
|
||||||
#include <libavutil/avutil.h>
|
|
||||||
#include <libavutil/dict.h>
|
#include <libavutil/dict.h>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,6 +33,6 @@ struct tag_handler;
|
|||||||
|
|
||||||
void
|
void
|
||||||
ffmpeg_scan_dictionary(AVDictionary *dict,
|
ffmpeg_scan_dictionary(AVDictionary *dict,
|
||||||
const struct tag_handler *handler, void *handler_ctx);
|
const tag_handler *handler, void *handler_ctx);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -117,6 +117,7 @@ gme_container_scan(const char *path_fs, const unsigned int tnum)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const unsigned num_songs = gme_track_count(emu);
|
const unsigned num_songs = gme_track_count(emu);
|
||||||
|
gme_delete(emu);
|
||||||
/* if it only contains a single tune, don't treat as container */
|
/* if it only contains a single tune, don't treat as container */
|
||||||
if (num_songs < 2)
|
if (num_songs < 2)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -234,7 +235,7 @@ gme_scan_file(const char *path_fs,
|
|||||||
|
|
||||||
if (ti->length > 0)
|
if (ti->length > 0)
|
||||||
tag_handler_invoke_duration(handler, handler_ctx,
|
tag_handler_invoke_duration(handler, handler_ctx,
|
||||||
ti->length / 100);
|
ti->length / 1000);
|
||||||
|
|
||||||
if (ti->song != nullptr) {
|
if (ti->song != nullptr) {
|
||||||
if (gme_track_count(emu) > 1) {
|
if (gme_track_count(emu) > 1) {
|
||||||
|
@@ -353,18 +353,8 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
|
|||||||
memcpy(allocated, stream.this_frame, count);
|
memcpy(allocated, stream.this_frame, count);
|
||||||
mad_stream_skip(&(stream), count);
|
mad_stream_skip(&(stream), count);
|
||||||
|
|
||||||
while (count < tagsize) {
|
if (!decoder_read_full(decoder, input_stream,
|
||||||
size_t len;
|
allocated + count, tagsize - count)) {
|
||||||
|
|
||||||
len = decoder_read(decoder, input_stream,
|
|
||||||
allocated + count, tagsize - count);
|
|
||||||
if (len == 0)
|
|
||||||
break;
|
|
||||||
else
|
|
||||||
count += len;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count != tagsize) {
|
|
||||||
LogDebug(mad_domain, "error parsing ID3 tag");
|
LogDebug(mad_domain, "error parsing ID3 tag");
|
||||||
g_free(allocated);
|
g_free(allocated);
|
||||||
return;
|
return;
|
||||||
@@ -413,20 +403,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
|
|||||||
mad_stream_skip(&stream, tagsize);
|
mad_stream_skip(&stream, tagsize);
|
||||||
} else {
|
} else {
|
||||||
mad_stream_skip(&stream, count);
|
mad_stream_skip(&stream, count);
|
||||||
|
decoder_skip(decoder, input_stream, tagsize - count);
|
||||||
while (count < tagsize) {
|
|
||||||
size_t len = tagsize - count;
|
|
||||||
char ignored[1024];
|
|
||||||
if (len > sizeof(ignored))
|
|
||||||
len = sizeof(ignored);
|
|
||||||
|
|
||||||
len = decoder_read(decoder, input_stream,
|
|
||||||
ignored, len);
|
|
||||||
if (len == 0)
|
|
||||||
break;
|
|
||||||
else
|
|
||||||
count += len;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,8 @@
|
|||||||
#include "OggSyncState.hxx"
|
#include "OggSyncState.hxx"
|
||||||
#include "util/Error.hxx"
|
#include "util/Error.hxx"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
bool
|
bool
|
||||||
OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet)
|
OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet)
|
||||||
{
|
{
|
||||||
|
@@ -81,7 +81,7 @@ bool
|
|||||||
OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page,
|
OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page,
|
||||||
Decoder *decoder, InputStream &input_stream)
|
Decoder *decoder, InputStream &input_stream)
|
||||||
{
|
{
|
||||||
size_t remaining_skipped = 16384;
|
size_t remaining_skipped = 32768;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
int r = ogg_sync_pageseek(&oy, &page);
|
int r = ogg_sync_pageseek(&oy, &page);
|
||||||
|
@@ -40,6 +40,7 @@
|
|||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
static constexpr opus_int32 opus_sample_rate = 48000;
|
static constexpr opus_int32 opus_sample_rate = 48000;
|
||||||
|
|
||||||
|
@@ -31,10 +31,24 @@
|
|||||||
|
|
||||||
static constexpr Domain sndfile_domain("sndfile");
|
static constexpr Domain sndfile_domain("sndfile");
|
||||||
|
|
||||||
|
struct SndfileInputStream {
|
||||||
|
Decoder *const decoder;
|
||||||
|
InputStream &is;
|
||||||
|
|
||||||
|
size_t Read(void *buffer, size_t size) {
|
||||||
|
/* libsndfile chokes on partial reads; therefore
|
||||||
|
always force full reads */
|
||||||
|
return decoder_read_full(decoder, is, buffer, size)
|
||||||
|
? size
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
static sf_count_t
|
static sf_count_t
|
||||||
sndfile_vio_get_filelen(void *user_data)
|
sndfile_vio_get_filelen(void *user_data)
|
||||||
{
|
{
|
||||||
const InputStream &is = *(const InputStream *)user_data;
|
SndfileInputStream &sis = *(SndfileInputStream *)user_data;
|
||||||
|
const InputStream &is = sis.is;
|
||||||
|
|
||||||
return is.GetSize();
|
return is.GetSize();
|
||||||
}
|
}
|
||||||
@@ -42,10 +56,14 @@ sndfile_vio_get_filelen(void *user_data)
|
|||||||
static sf_count_t
|
static sf_count_t
|
||||||
sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
|
sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
|
||||||
{
|
{
|
||||||
InputStream &is = *(InputStream *)user_data;
|
SndfileInputStream &sis = *(SndfileInputStream *)user_data;
|
||||||
|
InputStream &is = sis.is;
|
||||||
|
|
||||||
if (!is.LockSeek(offset, whence, IgnoreError()))
|
Error error;
|
||||||
|
if (!is.LockSeek(offset, whence, error)) {
|
||||||
|
LogError(error, "Seek failed");
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
return is.GetOffset();
|
return is.GetOffset();
|
||||||
}
|
}
|
||||||
@@ -53,16 +71,9 @@ sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
|
|||||||
static sf_count_t
|
static sf_count_t
|
||||||
sndfile_vio_read(void *ptr, sf_count_t count, void *user_data)
|
sndfile_vio_read(void *ptr, sf_count_t count, void *user_data)
|
||||||
{
|
{
|
||||||
InputStream &is = *(InputStream *)user_data;
|
SndfileInputStream &sis = *(SndfileInputStream *)user_data;
|
||||||
|
|
||||||
Error error;
|
return sis.Read(ptr, count);
|
||||||
size_t nbytes = is.LockRead(ptr, count, error);
|
|
||||||
if (nbytes == 0 && error.IsDefined()) {
|
|
||||||
LogError(error);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nbytes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static sf_count_t
|
static sf_count_t
|
||||||
@@ -77,7 +88,8 @@ sndfile_vio_write(gcc_unused const void *ptr,
|
|||||||
static sf_count_t
|
static sf_count_t
|
||||||
sndfile_vio_tell(void *user_data)
|
sndfile_vio_tell(void *user_data)
|
||||||
{
|
{
|
||||||
const InputStream &is = *(const InputStream *)user_data;
|
SndfileInputStream &sis = *(SndfileInputStream *)user_data;
|
||||||
|
const InputStream &is = sis.is;
|
||||||
|
|
||||||
return is.GetOffset();
|
return is.GetOffset();
|
||||||
}
|
}
|
||||||
@@ -123,7 +135,8 @@ sndfile_stream_decode(Decoder &decoder, InputStream &is)
|
|||||||
|
|
||||||
info.format = 0;
|
info.format = 0;
|
||||||
|
|
||||||
sf = sf_open_virtual(&vio, SFM_READ, &info, &is);
|
SndfileInputStream sis{&decoder, is};
|
||||||
|
sf = sf_open_virtual(&vio, SFM_READ, &info, &sis);
|
||||||
if (sf == nullptr) {
|
if (sf == nullptr) {
|
||||||
LogWarning(sndfile_domain, "sf_open_virtual() failed");
|
LogWarning(sndfile_domain, "sf_open_virtual() failed");
|
||||||
return;
|
return;
|
||||||
|
@@ -118,7 +118,7 @@ BufferedSocket::OnSocketReady(unsigned flags)
|
|||||||
if (!ReadToBuffer() || !ResumeInput())
|
if (!ReadToBuffer() || !ResumeInput())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (input.IsFull())
|
if (!input.IsFull())
|
||||||
ScheduleRead();
|
ScheduleRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -27,9 +27,11 @@ DeferredMonitor::Cancel()
|
|||||||
#ifdef USE_EPOLL
|
#ifdef USE_EPOLL
|
||||||
pending = false;
|
pending = false;
|
||||||
#else
|
#else
|
||||||
const auto id = source_id.exchange(0);
|
const ScopeLock protect(mutex);
|
||||||
if (id != 0)
|
if (source_id != 0) {
|
||||||
g_source_remove(id);
|
g_source_remove(source_id);
|
||||||
|
source_id = 0;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,10 +42,9 @@ DeferredMonitor::Schedule()
|
|||||||
if (!pending.exchange(true))
|
if (!pending.exchange(true))
|
||||||
fd.Write();
|
fd.Write();
|
||||||
#else
|
#else
|
||||||
const unsigned id = loop.AddIdle(Callback, this);
|
const ScopeLock protect(mutex);
|
||||||
const auto old_id = source_id.exchange(id);
|
if (source_id == 0)
|
||||||
if (old_id != 0)
|
source_id = loop.AddIdle(Callback, this);
|
||||||
g_source_remove(old_id);
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,9 +66,16 @@ DeferredMonitor::OnSocketReady(unsigned)
|
|||||||
void
|
void
|
||||||
DeferredMonitor::Run()
|
DeferredMonitor::Run()
|
||||||
{
|
{
|
||||||
const auto id = source_id.exchange(0);
|
{
|
||||||
if (id != 0)
|
const ScopeLock protect(mutex);
|
||||||
RunDeferred();
|
if (source_id == 0)
|
||||||
|
/* cancelled */
|
||||||
|
return;
|
||||||
|
|
||||||
|
source_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
RunDeferred();
|
||||||
}
|
}
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
|
@@ -27,6 +27,7 @@
|
|||||||
#include "SocketMonitor.hxx"
|
#include "SocketMonitor.hxx"
|
||||||
#include "WakeFD.hxx"
|
#include "WakeFD.hxx"
|
||||||
#else
|
#else
|
||||||
|
#include "thread/Mutex.hxx"
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -48,7 +49,9 @@ class DeferredMonitor
|
|||||||
#else
|
#else
|
||||||
EventLoop &loop;
|
EventLoop &loop;
|
||||||
|
|
||||||
std::atomic<guint> source_id;
|
Mutex mutex;
|
||||||
|
|
||||||
|
guint source_id;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@@ -141,7 +141,7 @@ get_remote_uid(int fd)
|
|||||||
socklen_t len = sizeof (cred);
|
socklen_t len = sizeof (cred);
|
||||||
|
|
||||||
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0)
|
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0)
|
||||||
return 0;
|
return -1;
|
||||||
|
|
||||||
return cred.uid;
|
return cred.uid;
|
||||||
#else
|
#else
|
||||||
|
@@ -39,6 +39,12 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
#ifdef USE_SIGNALFD
|
||||||
|
#include <pthread.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
class SignalMonitor final : private SocketMonitor {
|
class SignalMonitor final : private SocketMonitor {
|
||||||
#ifdef USE_SIGNALFD
|
#ifdef USE_SIGNALFD
|
||||||
SignalFD fd;
|
SignalFD fd;
|
||||||
@@ -99,7 +105,21 @@ static std::atomic_bool signal_pending[MAX_SIGNAL];
|
|||||||
|
|
||||||
static Manual<SignalMonitor> monitor;
|
static Manual<SignalMonitor> monitor;
|
||||||
|
|
||||||
#ifndef USE_SIGNALFD
|
#ifdef USE_SIGNALFD
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a pthread_atfork() callback that unblocks the signals that
|
||||||
|
* were blocked for our signalfd(). Without this, our child processes
|
||||||
|
* would inherit the blocked signals.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
at_fork_child()
|
||||||
|
{
|
||||||
|
sigprocmask(SIG_UNBLOCK, &signal_mask, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
static void
|
static void
|
||||||
SignalCallback(int signo)
|
SignalCallback(int signo)
|
||||||
{
|
{
|
||||||
@@ -108,6 +128,7 @@ SignalCallback(int signo)
|
|||||||
if (!signal_pending[signo].exchange(true))
|
if (!signal_pending[signo].exchange(true))
|
||||||
monitor->WakeUp();
|
monitor->WakeUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -115,6 +136,8 @@ SignalMonitorInit(EventLoop &loop)
|
|||||||
{
|
{
|
||||||
#ifdef USE_SIGNALFD
|
#ifdef USE_SIGNALFD
|
||||||
sigemptyset(&signal_mask);
|
sigemptyset(&signal_mask);
|
||||||
|
|
||||||
|
pthread_atfork(nullptr, nullptr, at_fork_child);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
monitor.Construct(loop);
|
monitor.Construct(loop);
|
||||||
|
@@ -64,7 +64,9 @@ TimeoutMonitor::ScheduleSeconds(unsigned s)
|
|||||||
void
|
void
|
||||||
TimeoutMonitor::Run()
|
TimeoutMonitor::Run()
|
||||||
{
|
{
|
||||||
#ifndef USE_EPOLL
|
#ifdef USE_EPOLL
|
||||||
|
active = false;
|
||||||
|
#else
|
||||||
Cancel();
|
Cancel();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@@ -46,7 +46,11 @@ AllocatedPath::Build(const_pointer a, const_pointer b)
|
|||||||
AllocatedPath
|
AllocatedPath
|
||||||
AllocatedPath::FromUTF8(const char *path_utf8)
|
AllocatedPath::FromUTF8(const char *path_utf8)
|
||||||
{
|
{
|
||||||
return AllocatedPath(Donate(), ::PathFromUTF8(path_utf8));
|
char *path = ::PathFromUTF8(path_utf8);
|
||||||
|
if (path == nullptr)
|
||||||
|
return AllocatedPath::Null();
|
||||||
|
|
||||||
|
return AllocatedPath(Donate(), path);
|
||||||
}
|
}
|
||||||
|
|
||||||
AllocatedPath
|
AllocatedPath
|
||||||
|
@@ -41,7 +41,12 @@
|
|||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
#ifdef HAVE_CDIO_PARANOIA_PARANOIA_H
|
||||||
|
#include <cdio/paranoia/paranoia.h>
|
||||||
|
#else
|
||||||
#include <cdio/paranoia.h>
|
#include <cdio/paranoia.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <cdio/cd_types.h>
|
#include <cdio/cd_types.h>
|
||||||
|
|
||||||
struct CdioParanoiaInputStream {
|
struct CdioParanoiaInputStream {
|
||||||
|
@@ -273,12 +273,27 @@ public:
|
|||||||
SocketAction(CURL_SOCKET_TIMEOUT, 0);
|
SocketAction(CURL_SOCKET_TIMEOUT, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a kludge to allow pausing/resuming a stream with
|
||||||
|
* libcurl < 7.32.0. Read the curl_easy_pause manpage for
|
||||||
|
* more information.
|
||||||
|
*/
|
||||||
|
void ResumeSockets() {
|
||||||
|
int running_handles;
|
||||||
|
curl_multi_socket_all(multi, &running_handles);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static int TimerFunction(CURLM *multi, long timeout_ms, void *userp);
|
static int TimerFunction(CURLM *multi, long timeout_ms, void *userp);
|
||||||
|
|
||||||
virtual void OnTimeout() override;
|
virtual void OnTimeout() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* libcurl version number encoded in a 24 bit integer.
|
||||||
|
*/
|
||||||
|
static unsigned curl_version_num;
|
||||||
|
|
||||||
/** libcurl should accept "ICY 200 OK" */
|
/** libcurl should accept "ICY 200 OK" */
|
||||||
static struct curl_slist *http_200_aliases;
|
static struct curl_slist *http_200_aliases;
|
||||||
|
|
||||||
@@ -330,6 +345,13 @@ input_curl_resume(struct input_curl *c)
|
|||||||
if (c->paused) {
|
if (c->paused) {
|
||||||
c->paused = false;
|
c->paused = false;
|
||||||
curl_easy_pause(c->easy, CURLPAUSE_CONT);
|
curl_easy_pause(c->easy, CURLPAUSE_CONT);
|
||||||
|
|
||||||
|
if (curl_version_num < 0x072000)
|
||||||
|
/* libcurl older than 7.32.0 does not update
|
||||||
|
its sockets after curl_easy_pause(); force
|
||||||
|
libcurl to do it now */
|
||||||
|
curl_multi->ResumeSockets();
|
||||||
|
|
||||||
curl_multi->InvalidateSockets();
|
curl_multi->InvalidateSockets();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -586,6 +608,16 @@ input_curl_init(const config_param ¶m, Error &error)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto version_info = curl_version_info(CURLVERSION_FIRST);
|
||||||
|
if (version_info != nullptr) {
|
||||||
|
FormatDebug(curl_domain, "version %s", version_info->version);
|
||||||
|
if (version_info->features & CURL_VERSION_SSL)
|
||||||
|
FormatDebug(curl_domain, "with %s",
|
||||||
|
version_info->ssl_version);
|
||||||
|
|
||||||
|
curl_version_num = version_info->version_num;
|
||||||
|
}
|
||||||
|
|
||||||
http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK");
|
http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK");
|
||||||
|
|
||||||
proxy = param.GetBlockValue("proxy");
|
proxy = param.GetBlockValue("proxy");
|
||||||
@@ -951,10 +983,10 @@ input_curl_easy_init(struct input_curl *c, Error &error)
|
|||||||
input_curl_writefunction);
|
input_curl_writefunction);
|
||||||
curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c);
|
curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c);
|
||||||
curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases);
|
curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases);
|
||||||
curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1);
|
curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1l);
|
||||||
curl_easy_setopt(c->easy, CURLOPT_NETRC, 1);
|
curl_easy_setopt(c->easy, CURLOPT_NETRC, 1l);
|
||||||
curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5);
|
curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5l);
|
||||||
curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true);
|
curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, 1l);
|
||||||
curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error);
|
curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error);
|
||||||
curl_easy_setopt(c->easy, CURLOPT_NOPROGRESS, 1l);
|
curl_easy_setopt(c->easy, CURLOPT_NOPROGRESS, 1l);
|
||||||
curl_easy_setopt(c->easy, CURLOPT_NOSIGNAL, 1l);
|
curl_easy_setopt(c->easy, CURLOPT_NOSIGNAL, 1l);
|
||||||
|
@@ -24,6 +24,7 @@
|
|||||||
#include "Main.hxx"
|
#include "Main.hxx"
|
||||||
#include "event/MultiSocketMonitor.hxx"
|
#include "event/MultiSocketMonitor.hxx"
|
||||||
#include "event/Loop.hxx"
|
#include "event/Loop.hxx"
|
||||||
|
#include "event/Call.hxx"
|
||||||
#include "util/ASCII.hxx"
|
#include "util/ASCII.hxx"
|
||||||
#include "util/ReusableArray.hxx"
|
#include "util/ReusableArray.hxx"
|
||||||
#include "util/Error.hxx"
|
#include "util/Error.hxx"
|
||||||
@@ -46,10 +47,22 @@ class AlsaMixerMonitor final : private MultiSocketMonitor {
|
|||||||
public:
|
public:
|
||||||
AlsaMixerMonitor(EventLoop &_loop, snd_mixer_t *_mixer)
|
AlsaMixerMonitor(EventLoop &_loop, snd_mixer_t *_mixer)
|
||||||
:MultiSocketMonitor(_loop), mixer(_mixer) {
|
:MultiSocketMonitor(_loop), mixer(_mixer) {
|
||||||
|
#ifdef USE_EPOLL
|
||||||
_loop.AddCall([this](){ InvalidateSockets(); });
|
_loop.AddCall([this](){ InvalidateSockets(); });
|
||||||
|
#else
|
||||||
|
_loop.AddIdle(InitAlsaMixerMonitor, this);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
#ifndef USE_EPOLL
|
||||||
|
static gboolean InitAlsaMixerMonitor(gpointer data) {
|
||||||
|
AlsaMixerMonitor &amm = *(AlsaMixerMonitor *)data;
|
||||||
|
amm.InvalidateSockets();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
virtual int PrepareSockets() override;
|
virtual int PrepareSockets() override;
|
||||||
virtual void DispatchSockets() override;
|
virtual void DispatchSockets() override;
|
||||||
};
|
};
|
||||||
|
@@ -28,7 +28,7 @@ struct notify {
|
|||||||
Cond cond;
|
Cond cond;
|
||||||
bool pending;
|
bool pending;
|
||||||
|
|
||||||
#ifndef WIN32
|
#if !defined(WIN32) && !defined(__NetBSD__)
|
||||||
constexpr
|
constexpr
|
||||||
#endif
|
#endif
|
||||||
notify():pending(false) {}
|
notify():pending(false) {}
|
||||||
|
@@ -106,12 +106,16 @@ struct AlsaOutput {
|
|||||||
snd_pcm_uframes_t period_position;
|
snd_pcm_uframes_t period_position;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set to non-zero when the Raspberry Pi workaround has been
|
* Do we need to call snd_pcm_prepare() before the next write?
|
||||||
* activated in alsa_recover(); decremented by each write.
|
* It means that we put the device to SND_PCM_STATE_SETUP by
|
||||||
* This will avoid activating it again, leading to an endless
|
* calling snd_pcm_drop().
|
||||||
* loop. This problem was observed with a "RME Digi9636/52".
|
*
|
||||||
|
* Without this flag, we could easily recover after a failed
|
||||||
|
* optimistic write (returning -EBADFD), but the Raspberry Pi
|
||||||
|
* audio driver is infamous for generating ugly artefacts from
|
||||||
|
* this.
|
||||||
*/
|
*/
|
||||||
unsigned pi_workaround;
|
bool must_prepare;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This buffer gets allocated after opening the ALSA device.
|
* This buffer gets allocated after opening the ALSA device.
|
||||||
@@ -676,8 +680,6 @@ alsa_open(struct audio_output *ao, AudioFormat &audio_format, Error &error)
|
|||||||
{
|
{
|
||||||
AlsaOutput *ad = (AlsaOutput *)ao;
|
AlsaOutput *ad = (AlsaOutput *)ao;
|
||||||
|
|
||||||
ad->pi_workaround = 0;
|
|
||||||
|
|
||||||
int err = snd_pcm_open(&ad->pcm, alsa_device(ad),
|
int err = snd_pcm_open(&ad->pcm, alsa_device(ad),
|
||||||
SND_PCM_STREAM_PLAYBACK, ad->mode);
|
SND_PCM_STREAM_PLAYBACK, ad->mode);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
@@ -699,6 +701,8 @@ alsa_open(struct audio_output *ao, AudioFormat &audio_format, Error &error)
|
|||||||
ad->in_frame_size = audio_format.GetFrameSize();
|
ad->in_frame_size = audio_format.GetFrameSize();
|
||||||
ad->out_frame_size = ad->pcm_export->GetFrameSize(audio_format);
|
ad->out_frame_size = ad->pcm_export->GetFrameSize(audio_format);
|
||||||
|
|
||||||
|
ad->must_prepare = false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -736,29 +740,6 @@ alsa_recover(AlsaOutput *ad, int err)
|
|||||||
case SND_PCM_STATE_XRUN:
|
case SND_PCM_STATE_XRUN:
|
||||||
ad->period_position = 0;
|
ad->period_position = 0;
|
||||||
err = snd_pcm_prepare(ad->pcm);
|
err = snd_pcm_prepare(ad->pcm);
|
||||||
|
|
||||||
if (err == 0 && ad->pi_workaround == 0) {
|
|
||||||
/* this works around a driver bug observed on
|
|
||||||
the Raspberry Pi: after snd_pcm_drop(), the
|
|
||||||
whole ring buffer must be invalidated, but
|
|
||||||
the snd_pcm_prepare() call above makes the
|
|
||||||
driver play random data that just happens
|
|
||||||
to be still in the buffer; by adding and
|
|
||||||
cancelling some silence, this bug does not
|
|
||||||
occur */
|
|
||||||
alsa_write_silence(ad, ad->period_frames);
|
|
||||||
|
|
||||||
/* cancel the silence data right away to avoid
|
|
||||||
increasing latency; even though this
|
|
||||||
function call invalidates the portion of
|
|
||||||
silence, the driver seems to avoid the
|
|
||||||
bug */
|
|
||||||
snd_pcm_reset(ad->pcm);
|
|
||||||
|
|
||||||
/* disable the workaround for some time */
|
|
||||||
ad->pi_workaround = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case SND_PCM_STATE_DISCONNECTED:
|
case SND_PCM_STATE_DISCONNECTED:
|
||||||
break;
|
break;
|
||||||
@@ -801,6 +782,7 @@ alsa_cancel(struct audio_output *ao)
|
|||||||
AlsaOutput *ad = (AlsaOutput *)ao;
|
AlsaOutput *ad = (AlsaOutput *)ao;
|
||||||
|
|
||||||
ad->period_position = 0;
|
ad->period_position = 0;
|
||||||
|
ad->must_prepare = true;
|
||||||
|
|
||||||
snd_pcm_drop(ad->pcm);
|
snd_pcm_drop(ad->pcm);
|
||||||
}
|
}
|
||||||
@@ -820,13 +802,34 @@ alsa_play(struct audio_output *ao, const void *chunk, size_t size,
|
|||||||
{
|
{
|
||||||
AlsaOutput *ad = (AlsaOutput *)ao;
|
AlsaOutput *ad = (AlsaOutput *)ao;
|
||||||
|
|
||||||
|
assert(size > 0);
|
||||||
assert(size % ad->in_frame_size == 0);
|
assert(size % ad->in_frame_size == 0);
|
||||||
|
|
||||||
|
if (ad->must_prepare) {
|
||||||
|
ad->must_prepare = false;
|
||||||
|
|
||||||
|
int err = snd_pcm_prepare(ad->pcm);
|
||||||
|
if (err < 0) {
|
||||||
|
error.Set(alsa_output_domain, err, snd_strerror(-err));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t original_size = size;
|
||||||
chunk = ad->pcm_export->Export(chunk, size, size);
|
chunk = ad->pcm_export->Export(chunk, size, size);
|
||||||
|
if (size == 0)
|
||||||
|
/* the DoP (DSD over PCM) filter converts two frames
|
||||||
|
at a time and ignores the last odd frame; if there
|
||||||
|
was only one frame (e.g. the last frame in the
|
||||||
|
file), the result is empty; to avoid an endless
|
||||||
|
loop, bail out here, and pretend the one frame has
|
||||||
|
been played */
|
||||||
|
return original_size;
|
||||||
|
|
||||||
assert(size % ad->out_frame_size == 0);
|
assert(size % ad->out_frame_size == 0);
|
||||||
|
|
||||||
size /= ad->out_frame_size;
|
size /= ad->out_frame_size;
|
||||||
|
assert(size > 0);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);
|
snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);
|
||||||
@@ -834,9 +837,6 @@ alsa_play(struct audio_output *ao, const void *chunk, size_t size,
|
|||||||
ad->period_position = (ad->period_position + ret)
|
ad->period_position = (ad->period_position + ret)
|
||||||
% ad->period_frames;
|
% ad->period_frames;
|
||||||
|
|
||||||
if (ad->pi_workaround > 0)
|
|
||||||
--ad->pi_workaround;
|
|
||||||
|
|
||||||
size_t bytes_written = ret * ad->out_frame_size;
|
size_t bytes_written = ret * ad->out_frame_size;
|
||||||
return ad->pcm_export->CalcSourceSize(bytes_written);
|
return ad->pcm_export->CalcSourceSize(bytes_written);
|
||||||
}
|
}
|
||||||
|
@@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
HttpdClient::~HttpdClient()
|
HttpdClient::~HttpdClient()
|
||||||
{
|
{
|
||||||
|
@@ -363,9 +363,9 @@ osx_output_open(struct audio_output *ao, AudioFormat &audio_format,
|
|||||||
|
|
||||||
OSStatus status = AudioUnitInitialize(od->au);
|
OSStatus status = AudioUnitInitialize(od->au);
|
||||||
if (status != noErr) {
|
if (status != noErr) {
|
||||||
error.Set(osx_output_domain, status,
|
error.Format(osx_output_domain, status,
|
||||||
"Unable to initialize OS X audio unit: %s",
|
"Unable to initialize OS X audio unit: %s",
|
||||||
GetMacOSStatusCommentString(status));
|
GetMacOSStatusCommentString(status));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
||||||
#ifndef HAVE_OSX
|
#ifndef __APPLE__
|
||||||
#include <AL/al.h>
|
#include <AL/al.h>
|
||||||
#include <AL/alc.h>
|
#include <AL/alc.h>
|
||||||
#else
|
#else
|
||||||
|
@@ -727,6 +727,8 @@ oss_output_play(struct audio_output *ao, const void *chunk, size_t size,
|
|||||||
OssOutput *od = (OssOutput *)ao;
|
OssOutput *od = (OssOutput *)ao;
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
|
|
||||||
|
assert(size > 0);
|
||||||
|
|
||||||
/* reopen the device since it was closed by dropBufferedAudio */
|
/* reopen the device since it was closed by dropBufferedAudio */
|
||||||
if (od->fd < 0 && !oss_reopen(od, error))
|
if (od->fd < 0 && !oss_reopen(od, error))
|
||||||
return 0;
|
return 0;
|
||||||
@@ -735,6 +737,8 @@ oss_output_play(struct audio_output *ao, const void *chunk, size_t size,
|
|||||||
chunk = od->pcm_export->Export(chunk, size, size);
|
chunk = od->pcm_export->Export(chunk, size, size);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
assert(size > 0);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
ret = write(od->fd, chunk, size);
|
ret = write(od->fd, chunk, size);
|
||||||
if (ret > 0) {
|
if (ret > 0) {
|
||||||
|
@@ -369,8 +369,6 @@ pulse_output_enable(struct audio_output *ao, Error &error)
|
|||||||
|
|
||||||
po->mainloop = pa_threaded_mainloop_new();
|
po->mainloop = pa_threaded_mainloop_new();
|
||||||
if (po->mainloop == nullptr) {
|
if (po->mainloop == nullptr) {
|
||||||
g_free(po);
|
|
||||||
|
|
||||||
error.Set(pulse_output_domain,
|
error.Set(pulse_output_domain,
|
||||||
"pa_threaded_mainloop_new() has failed");
|
"pa_threaded_mainloop_new() has failed");
|
||||||
return false;
|
return false;
|
||||||
|
@@ -46,7 +46,7 @@ class RoarOutput {
|
|||||||
struct roar_connection con;
|
struct roar_connection con;
|
||||||
struct roar_audio_info info;
|
struct roar_audio_info info;
|
||||||
mutable Mutex mutex;
|
mutable Mutex mutex;
|
||||||
volatile bool alive;
|
bool alive;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RoarOutput()
|
RoarOutput()
|
||||||
|
@@ -148,3 +148,26 @@ PcmResampler::Resample32(unsigned channels, unsigned src_rate,
|
|||||||
src_rate, src_buffer, src_size,
|
src_rate, src_buffer, src_size,
|
||||||
dest_rate, dest_size_r);
|
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);
|
||||||
|
}
|
||||||
|
@@ -124,13 +124,7 @@ struct PcmResampler {
|
|||||||
const int32_t *Resample24(unsigned channels, unsigned src_rate,
|
const int32_t *Resample24(unsigned channels, unsigned src_rate,
|
||||||
const int32_t *src_buffer, size_t src_size,
|
const int32_t *src_buffer, size_t src_size,
|
||||||
unsigned dest_rate, size_t *dest_size_r,
|
unsigned dest_rate, size_t *dest_size_r,
|
||||||
Error &error_r)
|
Error &error_r);
|
||||||
{
|
|
||||||
/* reuse the 32 bit code - the resampler code doesn't care if
|
|
||||||
the upper 8 bits are actually used */
|
|
||||||
return Resample32(channels, src_rate, src_buffer, src_size,
|
|
||||||
dest_rate, dest_size_r, error_r);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
@@ -69,6 +69,15 @@ pcm_resample_lsr_32(PcmResampler *state,
|
|||||||
unsigned dest_rate, size_t *dest_size_r,
|
unsigned dest_rate, size_t *dest_size_r,
|
||||||
Error &error);
|
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
|
#endif
|
||||||
|
|
||||||
const int16_t *
|
const int16_t *
|
||||||
|
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "PcmResampleInternal.hxx"
|
#include "PcmResampleInternal.hxx"
|
||||||
|
#include "PcmUtils.hxx"
|
||||||
#include "util/ASCII.hxx"
|
#include "util/ASCII.hxx"
|
||||||
#include "util/Error.hxx"
|
#include "util/Error.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
@@ -283,3 +284,27 @@ pcm_resample_lsr_32(PcmResampler *state,
|
|||||||
|
|
||||||
return dest_buffer;
|
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;
|
||||||
|
}
|
||||||
|
@@ -63,4 +63,16 @@ PcmClamp(U x)
|
|||||||
return T(x);
|
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
|
#endif
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user