Compare commits

...

51 Commits

Author SHA1 Message Date
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
40 changed files with 732 additions and 387 deletions

View File

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

27
NEWS
View File

@@ -1,3 +1,30 @@
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) ver 0.18.9 (2014/03/02)
* protocol * protocol
- "findadd" requires the "add" permission - "findadd" requires the "add" permission

View File

@@ -1,6 +1,6 @@
AC_PREREQ(2.60) AC_PREREQ(2.60)
AC_INIT(mpd, 0.18.9, mpd-devel@musicpd.org) AC_INIT(mpd, 0.18.12, mpd-devel@musicpd.org)
VERSION_MAJOR=0 VERSION_MAJOR=0
VERSION_MINOR=18 VERSION_MINOR=18

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);
/** /**

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 */
} }

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

@@ -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;
@@ -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;
@@ -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;
@@ -493,10 +516,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;

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

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

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

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

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)