Compare commits

...

79 Commits

Author SHA1 Message Date
Max Kellermann
86e8b3b4bd release v0.18.13 2014-08-31 14:50:23 +02:00
Max Kellermann
a26ead035a PlaylistControl: use SeekSongOrder(current) to keep current song
The "current" attribute is a "song order", not a "song position".
This is usually the same - except in random mode.  Fixes Mantis ticket
0004073.
2014-08-31 14:44:20 +02:00
Max Kellermann
704be54c3a PlaylistControl: move code to new method SeekSongOrder() 2014-08-31 14:23:06 +02:00
Max Kellermann
2406152576 output/alsa: fix endless loop at end of file in dsd_usb mode 2014-08-31 14:01:57 +02:00
Max Kellermann
af260b5a64 output/{alsa,oss}: add assertions 2014-08-31 14:00:09 +02:00
Joachim Fasting
4efa96df21 doc/protocol: fix description of "stats" response
Fix incorrect description of the "songs" field and add missing
"albums" field.

Signed-off-by: Joachim Fasting <joachifm@fastmail.fm>
2014-08-31 13:16:39 +02:00
Max Kellermann
8b62127770 decoder/gme: fix song duration
The unit of gme_info_t::length is milliseconds, not centiseconds.
2014-08-29 23:03:29 +02:00
Max Kellermann
f06fe1ea98 event/TimeoutMonitor: really reset "active" flag before invoking OnTimeout()
The previous commit was broken.  D'oh!
2014-08-24 13:19:50 +02:00
Max Kellermann
d16fb79708 event/TimeoutMonitor: reset "active" flag before invoking OnTimeout()
The IsActive() method returned true even if the timer was not active,
after it completed once.  This broke the state file timer, and the
state file was not saved periodically.
2014-08-24 13:13:12 +02:00
Thomas Klausner
c38f29ce56 system/ByteOrder: <endian.h> is a non-standard header that only Linux provides. 2014-08-23 14:27:44 +02:00
Max Kellermann
78abcd7df7 decoer/dsdiff: fix endless loop on malformed file
Same bug as in the previous commit.
2014-08-21 12:48:03 +02:00
Max Kellermann
23dce21647 decoer/dsf: fix endless loop on malformed file
When the data chunk size is not a multiple of the frame size, the last
partial frame lead to an endless loop.  We fix this by checking
chunk_sze>=frame instead of chunk_sze>0.  This way, the partial frame
is simply skipped.
2014-08-21 12:37:22 +02:00
François Revol
40280fa6cf util: Fix header for strcasecmp
According to POSIX and both OSX and Linux manpages,
strcasecmp comes from strings.h, not string.h.

Most OSes also have them available in string.h,
but we just fixed the headers on Haiku and it now
only provides them in strings.h.

We might want to fall back to string.h for other
OSes though...

cf.
http://pubs.opengroup.org/onlinepubs/009695399/functions/strcasecmp.html
http://linux.die.net/man/3/strcasecmp
https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/strcasecmp.3.html
2014-08-16 06:51:13 +02:00
Max Kellermann
fe9299ceff decoder/ffmpeg: use avcodec_descriptor_get() to determine codec name
In version 11, both ffmpeg and libav deprecate
AVCodecContext::codec_name.  The function avcodec_descriptor_get() has
been introduced long ago.
2014-08-13 18:40:39 +02:00
Max Kellermann
c3f111a56c event/BufferedSocket: fix inversed buffer check
This was broken by commit 84d20d9e, which deleted the "!" from the
check.
2014-08-07 16:03:44 +02:00
François Revol
250318329f Makefile.am: fix dependencies for win32
It happened to me when doing the Haiku port, src/mpd failed to
be relinked properly when editing source files, and likely also
happens on win32, although I didn't try this change.

When building for windows, src_mpd_DEPENDENCIES is overriden.

Automake then disables the default version which contains all
the static libraries. In Makefile.in:
@HAVE_WINDOWS_FALSE@src_mpd_DEPENDENCIES = libmpd.a \

Instead we use EXTRA_src_mpd_DEPENDENCIES which is meant for this.
2014-08-02 08:48:44 +02:00
Max Kellermann
14c538c9c7 Win32Main: move to win32/ 2014-08-02 08:48:30 +02:00
Max Kellermann
abe4c57663 configure.ac: prepare for 0.18.13 2014-08-02 08:45:44 +02:00
Max Kellermann
a3f3c7ba24 release v0.18.12 2014-07-30 10:30:17 +02:00
Max Kellermann
94efeb2845 decoder/dsdiff: simplify dsdlib_skip() call 2014-07-12 20:51:00 +02:00
Max Kellermann
a73834436f decoder/dsdiff: simplify loop condition, merge branches 2014-07-12 20:46:24 +02:00
Max Kellermann
85f4aeca05 decoder/dsdiff: ignore garbage null byte at end of file
Failure to read another chunk header is not fatal.  Continue to read
metadata.
2014-07-12 20:41:26 +02:00
Max Kellermann
7db84a961a decoder/dsdiff: fix metadata parser bug (uninitialized variables) 2014-07-12 20:41:26 +02:00
Max Kellermann
a960e2ef48 decoder/faad: estimate song duration for remote files
Previously, MPD tried to slurp the whole song file, count the number
of frames and calculate the song duration from that.  That however is
extremely expensive for remote files, and will delay playback for a
long time.  Workaround: check only the first 128 frames and try to
extrapolate from here.  Fixes Mantis ticket 0004035.
2014-07-12 00:37:00 +02:00
Max Kellermann
4fe272a7fb DecoderBuffer: add method _available() 2014-07-12 00:35:32 +02:00
Max Kellermann
a7d9f248ea DecoderBuffer: add method _get_stream() 2014-07-12 00:23:22 +02:00
Max Kellermann
06aa689383 decoder/faad: bail out early if sample rate is invalid 2014-07-12 00:23:11 +02:00
Max Kellermann
835b0c44cd decoder/faad: use adts_check_frame() in faad_song_duration()
Eliminate more duplicate code.
2014-07-12 00:18:02 +02:00
Max Kellermann
54b6f8a4ae decoder/faad: test "seekable" after ADTS frame check
Don't bother to check for ADIF just because the stream is not
seekable.
2014-07-12 00:17:51 +02:00
Max Kellermann
18787ebe8f decoder/faad: move code to faad_decoder_new()
Merge some duplicate code.
2014-07-12 00:17:43 +02:00
Max Kellermann
47e8fcf37e decoder/faad: remove unnecessary read
Eliminate some overhead when the caller doesn't need the buffer.
2014-07-12 00:17:30 +02:00
Max Kellermann
5958b78459 DecoderBuffer: add "pure" attributes 2014-07-12 00:16:41 +02:00
Max Kellermann
9d9697b366 DecoderBuffer: add method _clear() 2014-07-12 00:15:35 +02:00
Max Kellermann
6585e18571 decoder/faad: check sample_rate, not frames_per_second
Checking the integer is faster, easier and more reliable.
2014-07-11 23:12:08 +02:00
Max Kellermann
6f1b4292f0 decoder/faad: make variables more local 2014-07-11 22:52:31 +02:00
Max Kellermann
ef9ef03b1f decoder/faad: use MAX_CHANNELS
.. instead of declaring a new constant.
2014-07-11 22:40:28 +02:00
Max Kellermann
ecb67a1ed1 decoder/sndfile: use decoder_read_full()
Replaces the loop in sndfile_vio_read(), eliminating duplicate and
fragile code.
2014-07-11 21:18:44 +02:00
Max Kellermann
0ef843f138 decoder/sndfile: use decoder_read()
.. instead of InputStream::LockRead(). The former is cancellable.
2014-07-11 21:18:44 +02:00
Max Kellermann
eb79d83051 decoder/sndfile: log seek errors 2014-07-11 21:18:44 +02:00
Max Kellermann
ca1a11493d decoder/audiofile: log seek errors 2014-07-11 21:18:44 +02:00
Max Kellermann
69bb086ba5 decoder/audiofile: fix typo in comment 2014-07-11 21:18:44 +02:00
Max Kellermann
11a5ee821b PlaylistEdit: postpone UpdateQueuedSong() when adding multiple songs
Implement a "bulk" edit mode that postpones both UpdateQueuedSong()
and OnModified().  This way, the playlist version gets incremented
only once.  More importantly: when adding multiple songs to a queue
that consists of only one song, the first song that got added will
always be played next.  By postponing this choice, all newly added
songs get a chance to become the next song.  Fixes the second (and
last) part of Mantis ticket 0004005.
2014-07-11 20:22:35 +02:00
Max Kellermann
a8a85143f6 QueueCommands: make "result" more local 2014-07-11 20:22:35 +02:00
Max Kellermann
e2cc328eef Playlist: randomize next song when enabling "random" mode while not playing
Don't restore the current song after shufflung when MPD is stopped
(but still remembers the current song internally).  Fixes the first
part of Mantis ticket 0004005.
2014-07-11 19:41:39 +02:00
Max Kellermann
344d10a8e3 PlaylistControl: update code comment 2014-07-11 19:29:25 +02:00
Joff
09384df32c decoder/dsd: use decoder_read_full() where appropriate
Addresses Mantis ticket 0004015.

[mk: use decoder_read_full() only when needed, and a few formal
changes]
2014-07-09 19:18:36 +02:00
Max Kellermann
20538516b9 decoder/audiofile: use decoder_read_full()
Works around WAV stream playback bug, because libaudiofile does not
like partial reads (Mantis 0004028).
2014-07-09 19:05:20 +02:00
Max Kellermann
0759421d11 DecoderAPI: add function decoder_read_full()
Move code from the "mad" plugin.
2014-07-09 19:03:58 +02:00
Max Kellermann
bf7417981f DecoderAPI: add function decoder_skip()
Move code from the "mad" plugin.
2014-07-09 19:03:31 +02:00
Max Kellermann
dba41e2e4a test: merge duplicate code to FakeDecoderAPI.cxx 2014-07-09 19:01:38 +02:00
Max Kellermann
bc6472bb9e decoder/audiofile: use decoder_read()
.. instead of InputStream::LockRead(). The former is cancellable.
2014-07-09 18:57:50 +02:00
Gustavo Zacarias
d4bd947bf5 playlist/PlsPlaylistPlugin: fix build failure due to missing stdio.h include
Signed-off-by: Gustavo Zacarias <gustavo@zacarias.com.ar>
2014-07-09 17:41:31 +02:00
Gustavo Zacarias
d8e8eabf60 output/HttpdClient: fix build failure due to missing stdio.h include
Signed-off-by: Gustavo Zacarias <gustavo@zacarias.com.ar>
2014-07-09 17:41:31 +02:00
Gustavo Zacarias
a70443af31 decoder/OpusDecoderPlugin: fix build failure due to missing stdio.h include
Signed-off-by: Gustavo Zacarias <gustavo@zacarias.com.ar>
2014-07-09 17:41:31 +02:00
Gustavo Zacarias
3f221e2edb decoder/AudiofileDecoderPlugin: fix build failure due to missing stdio.h include
Signed-off-by: Gustavo Zacarias <gustavo@zacarias.com.ar>
2014-07-09 17:41:31 +02:00
Max Kellermann
848ed14788 db/proxy: fall back to recursive walk on old libmpdclient/MPD
Error message was 'too few arguments for "find"' because the "base"
constraint was not supported, and no other constraints remained.
2014-06-23 09:18:11 +02:00
Max Kellermann
4c8a5dfb05 db/proxy: use mpd_song_get_{start,end}() only with libmpdclient >= 2.3 2014-06-23 09:17:35 +02:00
Max Kellermann
4f61ba766d configure.ac: prepare for 0.18.12 2014-06-23 09:14:35 +02:00
Max Kellermann
8bfdb4ed0c release v0.18.11 2014-05-12 18:20:26 +02:00
Max Kellermann
70bd35abe2 decoder/OggUtil: allow skipping up to 32 kB after seek
Fixes missing song length on high-latency Opus files.

According to tests with 320 kbit/s opus files with 60ms packets, we
need to skip up to 29 kB.
2014-04-29 11:56:05 +02:00
Max Kellermann
0efb67b51e DeferredMonitor: fix race condition when using GLib event loop
Turns out the lock-free code using atomics was not thread-safe.  The
given callback could be invoked by GLib before the source_id attribute
was assigned.  This commit changes the DeferredMonitor class to use a
Mutex to block the event loop until source_id is assigned.  This bug
does not exist in the 0.19 branch because it does not use the GLib
main loop anymore.
2014-04-26 22:11:23 +02:00
Max Kellermann
54ebf2a699 configure.ac: prepare for 0.18.11 2014-04-26 22:08:08 +02:00
Max Kellermann
d0119548c1 release v0.18.10 2014-04-10 13:36:38 +02:00
Marcello Desantis
95ac6071b9 decoder/sndfile: work around libsndfile bug on partial read 2014-04-09 23:58:56 +02:00
Weng Xuetian
3a4e667078 PlaylistEdit: don't interrupt playback when current song gets deleted 2014-04-09 23:10:14 +02:00
Max Kellermann
ce18c36ed9 decoder/ffmpeg: handle unknown stream start time 2014-03-18 09:16:09 +01:00
Max Kellermann
8e39cf62e7 decoder/ffmpeg: pass AVSEEK_FLAG_ANY to av_seek_frame()
This corrects a major mistake from commit 724a59aa - there was one
small thing that commit was supposed to do, and it failed.
AV_TIME_BASE is not a seek flag.
2014-03-18 09:10:36 +01:00
Max Kellermann
a9e351e00d decoder/gme: fix memory leak in container_scan() 2014-03-06 13:12:39 +01:00
Max Kellermann
d65841a2db configure.ac: prepare for 0.18.10 2014-03-06 13:08:30 +01:00
Max Kellermann
2784d65618 release v0.18.9 2014-03-02 11:25:01 +01:00
Max Kellermann
47ea69233b output/alsa: remove the obsolete Raspberry Pi workaround
Has been superseded by the previous commit.
2014-03-02 11:22:04 +01:00
Max Kellermann
a884e37de1 output/alsa: call snd_pcm_prepare() after snd_pcm_drop()
Don't wait for an optimistic write to fail.  This is an improved
workaround for the infamous Raspberry Pi bug (see commit af991765).
It works much better and comes without the negative side effects.  The
old workaround is now obsolete.
2014-03-02 11:12:25 +01:00
Max Kellermann
0102a8665a event/SignalMonitor: fix build failure due to missing signal.h include 2014-03-02 10:21:31 +01:00
Max Kellermann
d34ae0850c AllCommands: "findadd" requires the "add" permission 2014-02-27 23:08:22 +01:00
Max Kellermann
6526de024a output/pulse: remove bogus g_free() call 2014-02-24 21:23:49 +01:00
Max Kellermann
5e1e92626c event/SignalMonitor: unblock signals after fork
Fixes hanging child process in the "pipe" output plugin.
2014-02-18 19:13:50 +01:00
Max Kellermann
7fee85c80a configure.ac: fix linker failure when libvorbis/libogg are static
Link libvorbisfile first, followed to libvorbis and finally libogg.
This order is necessary because libvorbisfile depends on libvorbis.
2014-02-18 18:39:19 +01:00
Max Kellermann
5d87a274a5 configure.ac: link the Vorbis encoder with libogg
Fixes another linker failure.  Similar to commit ea406875
2014-02-17 19:42:38 +01:00
Max Kellermann
57e862712a configure.ac: prepare for 0.18.9 2014-02-09 22:58:14 +01:00
54 changed files with 883 additions and 450 deletions

2
.gitignore vendored
View File

@@ -40,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

View File

@@ -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
@@ -1218,6 +1219,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 \
@@ -1271,6 +1273,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 \
@@ -1630,4 +1633,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

52
NEWS
View File

@@ -1,3 +1,55 @@
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) ver 0.18.8 (2014/02/07)
* decoder * decoder
- ffmpeg: support libav v10_alpha1 - ffmpeg: support libav v10_alpha1

View File

@@ -1,6 +1,6 @@
AC_PREREQ(2.60) AC_PREREQ(2.60)
AC_INIT(mpd, 0.18.8, mpd-devel@musicpd.org) AC_INIT(mpd, 0.18.13, mpd-devel@musicpd.org)
VERSION_MAJOR=0 VERSION_MAJOR=0
VERSION_MINOR=18 VERSION_MINOR=18
@@ -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"
@@ -1023,7 +1023,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])
@@ -1139,7 +1139,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 vorbis], 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

View File

@@ -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>

41
src/BulkEdit.hxx Normal file
View 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

View File

@@ -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
{ {

View File

@@ -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;
}; };

View File

@@ -292,6 +292,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)
{ {

View File

@@ -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

View File

@@ -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)
{ {

View File

@@ -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

View File

@@ -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();

View File

@@ -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);

View File

@@ -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);
} }

View File

@@ -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)

View File

@@ -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
{ {

View File

@@ -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.

View File

@@ -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 },

View File

@@ -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)

View File

@@ -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,

View File

@@ -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)

View File

@@ -398,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));
@@ -561,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,
@@ -572,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);

View File

@@ -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,11 +103,14 @@ 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)) {
LogError(error, "Seek failed");
return is.GetOffset(); return is.GetOffset();
} else { } else {
return -1; return -1;
@@ -103,10 +118,10 @@ audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative)
} }
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) {

View File

@@ -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);

View File

@@ -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);

View File

@@ -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,16 +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));
}
}
/* done processing chunk headers, process tags if any */ /* done processing chunk headers, process tags if any */
#ifdef HAVE_ID3TAG #ifdef HAVE_ID3TAG
@@ -328,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;
@@ -381,7 +377,7 @@ 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 */
size_t now_size = buffer_size; size_t now_size = buffer_size;
@@ -391,10 +387,10 @@ dsdiff_decode_chunk(Decoder &decoder, InputStream &is,
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)

View File

@@ -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,7 +238,7 @@ 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 */
size_t now_size = buffer_size; size_t now_size = buffer_size;
@@ -247,10 +248,10 @@ dsf_decode_chunk(Decoder &decoder, InputStream &is,
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)

View File

@@ -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,21 @@ faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
return false; return false;
} }
nbytes = NeAACDecInit(decoder, uint8_t channels;
/* deconst hack, libfaad requires this */ uint32_t sample_rate;
const_cast<unsigned char *>(data), #ifdef HAVE_FAAD_LONG
length, /* neaacdec.h declares all arguments as "unsigned long", but
sample_rate_p, &channels); 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
long nbytes = NeAACDecInit(decoder,
/* deconst hack, libfaad requires this */
const_cast<unsigned char *>(data),
length,
sample_rate_p, &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 +330,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 +363,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,8 +392,8 @@ 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); decoder_buffer_free(buffer);
@@ -404,21 +407,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,
@@ -470,7 +472,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;

View File

@@ -197,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,
@@ -263,7 +286,7 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is,
{ {
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 - stream->start_time, time_from_ffmpeg(packet->pts - start_time_fallback(*stream),
stream->time_base)); stream->time_base));
AVPacket packet2 = *packet; AVPacket packet2 = *packet;
@@ -410,9 +433,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);
@@ -493,10 +525,10 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
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) +
av_stream->start_time; 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);

View File

@@ -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) {

View File

@@ -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
} }

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();
} }

View File

@@ -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

View File

@@ -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:

View File

@@ -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);

View File

@@ -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

View File

@@ -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);
} }

View File

@@ -30,6 +30,7 @@
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include <stdio.h>
HttpdClient::~HttpdClient() HttpdClient::~HttpdClient()
{ {

View File

@@ -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) {

View File

@@ -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;

View File

@@ -31,6 +31,7 @@
#include <glib.h> #include <glib.h>
#include <string> #include <string>
#include <stdio.h>
static constexpr Domain pls_domain("pls"); static constexpr Domain pls_domain("pls");

View File

@@ -40,6 +40,16 @@
/* well-known big-endian */ /* well-known big-endian */
# define IS_LITTLE_ENDIAN false # define IS_LITTLE_ENDIAN false
# define IS_BIG_ENDIAN true # define IS_BIG_ENDIAN true
#elif defined(__APPLE__)
/* compile-time check for MacOS */
# include <machine/endian.h>
# if BYTE_ORDER == LITTLE_ENDIAN
# define IS_LITTLE_ENDIAN true
# define IS_BIG_ENDIAN false
# else
# define IS_LITTLE_ENDIAN false
# define IS_BIG_ENDIAN true
# endif
#else #else
/* generic compile-time check */ /* generic compile-time check */
# include <endian.h> # include <endian.h>

View File

@@ -33,7 +33,7 @@
#include "Compiler.h" #include "Compiler.h"
#include <assert.h> #include <assert.h>
#include <string.h> #include <strings.h>
/** /**
* Determine whether two strings are equal, ignoring case for ASCII * Determine whether two strings are equal, ignoring case for ASCII

View File

Before

Width:  |  Height:  |  Size: 345 KiB

After

Width:  |  Height:  |  Size: 345 KiB

View File

@@ -3,7 +3,7 @@
#define VERSION_NUMBER @VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@ #define VERSION_NUMBER @VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@
#define VERSION_NUMBER_STR "@VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@" #define VERSION_NUMBER_STR "@VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@"
MPD_ICON ICON "@top_srcdir@/src/win/mpd.ico" MPD_ICON ICON "@top_srcdir@/src/win32/mpd.ico"
1 VERSIONINFO 1 VERSIONINFO
FILETYPE VFT_APP FILETYPE VFT_APP

144
test/FakeDecoderAPI.cxx Normal file
View File

@@ -0,0 +1,144 @@
/*
* Copyright (C) 2003-2012 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "DecoderAPI.hxx"
#include "InputStream.hxx"
#include "util/Error.hxx"
#include "Compiler.h"
#include <glib.h>
#include <unistd.h>
void
decoder_initialized(gcc_unused Decoder &decoder,
gcc_unused const AudioFormat audio_format,
gcc_unused bool seekable,
gcc_unused float total_time)
{
}
DecoderCommand
decoder_get_command(gcc_unused Decoder &decoder)
{
return DecoderCommand::NONE;
}
void
decoder_command_finished(gcc_unused Decoder &decoder)
{
}
double
decoder_seek_where(gcc_unused Decoder &decoder)
{
return 1.0;
}
void
decoder_seek_error(gcc_unused Decoder &decoder)
{
}
size_t
decoder_read(gcc_unused Decoder *decoder,
InputStream &is,
void *buffer, size_t length)
{
return is.LockRead(buffer, length, IgnoreError());
}
bool
decoder_read_full(Decoder *decoder, InputStream &is,
void *_buffer, size_t size)
{
uint8_t *buffer = (uint8_t *)_buffer;
while (size > 0) {
size_t nbytes = decoder_read(decoder, is, buffer, size);
if (nbytes == 0)
return false;
buffer += nbytes;
size -= nbytes;
}
return true;
}
bool
decoder_skip(Decoder *decoder, InputStream &is, size_t size)
{
while (size > 0) {
char buffer[1024];
size_t nbytes = decoder_read(decoder, is, buffer,
std::min(sizeof(buffer), size));
if (nbytes == 0)
return false;
size -= nbytes;
}
return true;
}
void
decoder_timestamp(gcc_unused Decoder &decoder,
gcc_unused double t)
{
}
DecoderCommand
decoder_data(gcc_unused Decoder &decoder,
gcc_unused InputStream *is,
const void *data, size_t datalen,
gcc_unused uint16_t kbit_rate)
{
gcc_unused ssize_t nbytes = write(1, data, datalen);
return DecoderCommand::NONE;
}
DecoderCommand
decoder_tag(gcc_unused Decoder &decoder,
gcc_unused InputStream *is,
gcc_unused Tag &&tag)
{
return DecoderCommand::NONE;
}
void
decoder_replay_gain(gcc_unused Decoder &decoder,
const ReplayGainInfo *rgi)
{
const ReplayGainTuple *tuple = &rgi->tuples[REPLAY_GAIN_ALBUM];
if (tuple->IsDefined())
g_printerr("replay_gain[album]: gain=%f peak=%f\n",
tuple->gain, tuple->peak);
tuple = &rgi->tuples[REPLAY_GAIN_TRACK];
if (tuple->IsDefined())
g_printerr("replay_gain[track]: gain=%f peak=%f\n",
tuple->gain, tuple->peak);
}
void
decoder_mixramp(gcc_unused Decoder &decoder, gcc_unused MixRampInfo &&mix_ramp)
{
}

View File

@@ -24,7 +24,6 @@
#include "Directory.hxx" #include "Directory.hxx"
#include "InputStream.hxx" #include "InputStream.hxx"
#include "ConfigGlobal.hxx" #include "ConfigGlobal.hxx"
#include "DecoderAPI.hxx"
#include "DecoderList.hxx" #include "DecoderList.hxx"
#include "InputInit.hxx" #include "InputInit.hxx"
#include "IOThread.hxx" #include "IOThread.hxx"
@@ -53,88 +52,6 @@ my_log_func(const gchar *log_domain, gcc_unused GLogLevelFlags log_level,
g_printerr("%s\n", message); g_printerr("%s\n", message);
} }
void
decoder_initialized(gcc_unused Decoder &decoder,
gcc_unused const AudioFormat audio_format,
gcc_unused bool seekable,
gcc_unused float total_time)
{
}
DecoderCommand
decoder_get_command(gcc_unused Decoder &decoder)
{
return DecoderCommand::NONE;
}
void
decoder_command_finished(gcc_unused Decoder &decoder)
{
}
double
decoder_seek_where(gcc_unused Decoder &decoder)
{
return 1.0;
}
void
decoder_seek_error(gcc_unused Decoder &decoder)
{
}
size_t
decoder_read(gcc_unused Decoder *decoder,
InputStream &is,
void *buffer, size_t length)
{
return is.LockRead(buffer, length, IgnoreError());
}
void
decoder_timestamp(gcc_unused Decoder &decoder,
gcc_unused double t)
{
}
DecoderCommand
decoder_data(gcc_unused Decoder &decoder,
gcc_unused InputStream *is,
const void *data, size_t datalen,
gcc_unused uint16_t kbit_rate)
{
gcc_unused ssize_t nbytes = write(1, data, datalen);
return DecoderCommand::NONE;
}
DecoderCommand
decoder_tag(gcc_unused Decoder &decoder,
gcc_unused InputStream *is,
gcc_unused Tag &&tag)
{
return DecoderCommand::NONE;
}
void
decoder_replay_gain(gcc_unused Decoder &decoder,
const ReplayGainInfo *rgi)
{
const ReplayGainTuple *tuple = &rgi->tuples[REPLAY_GAIN_ALBUM];
if (tuple->IsDefined())
g_printerr("replay_gain[album]: gain=%f peak=%f\n",
tuple->gain, tuple->peak);
tuple = &rgi->tuples[REPLAY_GAIN_TRACK];
if (tuple->IsDefined())
g_printerr("replay_gain[track]: gain=%f peak=%f\n",
tuple->gain, tuple->peak);
}
void
decoder_mixramp(gcc_unused Decoder &decoder, gcc_unused MixRampInfo &&mix_ramp)
{
}
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
const char *uri; const char *uri;

View File

@@ -20,7 +20,7 @@
#include "config.h" #include "config.h"
#include "IOThread.hxx" #include "IOThread.hxx"
#include "DecoderList.hxx" #include "DecoderList.hxx"
#include "DecoderAPI.hxx" #include "DecoderPlugin.hxx"
#include "InputInit.hxx" #include "InputInit.hxx"
#include "InputStream.hxx" #include "InputStream.hxx"
#include "AudioFormat.hxx" #include "AudioFormat.hxx"
@@ -42,79 +42,6 @@
#include <locale.h> #include <locale.h>
#endif #endif
void
decoder_initialized(gcc_unused Decoder &decoder,
gcc_unused const AudioFormat audio_format,
gcc_unused bool seekable,
gcc_unused float total_time)
{
}
DecoderCommand
decoder_get_command(gcc_unused Decoder &decoder)
{
return DecoderCommand::NONE;
}
void
decoder_command_finished(gcc_unused Decoder &decoder)
{
}
double
decoder_seek_where(gcc_unused Decoder &decoder)
{
return 1.0;
}
void
decoder_seek_error(gcc_unused Decoder &decoder)
{
}
size_t
decoder_read(gcc_unused Decoder *decoder,
InputStream &is,
void *buffer, size_t length)
{
return is.LockRead(buffer, length, IgnoreError());
}
void
decoder_timestamp(gcc_unused Decoder &decoder,
gcc_unused double t)
{
}
DecoderCommand
decoder_data(gcc_unused Decoder &decoder,
gcc_unused InputStream *is,
const void *data, size_t datalen,
gcc_unused uint16_t kbit_rate)
{
gcc_unused ssize_t nbytes = write(1, data, datalen);
return DecoderCommand::NONE;
}
DecoderCommand
decoder_tag(gcc_unused Decoder &decoder,
gcc_unused InputStream *is,
gcc_unused Tag &&tag)
{
return DecoderCommand::NONE;
}
void
decoder_replay_gain(gcc_unused Decoder &decoder,
gcc_unused const ReplayGainInfo *replay_gain_info)
{
}
void
decoder_mixramp(gcc_unused Decoder &decoder, gcc_unused MixRampInfo &&mix_ramp)
{
}
static bool empty = true; static bool empty = true;
static void static void

View File

@@ -101,6 +101,40 @@ decoder_read(gcc_unused Decoder *decoder,
return is.LockRead(buffer, length, IgnoreError()); return is.LockRead(buffer, length, IgnoreError());
} }
bool
decoder_read_full(Decoder *decoder, InputStream &is,
void *_buffer, size_t size)
{
uint8_t *buffer = (uint8_t *)_buffer;
while (size > 0) {
size_t nbytes = decoder_read(decoder, is, buffer, size);
if (nbytes == 0)
return false;
buffer += nbytes;
size -= nbytes;
}
return true;
}
bool
decoder_skip(Decoder *decoder, InputStream &is, size_t size)
{
while (size > 0) {
char buffer[1024];
size_t nbytes = decoder_read(decoder, is, buffer,
std::min(sizeof(buffer), size));
if (nbytes == 0)
return false;
size -= nbytes;
}
return true;
}
void void
decoder_timestamp(gcc_unused Decoder &decoder, decoder_timestamp(gcc_unused Decoder &decoder,
gcc_unused double t) gcc_unused double t)