Compare commits

...

58 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
48 changed files with 750 additions and 400 deletions

2
.gitignore vendored

@@ -40,7 +40,7 @@ tags
.#*
.stgit*
src/dsd2pcm/dsd2pcm
src/win/mpd_win32_rc.rc
src/win32/mpd_win32_rc.rc
doc/doxygen.conf
doc/protocol.html
doc/protocol

@@ -151,7 +151,7 @@ src_mpd_SOURCES = \
src/IOThread.cxx src/IOThread.hxx \
src/Main.cxx src/Main.hxx \
src/Instance.cxx src/Instance.hxx \
src/Win32Main.cxx \
src/win32/Win32Main.cxx \
src/GlobalEvents.cxx src/GlobalEvents.hxx \
src/Daemon.cxx src/Daemon.hxx \
src/AudioCompress/compress.c \
@@ -181,6 +181,7 @@ src_mpd_SOURCES = \
src/PlaylistInfo.hxx \
src/PlaylistDatabase.cxx src/PlaylistDatabase.hxx \
src/PlaylistUpdate.cxx \
src/BulkEdit.hxx \
src/IdTable.hxx \
src/Queue.cxx src/Queue.hxx \
src/QueuePrint.cxx src/QueuePrint.hxx \
@@ -210,14 +211,14 @@ src_mpd_SOURCES = \
# Windows resource file
#
src/win/mpd_win32_rc.$(OBJEXT): src/win/mpd_win32_rc.rc
src/win32/mpd_win32_rc.$(OBJEXT): src/win32/mpd_win32_rc.rc
$(WINDRES) -i $< -o $@
if HAVE_WINDOWS
noinst_DATA = src/win/mpd_win32_rc.rc
noinst_DATA = src/win32/mpd_win32_rc.rc
src_mpd_DEPENDENCIES = src/win/mpd_win32_rc.$(OBJEXT)
src_mpd_LDFLAGS = -Wl,src/win/mpd_win32_rc.$(OBJEXT)
EXTRA_src_mpd_DEPENDENCIES = src/win32/mpd_win32_rc.$(OBJEXT)
src_mpd_LDFLAGS = -Wl,src/win32/mpd_win32_rc.$(OBJEXT)
endif
if ENABLE_DESPOTIFY
@@ -1218,6 +1219,7 @@ test_dump_playlist_LDADD = \
libpcm.a \
$(GLIB_LIBS)
test_dump_playlist_SOURCES = test/dump_playlist.cxx \
test/FakeDecoderAPI.cxx \
$(DECODER_SRC) \
src/Log.cxx \
src/IOThread.cxx \
@@ -1271,6 +1273,7 @@ test_read_tags_LDADD = \
libutil.a \
$(GLIB_LIBS)
test_read_tags_SOURCES = test/read_tags.cxx \
test/FakeDecoderAPI.cxx \
src/Log.cxx \
src/IOThread.cxx \
src/ReplayGainInfo.cxx \
@@ -1630,4 +1633,4 @@ EXTRA_DIST = $(doc_DATA) autogen.sh \
test/test_archive_zzip.sh \
$(wildcard scripts/*.sh) \
$(man_MANS) $(DOCBOOK_FILES) doc/mpdconf.example doc/doxygen.conf \
src/win/mpd_win32_rc.rc.in src/win/mpd.ico
src/win32/mpd_win32_rc.rc.in src/win32/mpd.ico

26
NEWS

@@ -1,3 +1,29 @@
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

@@ -1,6 +1,6 @@
AC_PREREQ(2.60)
AC_INIT(mpd, 0.18.11, mpd-devel@musicpd.org)
AC_INIT(mpd, 0.18.13, mpd-devel@musicpd.org)
VERSION_MAJOR=0
VERSION_MINOR=18
@@ -70,7 +70,7 @@ host_is_darwin=no
case "$host_os" in
mingw32* | windows*)
AC_CONFIG_FILES([
src/win/mpd_win32_rc.rc
src/win32/mpd_win32_rc.rc
])
AC_CHECK_TOOL(WINDRES, windres)
AM_CPPFLAGS="$AM_CPPFLAGS -DWIN32_LEAN_AND_MEAN"

@@ -576,7 +576,12 @@
</listitem>
<listitem>
<para>
<varname>songs</varname>: number of albums
<varname>albums</varname>: number of albums
</para>
</listitem>
<listitem>
<para>
<varname>songs</varname>: number of songs
</para>
</listitem>
<listitem>

41
src/BulkEdit.hxx Normal file

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_BULK_EDIT_HXX
#define MPD_BULK_EDIT_HXX
#include "Partition.hxx"
/**
* Begin a "bulk edit" and commit it automatically.
*/
class ScopeBulkEdit {
Partition &partition;
public:
ScopeBulkEdit(Partition &_partition):partition(_partition) {
partition.playlist.BeginBulk();
}
~ScopeBulkEdit() {
partition.playlist.CommitBulk(partition.pc);
}
};
#endif

@@ -30,6 +30,18 @@ DatabaseSelection::DatabaseSelection(const char *_uri, bool _recursive,
uri = filter->GetBase();
}
bool
DatabaseSelection::IsEmpty() const
{
return uri.empty() && (filter == nullptr || filter->IsEmpty());
}
bool
DatabaseSelection::HasOtherThanBase() const
{
return filter != nullptr && filter->HasOtherThanBase();
}
bool
DatabaseSelection::Match(const Song &song) const
{

@@ -44,6 +44,15 @@ struct DatabaseSelection {
DatabaseSelection(const char *_uri, bool _recursive,
const SongFilter *_filter=nullptr);
gcc_pure
bool IsEmpty() const;
/**
* Does this selection contain constraints other than "base"?
*/
gcc_pure
bool HasOtherThanBase() const;
gcc_pure
bool Match(const Song &song) const;
};

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

@@ -112,6 +112,25 @@ decoder_read(Decoder &decoder, InputStream &is,
return decoder_read(&decoder, is, buffer, length);
}
/**
* Blocking read from the input stream. Attempts to fill the buffer
* completely; there is no partial result.
*
* @return true on success, false on error or command or not enough
* data
*/
bool
decoder_read_full(Decoder *decoder, InputStream &is,
void *buffer, size_t size);
/**
* Skip data on the #InputStream.
*
* @return true on success, false on error or command
*/
bool
decoder_skip(Decoder *decoder, InputStream &is, size_t size);
/**
* Sets the time stamp for the next data chunk [seconds]. The MPD
* core automatically counts it up, and a decoder plugin only needs to

@@ -70,6 +70,12 @@ decoder_buffer_free(DecoderBuffer *buffer)
g_free(buffer);
}
const InputStream &
decoder_buffer_get_stream(const DecoderBuffer *buffer)
{
return *buffer->is;
}
bool
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;
}
void
decoder_buffer_clear(DecoderBuffer *buffer)
{
buffer->length = buffer->consumed = 0;
}
static void
decoder_buffer_shift(DecoderBuffer *buffer)
{
@@ -118,6 +130,12 @@ decoder_buffer_fill(DecoderBuffer *buffer)
return true;
}
size_t
decoder_buffer_available(const DecoderBuffer *buffer)
{
return buffer->length - buffer->consumed;;
}
const void *
decoder_buffer_read(const DecoderBuffer *buffer, size_t *length_r)
{

@@ -20,6 +20,8 @@
#ifndef MPD_DECODER_BUFFER_HXX
#define MPD_DECODER_BUFFER_HXX
#include "Compiler.h"
#include <stddef.h>
/**
@@ -50,12 +52,21 @@ decoder_buffer_new(Decoder *decoder, InputStream &is,
void
decoder_buffer_free(DecoderBuffer *buffer);
gcc_pure
const InputStream &
decoder_buffer_get_stream(const DecoderBuffer *buffer);
gcc_pure
bool
decoder_buffer_is_empty(const DecoderBuffer *buffer);
gcc_pure
bool
decoder_buffer_is_full(const DecoderBuffer *buffer);
void
decoder_buffer_clear(DecoderBuffer *buffer);
/**
* Read data from the input_stream and append it to the buffer.
*
@@ -66,6 +77,13 @@ decoder_buffer_is_full(const DecoderBuffer *buffer);
bool
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
* have to call decoder_buffer_consume() to do that. The returned

@@ -103,6 +103,12 @@ playlist::UpdateQueuedSong(PlayerControl &pc, const Song *prev)
if (!playing)
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((queued < 0) == (prev == nullptr));
@@ -294,7 +300,9 @@ playlist::SetRandom(PlayerControl &pc, bool status)
if (queue.random) {
/* shuffle the queue order, but preserve current */
const int current_position = GetCurrentPosition();
const int current_position = playing
? GetCurrentPosition()
: -1;
queue.ShuffleOrder();

@@ -45,6 +45,18 @@ struct playlist {
*/
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 exceeds the length of the playlist, MPD gives up,
@@ -69,7 +81,9 @@ struct playlist {
int queued;
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() {
@@ -126,6 +140,9 @@ protected:
void UpdateQueuedSong(PlayerControl &pc, const Song *prev);
public:
void BeginBulk();
void CommitBulk(PlayerControl &pc);
void Clear(PlayerControl &pc);
/**
@@ -217,6 +234,10 @@ public:
void PlayPrevious(PlayerControl &pc);
PlaylistResult SeekSongOrder(PlayerControl &pc,
unsigned song_order,
float seek_time);
PlaylistResult SeekSongPosition(PlayerControl &pc,
unsigned song_position,
float seek_time);

@@ -153,7 +153,7 @@ playlist::PlayNext(PlayerControl &pc)
queue.ShuffleOrder();
/* note that current and queued are
now invalid, but playlist_play_order() will
now invalid, but PlayOrder() will
discard them anyway */
}
@@ -190,17 +190,12 @@ playlist::PlayPrevious(PlayerControl &pc)
}
PlaylistResult
playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time)
playlist::SeekSongOrder(PlayerControl &pc, unsigned i, float seek_time)
{
if (!queue.IsValidPosition(song))
return PlaylistResult::BAD_RANGE;
assert(queue.IsValidOrder(i));
const Song *queued_song = GetQueuedSong();
unsigned i = queue.random
? queue.PositionToOrder(song)
: song;
pc.ClearError();
stop_on_error = true;
error_count = 0;
@@ -228,6 +223,19 @@ playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time)
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
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)
seek_time = 0;
return SeekSongPosition(pc, current, seek_time);
return SeekSongOrder(pc, current, seek_time);
}

@@ -40,6 +40,12 @@
void
playlist::OnModified()
{
if (bulk_edit) {
/* postponed to CommitBulk() */
bulk_modified = true;
return;
}
queue.IncrementVersion();
idle_add(IDLE_PLAYLIST);
@@ -56,6 +62,35 @@ playlist::Clear(PlayerControl &pc)
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
playlist::AppendFile(PlayerControl &pc,
const char *path_utf8, unsigned *added_id)

@@ -203,6 +203,16 @@ SongFilter::Match(const Song &song) const
return true;
}
bool
SongFilter::HasOtherThanBase() const
{
for (const auto &i : items)
if (i.GetTag() != LOCATE_TAG_BASE_TYPE)
return true;
return false;
}
std::string
SongFilter::GetBase() const
{

@@ -109,6 +109,11 @@ public:
return items;
}
gcc_pure
bool IsEmpty() const {
return items.empty();
}
/**
* Is there at least one item with "fold case" enabled?
*/
@@ -121,6 +126,12 @@ public:
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
* empty string.

@@ -30,6 +30,7 @@
#include "util/Error.hxx"
#include "SongFilter.hxx"
#include "protocol/Result.hxx"
#include "BulkEdit.hxx"
#include <assert.h>
#include <string.h>
@@ -92,6 +93,8 @@ handle_match_add(Client &client, int argc, char *argv[], bool fold_case)
return CommandResult::ERROR;
}
const ScopeBulkEdit bulk_edit(client.partition);
const DatabaseSelection selection("", true, &filter);
Error error;
return AddFromDatabase(client.partition, selection, error)

@@ -26,6 +26,7 @@
#include "PlaylistFile.hxx"
#include "PlaylistVector.hxx"
#include "PlaylistQueue.hxx"
#include "BulkEdit.hxx"
#include "TimePrint.hxx"
#include "Client.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]))
return CommandResult::ERROR;
const ScopeBulkEdit bulk_edit(client.partition);
const PlaylistResult result =
playlist_open_into_queue(argv[1],
start_index, end_index,

@@ -28,6 +28,7 @@
#include "ClientFile.hxx"
#include "Client.hxx"
#include "Partition.hxx"
#include "BulkEdit.hxx"
#include "protocol/ArgParser.hxx"
#include "protocol/Result.hxx"
#include "ls.hxx"
@@ -43,7 +44,6 @@ CommandResult
handle_add(Client &client, gcc_unused int argc, char *argv[])
{
char *uri = argv[1];
PlaylistResult result;
if (memcmp(uri, "file:///", 8) == 0) {
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))
return print_error(client, error);
result = client.partition.AppendFile(path_utf8);
auto result = client.partition.AppendFile(path_utf8);
return print_playlist_result(client, result);
}
@@ -70,10 +70,12 @@ handle_add(Client &client, gcc_unused int argc, char *argv[])
return CommandResult::ERROR;
}
result = client.partition.AppendURI(uri);
auto result = client.partition.AppendURI(uri);
return print_playlist_result(client, result);
}
const ScopeBulkEdit bulk_edit(client.partition);
const DatabaseSelection selection(uri, true);
Error error;
return AddFromDatabase(client.partition, selection, error)

@@ -398,8 +398,13 @@ Convert(const struct mpd_song *song)
Song *s = Song::NewDetached(mpd_song_get_uri(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->end_ms = mpd_song_get_end(song) * 1000;
#else
s->start_ms = s->end_ms = 0;
#endif
TagBuilder tag;
tag.SetTime(mpd_song_get_duration(song));
@@ -561,6 +566,23 @@ SearchSongs(struct mpd_connection *connection,
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
ProxyDatabase::Visit(const DatabaseSelection &selection,
VisitDirectory visit_directory,
@@ -572,7 +594,10 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
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
certain conditions */
return ::SearchSongs(connection, selection, visit_song, error);

@@ -31,12 +31,27 @@
#include <af_vfs.h>
#include <assert.h>
#include <stdio.h>
/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */
#define CHUNK_SIZE 1020
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)
{
int total_time;
@@ -54,29 +69,26 @@ static int audiofile_get_duration(const char *file)
static ssize_t
audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length)
{
InputStream &is = *(InputStream *)vfile->closure;
AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
Error error;
size_t nbytes = is.LockRead(data, length, error);
if (nbytes == 0 && error.IsDefined()) {
LogError(error);
return -1;
}
return nbytes;
return afis.Read(data, length);
}
static AFfileoffset
audiofile_file_length(AFvirtualfile *vfile)
{
InputStream &is = *(InputStream *)vfile->closure;
AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
InputStream &is = afis.is;
return is.GetSize();
}
static AFfileoffset
audiofile_file_tell(AFvirtualfile *vfile)
{
InputStream &is = *(InputStream *)vfile->closure;
AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
InputStream &is = afis.is;
return is.GetOffset();
}
@@ -91,11 +103,14 @@ audiofile_file_destroy(AFvirtualfile *vfile)
static AFfileoffset
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);
Error error;
if (is.LockSeek(offset, whence, error)) {
LogError(error, "Seek failed");
return is.GetOffset();
} else {
return -1;
@@ -103,10 +118,10 @@ audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative)
}
static AFvirtualfile *
setup_virtual_fops(InputStream &stream)
setup_virtual_fops(AudioFileInputStream &afis)
{
AFvirtualfile *vf = new AFvirtualfile();
vf->closure = &stream;
vf->closure = &afis;
vf->write = nullptr;
vf->read = audiofile_file_read;
vf->length = audiofile_file_length;
@@ -173,7 +188,8 @@ audiofile_stream_decode(Decoder &decoder, InputStream &is)
return;
}
vf = setup_virtual_fops(is);
AudioFileInputStream afis{&decoder, is};
vf = setup_virtual_fops(afis);
af_fp = afOpenVirtualFile(vf, "r", nullptr);
if (af_fp == AF_NULL_FILEHANDLE) {

@@ -49,14 +49,6 @@ DsdId::Equals(const char *s) const
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.
*/
@@ -149,7 +141,7 @@ dsdlib_tag_id3(InputStream &is,
id3_byte_t *dsdid3data;
dsdid3data = dsdid3;
if (!dsdlib_read(nullptr, is, dsdid3data, count))
if (!decoder_read_full(nullptr, is, dsdid3data, count))
return;
id3_tag = id3_tag_parse(dsdid3data, count);

@@ -58,10 +58,6 @@ public:
}
};
bool
dsdlib_read(Decoder *decoder, InputStream &is,
void *data, size_t length);
bool
dsdlib_skip_to(Decoder *decoder, InputStream &is,
int64_t offset);

@@ -93,14 +93,14 @@ static bool
dsdiff_read_id(Decoder *decoder, InputStream &is,
DsdId *id)
{
return dsdlib_read(decoder, is, id, sizeof(*id));
return decoder_read_full(decoder, is, id, sizeof(*id));
}
static bool
dsdiff_read_chunk_header(Decoder *decoder, InputStream &is,
DsdiffChunkHeader *header)
{
return dsdlib_read(decoder, is, header, sizeof(*header));
return decoder_read_full(decoder, is, header, sizeof(*header));
}
static bool
@@ -112,8 +112,7 @@ dsdiff_read_payload(Decoder *decoder, InputStream &is,
if (size != (uint64_t)length)
return false;
size_t nbytes = decoder_read(decoder, is, data, length);
return nbytes == length;
return decoder_read_full(decoder, is, data, length);
}
/**
@@ -145,8 +144,8 @@ dsdiff_read_prop_snd(Decoder *decoder, InputStream &is,
} else if (header.id.Equals("CHNL")) {
uint16_t channels;
if (header.GetSize() < sizeof(channels) ||
!dsdlib_read(decoder, is,
&channels, sizeof(channels)) ||
!decoder_read_full(decoder, is,
&channels, sizeof(channels)) ||
!dsdlib_skip_to(decoder, is, chunk_end_offset))
return false;
@@ -154,8 +153,8 @@ dsdiff_read_prop_snd(Decoder *decoder, InputStream &is,
} else if (header.id.Equals("CMPR")) {
DsdId type;
if (header.GetSize() < sizeof(type) ||
!dsdlib_read(decoder, is,
&type, sizeof(type)) ||
!decoder_read_full(decoder, is,
&type, sizeof(type)) ||
!dsdlib_skip_to(decoder, is, chunk_end_offset))
return false;
@@ -208,7 +207,7 @@ dsdiff_handle_native_tag(InputStream &is,
struct dsdiff_native_tag metatag;
if (!dsdlib_read(nullptr, is, &metatag, sizeof(metatag)))
if (!decoder_read_full(nullptr, is, &metatag, sizeof(metatag)))
return;
uint32_t length = FromBE32(metatag.size);
@@ -221,7 +220,7 @@ dsdiff_handle_native_tag(InputStream &is,
char *label;
label = string;
if (!dsdlib_read(nullptr, is, label, (size_t)length))
if (!decoder_read_full(nullptr, is, label, (size_t)length))
return;
string[length] = '\0';
@@ -251,15 +250,17 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is,
if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
return false;
metadata->diar_offset = 0;
metadata->diti_offset = 0;
#ifdef HAVE_ID3TAG
metadata->id3_size = 0;
metadata->id3_offset = 0;
#endif
/* Now process all the remaining chunk headers in the stream
and record their position and size */
const auto size = is.GetSize();
while (is.GetOffset() < size) {
do {
uint64_t chunk_size = chunk_header->GetSize();
/* 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;
}
#endif
if (chunk_size != 0) {
if (!dsdlib_skip(decoder, is, chunk_size))
break;
}
if (is.GetOffset() < size) {
if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
return false;
}
}
if (!dsdlib_skip(decoder, is, chunk_size))
break;
} while (dsdiff_read_chunk_header(decoder, is, chunk_header));
/* done processing chunk headers, process tags if any */
#ifdef HAVE_ID3TAG
@@ -328,7 +324,7 @@ dsdiff_read_metadata(Decoder *decoder, InputStream &is,
DsdiffChunkHeader *chunk_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.format.Equals("DSD "))
return false;
@@ -381,7 +377,7 @@ dsdiff_decode_chunk(Decoder &decoder, InputStream &is,
const unsigned buffer_samples = buffer_frames * frame_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
fits into the local buffer */
size_t now_size = buffer_size;
@@ -391,10 +387,10 @@ dsdiff_decode_chunk(Decoder &decoder, InputStream &is,
now_size = now_frames * frame_size;
}
size_t nbytes = decoder_read(decoder, is, buffer, now_size);
if (nbytes != now_size)
if (!decoder_read_full(&decoder, is, buffer, now_size))
return false;
const size_t nbytes = now_size;
chunk_size -= nbytes;
if (lsbitfirst)

@@ -103,7 +103,7 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
DsfMetaData *metadata)
{
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 "))
return false;
@@ -117,7 +117,8 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
/* read the 'fmt ' chunk of the DSF file */
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 "))
return false;
@@ -143,7 +144,7 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
/* read the 'data' chunk of the DSF file */
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"))
return false;
@@ -237,7 +238,7 @@ dsf_decode_chunk(Decoder &decoder, InputStream &is,
const unsigned buffer_samples = buffer_frames * frame_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
fits into the local buffer */
size_t now_size = buffer_size;
@@ -247,10 +248,10 @@ dsf_decode_chunk(Decoder &decoder, InputStream &is,
now_size = now_frames * frame_size;
}
size_t nbytes = decoder_read(&decoder, is, buffer, now_size);
if (nbytes != now_size)
if (!decoder_read_full(&decoder, is, buffer, now_size))
return false;
const size_t nbytes = now_size;
chunk_size -= nbytes;
if (bitreverse)

@@ -34,8 +34,6 @@
#include <string.h>
#include <unistd.h>
#define AAC_MAX_CHANNELS 6
static const unsigned adts_sample_rates[] =
{ 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
16000, 12000, 11025, 8000, 7350, 0, 0, 0
@@ -66,16 +64,13 @@ adts_check_frame(const unsigned char *data)
static size_t
adts_find_frame(DecoderBuffer *buffer)
{
size_t length, frame_length;
bool ret;
while (true) {
size_t length;
const uint8_t *data = (const uint8_t *)
decoder_buffer_read(buffer, &length);
if (data == nullptr || length < 8) {
/* not enough data yet */
ret = decoder_buffer_fill(buffer);
if (!ret)
if (!decoder_buffer_fill(buffer))
/* failed */
return 0;
@@ -86,7 +81,7 @@ adts_find_frame(DecoderBuffer *buffer)
const uint8_t *p = (const uint8_t *)memchr(data, 0xff, length);
if (p == nullptr) {
/* no marker - discard the buffer */
decoder_buffer_consume(buffer, length);
decoder_buffer_clear(buffer);
continue;
}
@@ -97,7 +92,7 @@ adts_find_frame(DecoderBuffer *buffer)
}
/* is it a frame? */
frame_length = adts_check_frame(data);
const size_t frame_length = adts_check_frame(data);
if (frame_length == 0) {
/* it's just some random 0xff byte; discard it
and continue searching */
@@ -109,15 +104,11 @@ adts_find_frame(DecoderBuffer *buffer)
/* available buffer size is smaller than the
frame will be - attempt to read more
data */
ret = decoder_buffer_fill(buffer);
if (!ret) {
if (!decoder_buffer_fill(buffer)) {
/* not enough data; discard this frame
to prevent a possible buffer
overflow */
data = (const uint8_t *)
decoder_buffer_read(buffer, &length);
if (data != nullptr)
decoder_buffer_consume(buffer, length);
decoder_buffer_clear(buffer);
}
continue;
@@ -131,13 +122,18 @@ adts_find_frame(DecoderBuffer *buffer)
static float
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;
float frames_per_second;
/* Read all frames to ensure correct time and bitrate */
unsigned frames;
for (frames = 0;; frames++) {
frame_length = adts_find_frame(buffer);
const unsigned frame_length = adts_find_frame(buffer);
if (frame_length == 0)
break;
@@ -150,36 +146,52 @@ adts_song_duration(DecoderBuffer *buffer)
assert(frame_length <= buffer_length);
sample_rate = adts_sample_rates[(data[2] & 0x3c) >> 2];
if (sample_rate == 0)
break;
}
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 (frames_per_second <= 0)
if (sample_rate == 0)
return -1;
float frames_per_second = (float)sample_rate / 1024.0;
assert(frames_per_second > 0);
return (float)frames / frames_per_second;
}
static float
faad_song_duration(DecoderBuffer *buffer, InputStream &is)
{
size_t fileread;
size_t tagsize;
size_t length;
bool success;
const auto size = is.GetSize();
fileread = size >= 0 ? size : 0;
const size_t fileread = size >= 0 ? size : 0;
decoder_buffer_fill(buffer);
size_t length;
const uint8_t *data = (const uint8_t *)
decoder_buffer_read(buffer, &length);
if (data == nullptr)
return -1;
tagsize = 0;
size_t tagsize = 0;
if (length >= 10 && !memcmp(data, "ID3", 3)) {
/* skip the ID3 tag */
@@ -188,7 +200,7 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is)
tagsize += 10;
success = decoder_buffer_skip(buffer, tagsize) &&
const bool success = decoder_buffer_skip(buffer, tagsize) &&
decoder_buffer_fill(buffer);
if (!success)
return -1;
@@ -198,22 +210,20 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is)
return -1;
}
if (is.IsSeekable() && length >= 2 &&
data[0] == 0xFF && ((data[1] & 0xF6) == 0xF0)) {
if (length >= 8 && adts_check_frame(data) > 0) {
/* obtain the duration from the ADTS header */
if (!is.IsSeekable())
return -1;
float song_length = adts_song_duration(buffer);
is.LockSeek(tagsize, SEEK_SET, IgnoreError());
data = (const uint8_t *)decoder_buffer_read(buffer, &length);
if (data != nullptr)
decoder_buffer_consume(buffer, length);
decoder_buffer_fill(buffer);
decoder_buffer_clear(buffer);
return song_length;
} else if (length >= 5 && memcmp(data, "ADIF", 4) == 0) {
/* obtain the duration from the ADIF header */
unsigned bit_rate;
size_t skip_size = (data[4] & 0x80) ? 9 : 0;
if (8 + skip_size > length)
@@ -221,7 +231,7 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is)
header */
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[6 + skip_size] << 3) |
(data[7 + skip_size] & 0xE0);
@@ -234,6 +244,21 @@ faad_song_duration(DecoderBuffer *buffer, InputStream &is)
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
* inconsistencies in libfaad.
@@ -242,17 +267,6 @@ static bool
faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
AudioFormat &audio_format, Error &error)
{
int32_t nbytes;
uint32_t sample_rate;
uint8_t channels;
#ifdef HAVE_FAAD_LONG
/* neaacdec.h declares all arguments as "unsigned long", but
internally expects uint32_t pointers. To avoid gcc
warnings, use this workaround. */
unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
#else
uint32_t *sample_rate_p = &sample_rate;
#endif
size_t length;
const unsigned char *data = (const unsigned char *)
@@ -262,11 +276,21 @@ faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
return false;
}
nbytes = NeAACDecInit(decoder,
/* deconst hack, libfaad requires this */
const_cast<unsigned char *>(data),
length,
sample_rate_p, &channels);
uint8_t channels;
uint32_t sample_rate;
#ifdef HAVE_FAAD_LONG
/* neaacdec.h declares all arguments as "unsigned long", but
internally expects uint32_t pointers. To avoid gcc
warnings, use this workaround. */
unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
#else
uint32_t *sample_rate_p = &sample_rate;
#endif
long nbytes = NeAACDecInit(decoder,
/* deconst hack, libfaad requires this */
const_cast<unsigned char *>(data),
length,
sample_rate_p, &channels);
if (nbytes < 0) {
error.Set(faad_decoder_domain, "Not an AAC stream");
return false;
@@ -306,29 +330,21 @@ faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer *buffer,
static float
faad_get_file_time_float(InputStream &is)
{
DecoderBuffer *buffer;
float length;
DecoderBuffer *const buffer =
decoder_buffer_new(nullptr, is,
FAAD_MIN_STREAMSIZE * MAX_CHANNELS);
buffer = decoder_buffer_new(nullptr, is,
FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
length = faad_song_duration(buffer, is);
float length = faad_song_duration(buffer, is);
if (length < 0) {
bool ret;
AudioFormat audio_format;
NeAACDecHandle decoder = NeAACDecOpen();
NeAACDecConfigurationPtr config =
NeAACDecGetCurrentConfiguration(decoder);
config->outputFormat = FAAD_FMT_16BIT;
NeAACDecSetConfiguration(decoder, config);
NeAACDecHandle decoder = faad_decoder_new();
decoder_buffer_fill(buffer);
ret = faad_decoder_init(decoder, buffer, audio_format,
IgnoreError());
if (ret)
if (faad_decoder_init(decoder, buffer, audio_format,
IgnoreError()))
length = 0;
NeAACDecClose(decoder);
@@ -347,38 +363,25 @@ faad_get_file_time_float(InputStream &is)
static int
faad_get_file_time(InputStream &is)
{
int file_time = -1;
float length;
float length = faad_get_file_time_float(is);
if (length < 0)
return -1;
if ((length = faad_get_file_time_float(is)) >= 0)
file_time = length + 0.5;
return file_time;
return int(length + 0.5);
}
static void
faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
{
float total_time = 0;
AudioFormat audio_format;
bool ret;
uint16_t bit_rate = 0;
DecoderBuffer *buffer;
DecoderBuffer *const buffer =
decoder_buffer_new(&mpd_decoder, is,
FAAD_MIN_STREAMSIZE * MAX_CHANNELS);
buffer = decoder_buffer_new(&mpd_decoder, is,
FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
total_time = faad_song_duration(buffer, is);
const float total_time = faad_song_duration(buffer, is);
/* create the libfaad decoder */
NeAACDecHandle decoder = NeAACDecOpen();
NeAACDecConfigurationPtr config =
NeAACDecGetCurrentConfiguration(decoder);
config->outputFormat = FAAD_FMT_16BIT;
config->downMatrix = 1;
config->dontUpSampleImplicitSBR = 0;
NeAACDecSetConfiguration(decoder, config);
const NeAACDecHandle decoder = faad_decoder_new();
while (!decoder_buffer_is_full(buffer) && !is.LockIsEOF() &&
decoder_get_command(mpd_decoder) == DecoderCommand::NONE) {
@@ -389,8 +392,8 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
/* initialize it */
Error error;
ret = faad_decoder_init(decoder, buffer, audio_format, error);
if (!ret) {
AudioFormat audio_format;
if (!faad_decoder_init(decoder, buffer, audio_format, error)) {
LogError(error);
NeAACDecClose(decoder);
decoder_buffer_free(buffer);
@@ -404,21 +407,20 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
/* the decoder loop */
DecoderCommand cmd;
uint16_t bit_rate = 0;
do {
size_t frame_size;
const void *decoded;
NeAACDecFrameInfo frame_info;
/* find the next frame */
frame_size = adts_find_frame(buffer);
const size_t frame_size = adts_find_frame(buffer);
if (frame_size == 0)
/* end of file */
break;
/* 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) {
FormatWarning(faad_decoder_domain,
@@ -470,7 +472,6 @@ faad_scan_stream(InputStream &is,
const struct tag_handler *handler, void *handler_ctx)
{
int file_time = faad_get_file_time(is);
if (file_time < 0)
return false;

@@ -433,9 +433,18 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
AVStream *av_stream = format_context->streams[audio_stream];
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)
FormatDebug(ffmpeg_domain, "codec '%s'",
codec_context->codec_name);
#endif
AVCodec *codec = avcodec_find_decoder(codec_context->codec_id);

@@ -235,7 +235,7 @@ gme_scan_file(const char *path_fs,
if (ti->length > 0)
tag_handler_invoke_duration(handler, handler_ctx,
ti->length / 100);
ti->length / 1000);
if (ti->song != nullptr) {
if (gme_track_count(emu) > 1) {

@@ -353,18 +353,8 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
memcpy(allocated, stream.this_frame, count);
mad_stream_skip(&(stream), count);
while (count < tagsize) {
size_t len;
len = decoder_read(decoder, input_stream,
allocated + count, tagsize - count);
if (len == 0)
break;
else
count += len;
}
if (count != tagsize) {
if (!decoder_read_full(decoder, input_stream,
allocated + count, tagsize - count)) {
LogDebug(mad_domain, "error parsing ID3 tag");
g_free(allocated);
return;
@@ -413,20 +403,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
mad_stream_skip(&stream, tagsize);
} else {
mad_stream_skip(&stream, count);
while (count < tagsize) {
size_t len = tagsize - count;
char ignored[1024];
if (len > sizeof(ignored))
len = sizeof(ignored);
len = decoder_read(decoder, input_stream,
ignored, len);
if (len == 0)
break;
else
count += len;
}
decoder_skip(decoder, input_stream, tagsize - count);
}
#endif
}

@@ -40,6 +40,7 @@
#include <glib.h>
#include <string.h>
#include <stdio.h>
static constexpr opus_int32 opus_sample_rate = 48000;

@@ -31,10 +31,24 @@
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
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();
}
@@ -42,10 +56,14 @@ sndfile_vio_get_filelen(void *user_data)
static sf_count_t
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 is.GetOffset();
}
@@ -53,30 +71,9 @@ sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
static sf_count_t
sndfile_vio_read(void *ptr, sf_count_t count, void *user_data)
{
InputStream &is = *(InputStream *)user_data;
SndfileInputStream &sis = *(SndfileInputStream *)user_data;
sf_count_t total_bytes = 0;
Error error;
/* this loop is necessary because libsndfile chokes on partial
reads */
do {
size_t nbytes = is.LockRead((char *)ptr + total_bytes,
count - total_bytes, error);
if (nbytes == 0) {
if (error.IsDefined()) {
LogError(error);
return -1;
}
break;
}
total_bytes += nbytes;
} while (total_bytes < count);
return total_bytes;
return sis.Read(ptr, count);
}
static sf_count_t
@@ -91,7 +88,8 @@ sndfile_vio_write(gcc_unused const void *ptr,
static sf_count_t
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();
}
@@ -137,7 +135,8 @@ sndfile_stream_decode(Decoder &decoder, InputStream &is)
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) {
LogWarning(sndfile_domain, "sf_open_virtual() failed");
return;

@@ -118,7 +118,7 @@ BufferedSocket::OnSocketReady(unsigned flags)
if (!ReadToBuffer() || !ResumeInput())
return false;
if (input.IsFull())
if (!input.IsFull())
ScheduleRead();
}

@@ -64,7 +64,9 @@ TimeoutMonitor::ScheduleSeconds(unsigned s)
void
TimeoutMonitor::Run()
{
#ifndef USE_EPOLL
#ifdef USE_EPOLL
active = false;
#else
Cancel();
#endif

@@ -802,6 +802,7 @@ alsa_play(struct audio_output *ao, const void *chunk, size_t size,
{
AlsaOutput *ad = (AlsaOutput *)ao;
assert(size > 0);
assert(size % ad->in_frame_size == 0);
if (ad->must_prepare) {
@@ -814,11 +815,21 @@ alsa_play(struct audio_output *ao, const void *chunk, size_t size,
}
}
const size_t original_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);
size /= ad->out_frame_size;
assert(size > 0);
while (true) {
snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);

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

@@ -727,6 +727,8 @@ oss_output_play(struct audio_output *ao, const void *chunk, size_t size,
OssOutput *od = (OssOutput *)ao;
ssize_t ret;
assert(size > 0);
/* reopen the device since it was closed by dropBufferedAudio */
if (od->fd < 0 && !oss_reopen(od, error))
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);
#endif
assert(size > 0);
while (true) {
ret = write(od->fd, chunk, size);
if (ret > 0) {

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

@@ -40,6 +40,16 @@
/* well-known big-endian */
# define IS_LITTLE_ENDIAN false
# 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
/* generic compile-time check */
# include <endian.h>

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

Before

(image error) Size: 345 KiB

After

(image error) Size: 345 KiB

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

144
test/FakeDecoderAPI.cxx Normal 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)
{
}

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

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

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