Compare commits
67 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f28c746b6b | ||
![]() |
ab95027fc6 | ||
![]() |
ed3bc4ab63 | ||
![]() |
68064f1aa6 | ||
![]() |
475ac76a5f | ||
![]() |
79d4f8674c | ||
![]() |
e42eed4d4c | ||
![]() |
4a7042e847 | ||
![]() |
7f36923eb4 | ||
![]() |
2ca8d69126 | ||
![]() |
70367d70c8 | ||
![]() |
e6389ff5a1 | ||
![]() |
b46cf57d98 | ||
![]() |
6f59d71e07 | ||
![]() |
f9130f42a2 | ||
![]() |
faf2eeaa99 | ||
![]() |
1c7de0b4ac | ||
![]() |
58487e484f | ||
![]() |
104075f3e0 | ||
![]() |
b8097eaf2e | ||
![]() |
5eb0cbc887 | ||
![]() |
ba8e579e9b | ||
![]() |
072e39c9cf | ||
![]() |
8dc3f3b21a | ||
![]() |
faf0c950fe | ||
![]() |
4ecd325371 | ||
![]() |
5771d67202 | ||
![]() |
75c8aecffa | ||
![]() |
aa5d05eaa4 | ||
![]() |
15735552f4 | ||
![]() |
d6d9dc9d95 | ||
![]() |
dc57966dc3 | ||
![]() |
04ed50fb0f | ||
![]() |
c9553411bb | ||
![]() |
62221adf55 | ||
![]() |
a6bf4746c6 | ||
![]() |
72637d00e8 | ||
![]() |
27d4b15925 | ||
![]() |
7a77767e66 | ||
![]() |
1b26621860 | ||
![]() |
3db5f4d0aa | ||
![]() |
b2a6e327bf | ||
![]() |
9aec5fe907 | ||
![]() |
c731a82b71 | ||
![]() |
e6fad97edc | ||
![]() |
70495aada1 | ||
![]() |
f243f615ef | ||
![]() |
807c72b2f1 | ||
![]() |
74dbaade6f | ||
![]() |
53677172f2 | ||
![]() |
bef0ccf42a | ||
![]() |
ff35aa07dc | ||
![]() |
a3afd5178c | ||
![]() |
f1285a6dfd | ||
![]() |
cf7c1afb93 | ||
![]() |
e140a28073 | ||
![]() |
de61c3b962 | ||
![]() |
c46fc4531b | ||
![]() |
065a9ed10f | ||
![]() |
e44c0254f7 | ||
![]() |
13f9f0315f | ||
![]() |
1532ffe215 | ||
![]() |
b24cbc68ba | ||
![]() |
976fdd76c1 | ||
![]() |
bbda335e02 | ||
![]() |
d2dd6f7c70 | ||
![]() |
e9a544fa98 |
Makefile.amNEWSconfigure.ac
doc
src
PlayerThread.cxxnotify.hxx
archive
plugins
client
decoder
encoder
plugins
event
filter
mixer
plugins
output
plugins
pcm
protocol
tag
thread
util
test
@@ -467,6 +467,7 @@ libpcm_a_SOURCES = \
|
||||
src/pcm/PcmConvert.cxx src/pcm/PcmConvert.hxx \
|
||||
src/pcm/PcmDop.cxx src/pcm/PcmDop.hxx \
|
||||
src/pcm/Volume.cxx src/pcm/Volume.hxx \
|
||||
src/pcm/Silence.cxx src/pcm/Silence.hxx \
|
||||
src/pcm/PcmMix.cxx src/pcm/PcmMix.hxx \
|
||||
src/pcm/PcmChannels.cxx src/pcm/PcmChannels.hxx \
|
||||
src/pcm/PcmPack.cxx src/pcm/PcmPack.hxx \
|
||||
@@ -2109,7 +2110,9 @@ developer_DATA = $(wildcard doc/developer/*.html)
|
||||
|
||||
DOCBOOK_HTML = $(patsubst %.xml,%/index.html,$(DOCBOOK_FILES))
|
||||
|
||||
$(DOCBOOK_HTML): %/index.html: %.xml
|
||||
DOCBOOK_INCLUDES = $(wildcard $(srcdir)/doc/include/*.xml)
|
||||
|
||||
$(DOCBOOK_HTML): %/index.html: %.xml $(DOCBOOK_INCLUDES)
|
||||
$(XMLTO) -o $(@D) --stringparam chunker.output.encoding=utf-8 html --stringparam use.id.as.filename=1 $<
|
||||
|
||||
doc/api/html/index.html: doc/doxygen.conf
|
||||
@@ -2150,8 +2153,9 @@ EXTRA_DIST = $(doc_DATA) autogen.sh \
|
||||
test/test_archive_bzip2.sh \
|
||||
test/test_archive_iso9660.sh \
|
||||
test/test_archive_zzip.sh \
|
||||
$(wildcard scripts/*.sh) \
|
||||
$(wildcard $(srcdir)/scripts/*.rb) \
|
||||
$(man_MANS) $(DOCBOOK_FILES) doc/mpdconf.example doc/doxygen.conf \
|
||||
$(wildcard $(srcdir)/doc/include/*.xml) \
|
||||
systemd/mpd.socket \
|
||||
android/AndroidManifest.xml \
|
||||
android/build.py \
|
||||
|
33
NEWS
33
NEWS
@@ -1,3 +1,36 @@
|
||||
ver 0.19.17 (2016/07/09)
|
||||
* decoder
|
||||
- flac: fix assertion failure while seeking
|
||||
- flac: fix stream duration indicator
|
||||
- fix seek problems in several plugins
|
||||
* fix spurious seek error "Failed to allocate silence buffer"
|
||||
* replay gain: fix "replay_gain_handler mixer" setting
|
||||
* DSD: use 0x69 as silence pattern
|
||||
* fix use-after-free bug on "close" and "kill"
|
||||
|
||||
ver 0.19.16 (2016/06/13)
|
||||
* faster seeking
|
||||
* fix system include path order
|
||||
* add missing DocBook file to tarball
|
||||
|
||||
ver 0.19.15 (2016/04/30)
|
||||
* decoder
|
||||
- ffmpeg: support FFmpeg 3.0
|
||||
- ffmpeg: use as fallback instead of "mad" if no plugin matches
|
||||
- opus: support bigger OpusTags packets
|
||||
* fix more build failures on non-glibc builds due to constexpr Mutex
|
||||
* fix build failure due to missing include
|
||||
* fix unit test on Alpha
|
||||
|
||||
ver 0.19.14 (2016/03/18)
|
||||
* decoder
|
||||
- dsdiff: fix off-by-one buffer overflow
|
||||
- opus: limit tag size to 64 kB
|
||||
* archive
|
||||
- iso9660: fix buffer overflow
|
||||
* fix quadratic runtime bug in the tag pool
|
||||
* fix build failures on non-glibc builds due to constexpr Mutex
|
||||
|
||||
ver 0.19.13 (2016/02/23)
|
||||
* tags
|
||||
- aiff, riff: fix ID3 chunk padding
|
||||
|
10
configure.ac
10
configure.ac
@@ -1,10 +1,10 @@
|
||||
AC_PREREQ(2.60)
|
||||
|
||||
AC_INIT(mpd, 0.19.13, musicpd-dev-team@lists.sourceforge.net)
|
||||
AC_INIT(mpd, 0.19.17, musicpd-dev-team@lists.sourceforge.net)
|
||||
|
||||
VERSION_MAJOR=0
|
||||
VERSION_MINOR=19
|
||||
VERSION_REVISION=13
|
||||
VERSION_REVISION=17
|
||||
VERSION_EXTRA=0
|
||||
|
||||
AC_CONFIG_SRCDIR([src/Main.cxx])
|
||||
@@ -703,12 +703,6 @@ AC_ARG_ENABLE(glib,
|
||||
if test x$enable_glib = xyes; then
|
||||
PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.28 gthread-2.0],,
|
||||
[AC_MSG_ERROR([GLib 2.28 is required])])
|
||||
|
||||
if test x$GCC = xyes; then
|
||||
# suppress warnings in the GLib headers
|
||||
GLIB_CFLAGS=`echo $GLIB_CFLAGS |sed -e 's,-I/,-isystem /,g'`
|
||||
fi
|
||||
|
||||
AC_DEFINE(HAVE_GLIB, 1, [Define if GLib is used])
|
||||
fi
|
||||
AM_CONDITIONAL(HAVE_GLIB, test x$enable_glib = xyes)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
<?xml version='1.0' encoding="utf-8"?>
|
||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
|
||||
"docbook/dtd/xml/4.2/docbookx.dtd">
|
||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||
|
||||
<book>
|
||||
<title>The Music Player Daemon - Developer's Manual</title>
|
||||
|
||||
|
154
doc/include/tags.xml
Normal file
154
doc/include/tags.xml
Normal file
@@ -0,0 +1,154 @@
|
||||
<?xml version='1.0' encoding="utf-8"?>
|
||||
<!DOCTYPE itemizedlist PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>artist</varname>: the artist name. Its meaning is not
|
||||
well-defined; see <varname>composer</varname> and
|
||||
<varname>performer</varname> for more specific tags.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>artistsort</varname>: same as
|
||||
<varname>artist</varname>, but for sorting. This usually omits
|
||||
prefixes such as "The".
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>album</varname>: the album name.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>albumsort</varname>: same as <varname>album</varname>,
|
||||
but for sorting.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>albumartist</varname>: on multi-artist albums, this is
|
||||
the artist name which shall be used for the whole album. The
|
||||
exact meaning of this tag is not well-defined.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>albumartistsort</varname>: same as
|
||||
<varname>albumartist</varname>, but for sorting.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>title</varname>: the song title.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>track</varname>: the track number within the album.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>name</varname>: a name for this song. This is not the
|
||||
song title. The exact meaning of this tag is not well-defined.
|
||||
It is often used by badly configured internet radio stations
|
||||
with broken tags to squeeze both the artist name and the song
|
||||
title in one tag.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>genre</varname>: the music genre.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>date</varname>: the song's release date. This is
|
||||
usually a 4-digit year.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>composer</varname>: the artist who composed the song.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>performer</varname>: the artist who performed the song.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>comment</varname>: a human-readable comment about this
|
||||
song. The exact meaning of this tag is not well-defined.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>disc</varname>: the disc number in a multi-disc album.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>musicbrainz_artistid</varname>: the artist id in the
|
||||
<ulink
|
||||
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>musicbrainz_albumid</varname>: the album id in the
|
||||
<ulink
|
||||
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>musicbrainz_albumartistid</varname>: the album artist
|
||||
id in the <ulink
|
||||
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>musicbrainz_trackid</varname>: the track id in the
|
||||
<ulink
|
||||
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>musicbrainz_releasetrackid</varname>: the release track
|
||||
id in the <ulink
|
||||
url="http://musicbrainz.org/doc/MusicBrainzTag">MusicBrainz</ulink>
|
||||
database.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
@@ -174,18 +174,6 @@ MP3 playback.
|
||||
This specifies whether relative or absolute paths for song filenames are used
|
||||
when saving playlists. The default is "no".
|
||||
.TP
|
||||
.B metadata_to_use <tags>
|
||||
This specifies the tag types that will be scanned for and made available to
|
||||
clients. Note that you must recreate (not update) your database for changes to
|
||||
this parameter to take effect. Possible values are artist, album, title,
|
||||
track, name, genre, date, composer, performer, comment, disc,
|
||||
musicbrainz_artistid, musicbrainz_albumid, musicbrainz_albumartistid,
|
||||
musicbrainz_releasetrackid, musicbrainz_trackid. Multiple tags may be specified
|
||||
as a comma separated list.
|
||||
An example value is "artist,album,title,track". The special value "none" may
|
||||
be used alone to disable all metadata. The default is to use all known tag
|
||||
types except for comments and those starting with "musicbrainz".
|
||||
.TP
|
||||
.B auto_update <yes or no>
|
||||
This specifies the whether to support automatic update of music database when
|
||||
files are changed in music_directory. The default is to disable autoupdate
|
||||
|
@@ -115,7 +115,7 @@
|
||||
#
|
||||
# This setting defines a list of tag types that will be extracted during the
|
||||
# audio file discovery process. The complete list of possible values can be
|
||||
# found in the mpd.conf man page.
|
||||
# found in the user manual.
|
||||
#metadata_to_use "artist,album,title,track,name,genre,date,composer,performer,disc"
|
||||
#
|
||||
# This setting enables automatic update of MPD's database when files in
|
||||
|
@@ -1,6 +1,7 @@
|
||||
<?xml version='1.0' encoding="utf-8"?>
|
||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
|
||||
"docbook/dtd/xml/4.2/docbookx.dtd">
|
||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||
|
||||
<book>
|
||||
<title>The Music Player Daemon protocol</title>
|
||||
|
||||
@@ -201,6 +202,25 @@
|
||||
omitted, then the maximum possible value is assumed.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="tags">
|
||||
<title>Tags</title>
|
||||
|
||||
<para>
|
||||
The following tags are supported by
|
||||
<application>MPD</application>:
|
||||
</para>
|
||||
|
||||
<xi:include href="include/tags.xml"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"/>
|
||||
|
||||
<para>
|
||||
There can be multiple values for some of these tags. For
|
||||
example, <application>MPD</application> may return multiple
|
||||
lines with a <varname>performer</varname> tag. A tag value is
|
||||
a UTF-8 string.
|
||||
</para>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
<chapter id="recipes">
|
||||
|
47
doc/user.xml
47
doc/user.xml
@@ -1,6 +1,7 @@
|
||||
<?xml version='1.0' encoding="utf-8"?>
|
||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
|
||||
"docbook/dtd/xml/4.2/docbookx.dtd">
|
||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||
|
||||
<book>
|
||||
<title>The Music Player Daemon - User's Manual</title>
|
||||
|
||||
@@ -16,7 +17,7 @@
|
||||
<application>MPD</application> (Music Player Daemon) is, as the
|
||||
name suggests, a server software allowing you to remotely play
|
||||
your music, handle playlists, deliver music (HTTP streams with
|
||||
various sub-protocols) and organizze playlists.
|
||||
various sub-protocols) and organize playlists.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
@@ -944,6 +945,33 @@ systemctl start mpd.socket</programlisting>
|
||||
<section id="config_other">
|
||||
<title>Other Settings</title>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Setting</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>metadata_to_use</varname>
|
||||
<parameter>TAG1,TAG2,...</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
Use only the specified tags, and ignore the others.
|
||||
This setting can reduce the database size and
|
||||
<application>MPD</application>'s memory usage by
|
||||
omitting unused tags. By default, all tags but
|
||||
<varname>comment</varname> are enabled. The special
|
||||
value "none" disables all tags.
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
|
||||
<section>
|
||||
<title>The State File</title>
|
||||
|
||||
@@ -1297,6 +1325,19 @@ database {
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="tags">
|
||||
<title>Metadata</title>
|
||||
|
||||
<para>
|
||||
When scanning or playing a song,
|
||||
<application>MPD</application> parses its metadata. The
|
||||
following tags are supported:
|
||||
</para>
|
||||
|
||||
<xi:include href="include/tags.xml"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude"/>
|
||||
</section>
|
||||
|
||||
<section id="queue">
|
||||
<title>The queue</title>
|
||||
|
||||
|
@@ -25,6 +25,7 @@
|
||||
#include "MusicPipe.hxx"
|
||||
#include "MusicBuffer.hxx"
|
||||
#include "MusicChunk.hxx"
|
||||
#include "pcm/Silence.hxx"
|
||||
#include "DetachedSong.hxx"
|
||||
#include "system/FatalError.hxx"
|
||||
#include "CrossFade.hxx"
|
||||
@@ -486,8 +487,12 @@ Player::SendSilence()
|
||||
|
||||
MusicChunk *chunk = buffer.Allocate();
|
||||
if (chunk == nullptr) {
|
||||
LogError(player_domain, "Failed to allocate silence buffer");
|
||||
return false;
|
||||
/* this is non-fatal, because this means that the
|
||||
decoder has filled to buffer completely meanwhile;
|
||||
by ignoring the error, we work around this race
|
||||
condition */
|
||||
LogDebug(player_domain, "Failed to allocate silence buffer");
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -501,7 +506,7 @@ Player::SendSilence()
|
||||
|
||||
chunk->time = SignedSongTime::Negative(); /* undefined time stamp */
|
||||
chunk->length = num_frames * frame_size;
|
||||
memset(chunk->data, 0, chunk->length);
|
||||
PcmSilence({chunk->data, chunk->length}, play_audio_format.format);
|
||||
|
||||
Error error;
|
||||
if (!pc.outputs.Play(chunk, error)) {
|
||||
@@ -518,6 +523,8 @@ Player::SeekDecoder()
|
||||
{
|
||||
assert(pc.next_song != nullptr);
|
||||
|
||||
pc.outputs.Cancel();
|
||||
|
||||
const SongTime start_time = pc.next_song->GetStartTime();
|
||||
|
||||
if (!dc.LockIsCurrentSong(*pc.next_song)) {
|
||||
@@ -583,8 +590,6 @@ Player::SeekDecoder()
|
||||
/* re-fill the buffer after seeking */
|
||||
buffering = true;
|
||||
|
||||
pc.outputs.Cancel();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -66,7 +66,11 @@ public:
|
||||
return iso9660_iso_seek_read(iso, ptr, start, i_size);
|
||||
}
|
||||
|
||||
void Visit(const char *path, ArchiveVisitor &visitor);
|
||||
/**
|
||||
* @param capacity the path buffer size
|
||||
*/
|
||||
void Visit(char *path, size_t length, size_t capacity,
|
||||
ArchiveVisitor &visitor);
|
||||
|
||||
virtual void Close() override {
|
||||
Unref();
|
||||
@@ -84,32 +88,36 @@ static constexpr Domain iso9660_domain("iso9660");
|
||||
/* archive open && listing routine */
|
||||
|
||||
inline void
|
||||
Iso9660ArchiveFile::Visit(const char *psz_path, ArchiveVisitor &visitor)
|
||||
Iso9660ArchiveFile::Visit(char *path, size_t length, size_t capacity,
|
||||
ArchiveVisitor &visitor)
|
||||
{
|
||||
CdioList_t *entlist;
|
||||
CdioListNode_t *entnode;
|
||||
iso9660_stat_t *statbuf;
|
||||
char pathname[4096];
|
||||
|
||||
entlist = iso9660_ifs_readdir (iso, psz_path);
|
||||
auto *entlist = iso9660_ifs_readdir(iso, path);
|
||||
if (!entlist) {
|
||||
return;
|
||||
}
|
||||
/* Iterate over the list of nodes that iso9660_ifs_readdir gives */
|
||||
CdioListNode_t *entnode;
|
||||
_CDIO_LIST_FOREACH (entnode, entlist) {
|
||||
statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode);
|
||||
auto *statbuf = (iso9660_stat_t *)
|
||||
_cdio_list_node_data(entnode);
|
||||
const char *filename = statbuf->filename;
|
||||
if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0)
|
||||
continue;
|
||||
|
||||
strcpy(pathname, psz_path);
|
||||
strcat(pathname, statbuf->filename);
|
||||
size_t filename_length = strlen(filename);
|
||||
if (length + filename_length + 1 >= capacity)
|
||||
/* file name is too long */
|
||||
continue;
|
||||
|
||||
memcpy(path + length, filename, filename_length + 1);
|
||||
size_t new_length = length + filename_length;
|
||||
|
||||
if (iso9660_stat_s::_STAT_DIR == statbuf->type ) {
|
||||
if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) {
|
||||
strcat(pathname, "/");
|
||||
Visit(pathname, visitor);
|
||||
}
|
||||
memcpy(path + new_length, "/", 2);
|
||||
Visit(path, new_length + 1, capacity, visitor);
|
||||
} else {
|
||||
//remove leading /
|
||||
visitor.VisitArchiveEntry(pathname + 1);
|
||||
visitor.VisitArchiveEntry(path + 1);
|
||||
}
|
||||
}
|
||||
_cdio_list_free (entlist, true);
|
||||
@@ -133,7 +141,8 @@ iso9660_archive_open(Path pathname, Error &error)
|
||||
void
|
||||
Iso9660ArchiveFile::Visit(ArchiveVisitor &visitor)
|
||||
{
|
||||
Visit("/", visitor);
|
||||
char path[4096] = "/";
|
||||
Visit(path, 1, sizeof(path), visitor);
|
||||
}
|
||||
|
||||
/* single archive handling */
|
||||
|
@@ -52,8 +52,8 @@ Client::OnSocketInput(void *data, size_t length)
|
||||
break;
|
||||
|
||||
case CommandResult::KILL:
|
||||
Close();
|
||||
partition.instance.event_loop->Break();
|
||||
Close();
|
||||
return InputResult::CLOSED;
|
||||
|
||||
case CommandResult::FINISH:
|
||||
|
@@ -301,7 +301,8 @@ decoder_check_cancel_read(const Decoder *decoder)
|
||||
/* ignore the SEEK command during initialization, the plugin
|
||||
should handle that after it has initialized successfully */
|
||||
if (dc.command == DecoderCommand::SEEK &&
|
||||
(dc.state == DecoderState::START || decoder->seeking))
|
||||
(dc.state == DecoderState::START || decoder->seeking ||
|
||||
decoder->initial_seek_running))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
@@ -25,6 +25,7 @@
|
||||
#include "util/ConstBuffer.hxx"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct Decoder;
|
||||
class InputStream;
|
||||
|
@@ -255,7 +255,11 @@ decoder_run_stream_fallback(Decoder &decoder, InputStream &is)
|
||||
{
|
||||
const struct DecoderPlugin *plugin;
|
||||
|
||||
#ifdef HAVE_FFMPEG
|
||||
plugin = decoder_plugin_from_name("ffmpeg");
|
||||
#else
|
||||
plugin = decoder_plugin_from_name("mad");
|
||||
#endif
|
||||
return plugin != nullptr && plugin->stream_decode != nullptr &&
|
||||
decoder_stream_decode(*plugin, decoder, is);
|
||||
}
|
||||
|
@@ -205,7 +205,7 @@ dsdiff_handle_native_tag(InputStream &is,
|
||||
if (length == 0 || length > 60)
|
||||
return;
|
||||
|
||||
char string[length];
|
||||
char string[length + 1];
|
||||
char *label;
|
||||
label = string;
|
||||
|
||||
|
@@ -199,10 +199,10 @@ ffmpeg_init(gcc_unused const config_param ¶m)
|
||||
}
|
||||
|
||||
static int
|
||||
ffmpeg_find_audio_stream(const AVFormatContext *format_context)
|
||||
ffmpeg_find_audio_stream(const AVFormatContext &format_context)
|
||||
{
|
||||
for (unsigned i = 0; i < format_context->nb_streams; ++i)
|
||||
if (format_context->streams[i]->codec->codec_type ==
|
||||
for (unsigned i = 0; i < format_context.nb_streams; ++i)
|
||||
if (format_context.streams[i]->codec->codec_type ==
|
||||
AVMEDIA_TYPE_AUDIO)
|
||||
return i;
|
||||
|
||||
@@ -276,22 +276,22 @@ copy_interleave_frame2(uint8_t *dest, uint8_t **src,
|
||||
* Copy PCM data from a AVFrame to an interleaved buffer.
|
||||
*/
|
||||
static int
|
||||
copy_interleave_frame(const AVCodecContext *codec_context,
|
||||
const AVFrame *frame,
|
||||
copy_interleave_frame(const AVCodecContext &codec_context,
|
||||
const AVFrame &frame,
|
||||
uint8_t **output_buffer,
|
||||
uint8_t **global_buffer, int *global_buffer_size)
|
||||
{
|
||||
int plane_size;
|
||||
const int data_size =
|
||||
av_samples_get_buffer_size(&plane_size,
|
||||
codec_context->channels,
|
||||
frame->nb_samples,
|
||||
codec_context->sample_fmt, 1);
|
||||
codec_context.channels,
|
||||
frame.nb_samples,
|
||||
codec_context.sample_fmt, 1);
|
||||
if (data_size <= 0)
|
||||
return data_size;
|
||||
|
||||
if (av_sample_fmt_is_planar(codec_context->sample_fmt) &&
|
||||
codec_context->channels > 1) {
|
||||
if (av_sample_fmt_is_planar(codec_context.sample_fmt) &&
|
||||
codec_context.channels > 1) {
|
||||
if(*global_buffer_size < data_size) {
|
||||
av_freep(global_buffer);
|
||||
|
||||
@@ -303,12 +303,12 @@ copy_interleave_frame(const AVCodecContext *codec_context,
|
||||
*global_buffer_size = data_size;
|
||||
}
|
||||
*output_buffer = *global_buffer;
|
||||
copy_interleave_frame2(*output_buffer, frame->extended_data,
|
||||
frame->nb_samples,
|
||||
codec_context->channels,
|
||||
av_get_bytes_per_sample(codec_context->sample_fmt));
|
||||
copy_interleave_frame2(*output_buffer, frame.extended_data,
|
||||
frame.nb_samples,
|
||||
codec_context.channels,
|
||||
av_get_bytes_per_sample(codec_context.sample_fmt));
|
||||
} else {
|
||||
*output_buffer = frame->extended_data[0];
|
||||
*output_buffer = frame.extended_data[0];
|
||||
}
|
||||
|
||||
return data_size;
|
||||
@@ -349,41 +349,39 @@ PtsToPcmFrame(uint64_t pts, const AVStream &stream,
|
||||
*/
|
||||
static DecoderCommand
|
||||
ffmpeg_send_packet(Decoder &decoder, InputStream &is,
|
||||
const AVPacket *packet,
|
||||
AVCodecContext *codec_context,
|
||||
const AVStream *stream,
|
||||
AVPacket &&packet,
|
||||
AVCodecContext &codec_context,
|
||||
const AVStream &stream,
|
||||
AVFrame *frame,
|
||||
uint64_t min_frame, size_t pcm_frame_size,
|
||||
uint8_t **buffer, int *buffer_size)
|
||||
{
|
||||
size_t skip_bytes = 0;
|
||||
|
||||
const auto pts = StreamRelativePts(*packet, *stream);
|
||||
const auto pts = StreamRelativePts(packet, stream);
|
||||
if (pts >= 0) {
|
||||
if (min_frame > 0) {
|
||||
auto cur_frame = PtsToPcmFrame(pts, *stream,
|
||||
*codec_context);
|
||||
auto cur_frame = PtsToPcmFrame(pts, stream,
|
||||
codec_context);
|
||||
if (cur_frame < min_frame)
|
||||
skip_bytes = pcm_frame_size * (min_frame - cur_frame);
|
||||
} else
|
||||
decoder_timestamp(decoder,
|
||||
time_from_ffmpeg(pts, stream->time_base));
|
||||
time_from_ffmpeg(pts, stream.time_base));
|
||||
}
|
||||
|
||||
AVPacket packet2 = *packet;
|
||||
|
||||
uint8_t *output_buffer;
|
||||
|
||||
DecoderCommand cmd = DecoderCommand::NONE;
|
||||
while (packet2.size > 0 && cmd == DecoderCommand::NONE) {
|
||||
while (packet.size > 0 && cmd == DecoderCommand::NONE) {
|
||||
int audio_size = 0;
|
||||
int got_frame = 0;
|
||||
int len = avcodec_decode_audio4(codec_context,
|
||||
int len = avcodec_decode_audio4(&codec_context,
|
||||
frame, &got_frame,
|
||||
&packet2);
|
||||
&packet);
|
||||
if (len >= 0 && got_frame) {
|
||||
audio_size = copy_interleave_frame(codec_context,
|
||||
frame,
|
||||
*frame,
|
||||
&output_buffer,
|
||||
buffer, buffer_size);
|
||||
if (audio_size < 0)
|
||||
@@ -397,8 +395,8 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is,
|
||||
break;
|
||||
}
|
||||
|
||||
packet2.data += len;
|
||||
packet2.size -= len;
|
||||
packet.data += len;
|
||||
packet.size -= len;
|
||||
|
||||
if (audio_size <= 0)
|
||||
continue;
|
||||
@@ -417,7 +415,7 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is,
|
||||
|
||||
cmd = decoder_data(decoder, is,
|
||||
data, audio_size,
|
||||
codec_context->bit_rate / 1000);
|
||||
codec_context.bit_rate / 1000);
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
@@ -535,7 +533,7 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
|
||||
return;
|
||||
}
|
||||
|
||||
int audio_stream = ffmpeg_find_audio_stream(format_context);
|
||||
int audio_stream = ffmpeg_find_audio_stream(*format_context);
|
||||
if (audio_stream == -1) {
|
||||
LogError(ffmpeg_domain, "No audio stream inside");
|
||||
avformat_close_input(&format_context);
|
||||
@@ -631,8 +629,9 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
|
||||
|
||||
if (packet.stream_index == audio_stream) {
|
||||
cmd = ffmpeg_send_packet(decoder, input,
|
||||
&packet, codec_context,
|
||||
av_stream,
|
||||
std::move(packet),
|
||||
*codec_context,
|
||||
*av_stream,
|
||||
frame,
|
||||
min_frame, audio_format.GetFrameSize(),
|
||||
&interleaved_buffer, &interleaved_buffer_size);
|
||||
@@ -640,7 +639,11 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
|
||||
} else
|
||||
cmd = decoder_get_command(decoder);
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(56, 25, 100)
|
||||
av_packet_unref(&packet);
|
||||
#else
|
||||
av_free_packet(&packet);
|
||||
#endif
|
||||
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
int64_t where =
|
||||
@@ -709,7 +712,7 @@ ffmpeg_scan_stream(InputStream &is,
|
||||
}
|
||||
|
||||
ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx);
|
||||
int idx = ffmpeg_find_audio_stream(f);
|
||||
int idx = ffmpeg_find_audio_stream(*f);
|
||||
if (idx >= 0)
|
||||
ffmpeg_scan_dictionary(f->streams[idx]->metadata,
|
||||
handler, handler_ctx);
|
||||
|
@@ -33,7 +33,7 @@ flac_data::flac_data(Decoder &_decoder,
|
||||
InputStream &_input_stream)
|
||||
:FlacInput(_input_stream, &_decoder),
|
||||
initialized(false), unsupported(false),
|
||||
total_frames(0), first_frame(0), next_frame(0), position(0),
|
||||
position(0),
|
||||
decoder(_decoder), input_stream(_input_stream)
|
||||
{
|
||||
}
|
||||
@@ -59,6 +59,38 @@ flac_sample_format(unsigned bits_per_sample)
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
flac_data::Initialize(unsigned sample_rate, unsigned bits_per_sample,
|
||||
unsigned channels, FLAC__uint64 total_frames)
|
||||
{
|
||||
assert(!initialized);
|
||||
assert(!unsupported);
|
||||
|
||||
::Error error;
|
||||
if (!audio_format_init_checked(audio_format,
|
||||
sample_rate,
|
||||
flac_sample_format(bits_per_sample),
|
||||
channels, error)) {
|
||||
LogError(error);
|
||||
unsupported = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
frame_size = audio_format.GetFrameSize();
|
||||
|
||||
const auto duration = total_frames > 0
|
||||
? SignedSongTime::FromScale<uint64_t>(total_frames,
|
||||
audio_format.sample_rate)
|
||||
: SignedSongTime::Negative();
|
||||
|
||||
decoder_initialized(decoder, audio_format,
|
||||
input_stream.IsSeekable(),
|
||||
duration);
|
||||
|
||||
initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
flac_got_stream_info(struct flac_data *data,
|
||||
const FLAC__StreamMetadata_StreamInfo *stream_info)
|
||||
@@ -66,22 +98,10 @@ flac_got_stream_info(struct flac_data *data,
|
||||
if (data->initialized || data->unsupported)
|
||||
return;
|
||||
|
||||
Error error;
|
||||
if (!audio_format_init_checked(data->audio_format,
|
||||
stream_info->sample_rate,
|
||||
flac_sample_format(stream_info->bits_per_sample),
|
||||
stream_info->channels, error)) {
|
||||
LogError(error);
|
||||
data->unsupported = true;
|
||||
return;
|
||||
}
|
||||
|
||||
data->frame_size = data->audio_format.GetFrameSize();
|
||||
|
||||
if (data->total_frames == 0)
|
||||
data->total_frames = stream_info->total_samples;
|
||||
|
||||
data->initialized = true;
|
||||
data->Initialize(stream_info->sample_rate,
|
||||
stream_info->bits_per_sample,
|
||||
stream_info->channels,
|
||||
stream_info->total_samples);
|
||||
}
|
||||
|
||||
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
|
||||
@@ -125,28 +145,11 @@ flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header)
|
||||
if (data->unsupported)
|
||||
return false;
|
||||
|
||||
Error error;
|
||||
if (!audio_format_init_checked(data->audio_format,
|
||||
header->sample_rate,
|
||||
flac_sample_format(header->bits_per_sample),
|
||||
header->channels, error)) {
|
||||
LogError(error);
|
||||
data->unsupported = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
data->frame_size = data->audio_format.GetFrameSize();
|
||||
|
||||
const auto duration = SongTime::FromScale<uint64_t>(data->total_frames,
|
||||
data->audio_format.sample_rate);
|
||||
|
||||
decoder_initialized(data->decoder, data->audio_format,
|
||||
data->input_stream.IsSeekable(),
|
||||
duration);
|
||||
|
||||
data->initialized = true;
|
||||
|
||||
return true;
|
||||
return data->Initialize(header->sample_rate,
|
||||
header->bits_per_sample,
|
||||
header->channels,
|
||||
/* unknown duration */
|
||||
0);
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderWriteStatus
|
||||
@@ -155,7 +158,6 @@ flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
|
||||
FLAC__uint64 nbytes)
|
||||
{
|
||||
void *buffer;
|
||||
unsigned bit_rate;
|
||||
|
||||
if (!data->initialized && !flac_got_first_frame(data, &frame->header))
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
@@ -167,16 +169,12 @@ flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
|
||||
data->audio_format.format, buf,
|
||||
0, frame->header.blocksize);
|
||||
|
||||
if (nbytes > 0)
|
||||
bit_rate = nbytes * 8 * frame->header.sample_rate /
|
||||
(1000 * frame->header.blocksize);
|
||||
else
|
||||
bit_rate = 0;
|
||||
unsigned bit_rate = nbytes * 8 * frame->header.sample_rate /
|
||||
(1000 * frame->header.blocksize);
|
||||
|
||||
auto cmd = decoder_data(data->decoder, data->input_stream,
|
||||
buffer, buffer_size,
|
||||
bit_rate);
|
||||
data->next_frame += frame->header.blocksize;
|
||||
switch (cmd) {
|
||||
case DecoderCommand::NONE:
|
||||
case DecoderCommand::START:
|
||||
|
@@ -55,23 +55,9 @@ struct flac_data : public FlacInput {
|
||||
AudioFormat audio_format;
|
||||
|
||||
/**
|
||||
* The total number of frames in this song. The decoder
|
||||
* plugin may initialize this attribute to override the value
|
||||
* provided by libFLAC (e.g. for sub songs from a CUE sheet).
|
||||
* End of last frame's position within the stream. This is
|
||||
* used for bit rate calculations.
|
||||
*/
|
||||
FLAC__uint64 total_frames;
|
||||
|
||||
/**
|
||||
* The number of the first frame in this song. This is only
|
||||
* non-zero if playing sub songs from a CUE sheet.
|
||||
*/
|
||||
FLAC__uint64 first_frame;
|
||||
|
||||
/**
|
||||
* The number of the next frame which is going to be decoded.
|
||||
*/
|
||||
FLAC__uint64 next_frame;
|
||||
|
||||
FLAC__uint64 position;
|
||||
|
||||
Decoder &decoder;
|
||||
@@ -80,6 +66,12 @@ struct flac_data : public FlacInput {
|
||||
Tag tag;
|
||||
|
||||
flac_data(Decoder &decoder, InputStream &input_stream);
|
||||
|
||||
/**
|
||||
* Wrapper for decoder_initialized().
|
||||
*/
|
||||
bool Initialize(unsigned sample_rate, unsigned bits_per_sample,
|
||||
unsigned channels, FLAC__uint64 total_frames);
|
||||
};
|
||||
|
||||
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
|
||||
|
@@ -132,26 +132,16 @@ flac_decoder_new(void)
|
||||
}
|
||||
|
||||
static bool
|
||||
flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd,
|
||||
FLAC__uint64 duration)
|
||||
flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd)
|
||||
{
|
||||
data->total_frames = duration;
|
||||
|
||||
if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) {
|
||||
LogWarning(flac_domain, "problem reading metadata");
|
||||
if (FLAC__stream_decoder_get_state(sd) != FLAC__STREAM_DECODER_END_OF_STREAM)
|
||||
LogWarning(flac_domain, "problem reading metadata");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data->initialized) {
|
||||
/* done */
|
||||
|
||||
const auto duration2 =
|
||||
SongTime::FromScale<uint64_t>(data->total_frames,
|
||||
data->audio_format.sample_rate);
|
||||
|
||||
decoder_initialized(data->decoder, data->audio_format,
|
||||
data->input_stream.IsSeekable(),
|
||||
duration2);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -167,13 +157,10 @@ flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd,
|
||||
}
|
||||
|
||||
static void
|
||||
flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec,
|
||||
FLAC__uint64 t_start, FLAC__uint64 t_end)
|
||||
flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec)
|
||||
{
|
||||
Decoder &decoder = data->decoder;
|
||||
|
||||
data->first_frame = t_start;
|
||||
|
||||
while (true) {
|
||||
DecoderCommand cmd;
|
||||
if (!data->tag.IsEmpty()) {
|
||||
@@ -184,24 +171,49 @@ flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec,
|
||||
cmd = decoder_get_command(decoder);
|
||||
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
FLAC__uint64 seek_sample = t_start +
|
||||
FLAC__uint64 seek_sample =
|
||||
decoder_seek_where_frame(decoder);
|
||||
if (seek_sample >= t_start &&
|
||||
(t_end == 0 || seek_sample <= t_end) &&
|
||||
FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) {
|
||||
data->next_frame = seek_sample;
|
||||
if (FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) {
|
||||
data->position = 0;
|
||||
decoder_command_finished(decoder);
|
||||
} else
|
||||
decoder_seek_error(decoder);
|
||||
} else if (cmd == DecoderCommand::STOP ||
|
||||
FLAC__stream_decoder_get_state(flac_dec) == FLAC__STREAM_DECODER_END_OF_STREAM)
|
||||
} else if (cmd == DecoderCommand::STOP)
|
||||
break;
|
||||
|
||||
if (t_end != 0 && data->next_frame >= t_end)
|
||||
/* end of this sub track */
|
||||
switch (FLAC__stream_decoder_get_state(flac_dec)) {
|
||||
case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA:
|
||||
case FLAC__STREAM_DECODER_READ_METADATA:
|
||||
case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC:
|
||||
case FLAC__STREAM_DECODER_READ_FRAME:
|
||||
/* continue decoding */
|
||||
break;
|
||||
|
||||
case FLAC__STREAM_DECODER_END_OF_STREAM:
|
||||
/* regular end of stream */
|
||||
return;
|
||||
|
||||
case FLAC__STREAM_DECODER_SEEK_ERROR:
|
||||
/* try to recover from seek error */
|
||||
if (!FLAC__stream_decoder_flush(flac_dec)) {
|
||||
LogError(flac_domain, "FLAC__stream_decoder_flush() failed");
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case FLAC__STREAM_DECODER_OGG_ERROR:
|
||||
case FLAC__STREAM_DECODER_ABORTED:
|
||||
case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
|
||||
/* an error, fatal enough for us to abort the
|
||||
decoder */
|
||||
return;
|
||||
|
||||
case FLAC__STREAM_DECODER_UNINITIALIZED:
|
||||
/* we shouldn't see this, ever - bail out */
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FLAC__stream_decoder_process_single(flac_dec) &&
|
||||
decoder_get_command(decoder) == DecoderCommand::NONE) {
|
||||
/* a failure that was not triggered by a
|
||||
@@ -250,6 +262,24 @@ stream_init(FLAC__StreamDecoder *flac_dec, struct flac_data *data, bool is_ogg)
|
||||
: stream_init_flac(flac_dec, data);
|
||||
}
|
||||
|
||||
static bool
|
||||
FlacInitAndDecode(struct flac_data &data, FLAC__StreamDecoder *sd, bool is_ogg)
|
||||
{
|
||||
auto init_status = stream_init(sd, &data, is_ogg);
|
||||
if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
|
||||
LogWarning(flac_domain,
|
||||
FLAC__StreamDecoderInitStatusString[init_status]);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = flac_decoder_initialize(&data, sd);
|
||||
if (result)
|
||||
flac_decoder_loop(&data, sd);
|
||||
|
||||
FLAC__stream_decoder_finish(sd);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
flac_decode_internal(Decoder &decoder,
|
||||
InputStream &input_stream,
|
||||
@@ -263,24 +293,8 @@ flac_decode_internal(Decoder &decoder,
|
||||
|
||||
struct flac_data data(decoder, input_stream);
|
||||
|
||||
FLAC__StreamDecoderInitStatus status =
|
||||
stream_init(flac_dec, &data, is_ogg);
|
||||
if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
|
||||
FLAC__stream_decoder_delete(flac_dec);
|
||||
LogWarning(flac_domain,
|
||||
FLAC__StreamDecoderInitStatusString[status]);
|
||||
return;
|
||||
}
|
||||
FlacInitAndDecode(data, flac_dec, is_ogg);
|
||||
|
||||
if (!flac_decoder_initialize(&data, flac_dec, 0)) {
|
||||
FLAC__stream_decoder_finish(flac_dec);
|
||||
FLAC__stream_decoder_delete(flac_dec);
|
||||
return;
|
||||
}
|
||||
|
||||
flac_decoder_loop(&data, flac_dec, 0, 0);
|
||||
|
||||
FLAC__stream_decoder_finish(flac_dec);
|
||||
FLAC__stream_decoder_delete(flac_dec);
|
||||
}
|
||||
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#include "config.h"
|
||||
#include "FlacIOHandle.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <errno.h>
|
||||
@@ -87,7 +88,13 @@ FlacIOSeek(FLAC__IOHandle handle, FLAC__int64 _offset, int whence)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return is->LockSeek(offset, IgnoreError()) ? 0 : -1;
|
||||
Error error;
|
||||
if (!is->LockSeek(offset, error)) {
|
||||
LogError(error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static FLAC__int64
|
||||
|
@@ -441,13 +441,15 @@ mpd_opus_scan_stream(InputStream &is,
|
||||
if (!oy.ExpectFirstPage(os))
|
||||
return false;
|
||||
|
||||
/* read at most two more pages */
|
||||
unsigned remaining_pages = 2;
|
||||
/* read at most 64 more pages */
|
||||
unsigned remaining_pages = 64;
|
||||
|
||||
unsigned remaining_packets = 4;
|
||||
|
||||
bool result = false;
|
||||
|
||||
ogg_packet packet;
|
||||
while (true) {
|
||||
while (remaining_packets > 0) {
|
||||
int r = ogg_stream_packetout(&os, &packet);
|
||||
if (r < 0) {
|
||||
result = false;
|
||||
@@ -466,6 +468,8 @@ mpd_opus_scan_stream(InputStream &is,
|
||||
continue;
|
||||
}
|
||||
|
||||
--remaining_packets;
|
||||
|
||||
if (packet.b_o_s) {
|
||||
if (!IsOpusHead(packet))
|
||||
break;
|
||||
|
@@ -85,7 +85,7 @@ public:
|
||||
|
||||
char *ReadString() {
|
||||
uint32_t length;
|
||||
if (!ReadWord(length))
|
||||
if (!ReadWord(length) || length >= 65536)
|
||||
return nullptr;
|
||||
|
||||
const char *src = (const char *)Read(length);
|
||||
|
@@ -27,6 +27,8 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
static constexpr uint16_t WAVE_FORMAT_PCM = 1;
|
||||
|
||||
struct WaveEncoder {
|
||||
Encoder encoder;
|
||||
unsigned bits;
|
||||
@@ -64,15 +66,15 @@ fill_wave_header(struct wave_header *header, int channels, int bits,
|
||||
header->id_fmt = ToLE32(0x20746d66);
|
||||
header->id_data = ToLE32(0x61746164);
|
||||
|
||||
/* wave format */
|
||||
header->format = ToLE16(1); // PCM_FORMAT
|
||||
/* wave format */
|
||||
header->format = ToLE16(WAVE_FORMAT_PCM);
|
||||
header->channels = ToLE16(channels);
|
||||
header->bits = ToLE16(bits);
|
||||
header->freq = ToLE32(freq);
|
||||
header->blocksize = ToLE16(block_size);
|
||||
header->byterate = ToLE32(freq * block_size);
|
||||
|
||||
/* chunk sizes (fake data length) */
|
||||
/* chunk sizes (fake data length) */
|
||||
header->fmt_size = ToLE32(16);
|
||||
header->data_size = ToLE32(data_size);
|
||||
header->riff_size = ToLE32(4 + (8 + 16) + (8 + data_size));
|
||||
|
@@ -118,9 +118,15 @@ BufferedSocket::OnSocketReady(unsigned flags)
|
||||
if (flags & READ) {
|
||||
assert(!input.IsFull());
|
||||
|
||||
if (!ReadToBuffer() || !ResumeInput())
|
||||
if (!ReadToBuffer())
|
||||
return false;
|
||||
|
||||
if (!ResumeInput())
|
||||
/* we must return "true" here or
|
||||
SocketMonitor::Dispatch() will call
|
||||
Cancel() on a freed object */
|
||||
return true;
|
||||
|
||||
if (!input.IsFull())
|
||||
ScheduleRead();
|
||||
}
|
||||
|
@@ -134,8 +134,6 @@ ReplayGainFilter::Update()
|
||||
volume = pcm_float_to_volume(scale);
|
||||
}
|
||||
|
||||
pv.SetVolume(volume);
|
||||
|
||||
if (mixer != nullptr) {
|
||||
/* update the hardware mixer volume */
|
||||
|
||||
@@ -146,7 +144,8 @@ ReplayGainFilter::Update()
|
||||
Error error;
|
||||
if (!mixer_set_volume(mixer, _volume, error))
|
||||
LogError(error, "Failed to update hardware mixer");
|
||||
}
|
||||
} else
|
||||
pv.SetVolume(volume);
|
||||
}
|
||||
|
||||
static Filter *
|
||||
@@ -174,7 +173,9 @@ ReplayGainFilter::Close()
|
||||
ConstBuffer<void>
|
||||
ReplayGainFilter::FilterPCM(ConstBuffer<void> src, gcc_unused Error &error)
|
||||
{
|
||||
return pv.Apply(src);
|
||||
return mixer != nullptr
|
||||
? src
|
||||
: pv.Apply(src);
|
||||
}
|
||||
|
||||
const struct filter_plugin replay_gain_filter_plugin = {
|
||||
|
@@ -47,9 +47,11 @@
|
||||
#include "filter/FilterInternal.hxx"
|
||||
#include "filter/FilterRegistry.hxx"
|
||||
#include "pcm/PcmBuffer.hxx"
|
||||
#include "pcm/Silence.hxx"
|
||||
#include "util/StringUtil.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/WritableBuffer.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -266,9 +268,8 @@ RouteFilter::FilterPCM(ConstBuffer<void> src, gcc_unused Error &error)
|
||||
(unsigned)sources[c] >= input_format.channels) {
|
||||
// No source for this destination output,
|
||||
// give it zeroes as input
|
||||
memset(chan_destination,
|
||||
0x00,
|
||||
bytes_per_frame_per_channel);
|
||||
PcmSilence({chan_destination, bytes_per_frame_per_channel},
|
||||
input_format.format);
|
||||
} else {
|
||||
// Get the data from channel sources[c]
|
||||
// and copy it to the output
|
||||
|
@@ -218,7 +218,7 @@ PulseMixer::SetVolume(unsigned new_volume, Error &error)
|
||||
|
||||
struct pa_cvolume cvolume;
|
||||
pa_cvolume_set(&cvolume, volume.channels,
|
||||
(pa_volume_t)new_volume * PA_VOLUME_NORM / 100 + 0.5);
|
||||
(new_volume * PA_VOLUME_NORM + 50) / 100);
|
||||
bool success = pulse_output_set_volume(output, &cvolume, error);
|
||||
if (success)
|
||||
volume = cvolume;
|
||||
|
@@ -28,7 +28,7 @@ struct notify {
|
||||
Cond cond;
|
||||
bool pending;
|
||||
|
||||
#if !defined(WIN32) && !defined(__NetBSD__) && !defined(__BIONIC__)
|
||||
#ifdef __GLIBC__
|
||||
constexpr
|
||||
#endif
|
||||
notify():pending(false) {}
|
||||
|
@@ -247,7 +247,6 @@ ShoutOutput::Configure(const config_param ¶m, Error &error)
|
||||
|
||||
{
|
||||
char temp[11];
|
||||
memset(temp, 0, sizeof(temp));
|
||||
|
||||
snprintf(temp, sizeof(temp), "%u", audio_format.channels);
|
||||
shout_set_audio_info(shout_conn, SHOUT_AI_CHANNELS, temp);
|
||||
|
35
src/pcm/Silence.cxx
Normal file
35
src/pcm/Silence.cxx
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2016 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 "Silence.hxx"
|
||||
#include "AudioFormat.hxx"
|
||||
#include "util/WritableBuffer.hxx"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
void
|
||||
PcmSilence(WritableBuffer<void> dest, SampleFormat format)
|
||||
{
|
||||
uint8_t pattern = 0;
|
||||
if (format == SampleFormat::DSD)
|
||||
pattern = 0x69;
|
||||
|
||||
memset(dest.data, pattern, dest.size);
|
||||
}
|
36
src/pcm/Silence.hxx
Normal file
36
src/pcm/Silence.hxx
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2016 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_PCM_SILENCE_HXX
|
||||
#define MPD_PCM_SILENCE_HXX
|
||||
|
||||
#include "check.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
template<typename T> struct WritableBuffer;
|
||||
enum class SampleFormat : uint8_t;
|
||||
|
||||
/**
|
||||
* Fill the given buffer with the format-specific silence pattern.
|
||||
*/
|
||||
void
|
||||
PcmSilence(WritableBuffer<void> dest, SampleFormat format);
|
||||
|
||||
#endif
|
@@ -19,10 +19,12 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "Volume.hxx"
|
||||
#include "Silence.hxx"
|
||||
#include "Domain.hxx"
|
||||
#include "PcmUtils.hxx"
|
||||
#include "Traits.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/WritableBuffer.hxx"
|
||||
#include "util/Error.hxx"
|
||||
|
||||
#include "PcmDither.cxx" // including the .cxx file to get inlined templates
|
||||
@@ -134,9 +136,7 @@ PcmVolume::Apply(ConstBuffer<void> src)
|
||||
|
||||
if (volume == 0) {
|
||||
/* optimized special case: 0% volume = memset(0) */
|
||||
/* TODO: is this valid for all sample formats? What
|
||||
about floating point? */
|
||||
memset(data, 0, src.size);
|
||||
PcmSilence({data, src.size}, format);
|
||||
return { data, src.size };
|
||||
}
|
||||
|
||||
|
@@ -92,7 +92,7 @@ check_range(Client &client, unsigned *value_r1, unsigned *value_r2,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (unsigned(value) > std::numeric_limits<unsigned>::max()) {
|
||||
if (value > std::numeric_limits<int>::max()) {
|
||||
command_error(client, ACK_ERROR_ARG,
|
||||
"Number too large: %s", s);
|
||||
return false;
|
||||
@@ -117,7 +117,7 @@ check_range(Client &client, unsigned *value_r1, unsigned *value_r2,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (unsigned(value) > std::numeric_limits<unsigned>::max()) {
|
||||
if (value > std::numeric_limits<int>::max()) {
|
||||
command_error(client, ACK_ERROR_ARG,
|
||||
"Number too large: %s", s);
|
||||
return false;
|
||||
|
@@ -23,19 +23,23 @@
|
||||
#include "util/Cast.hxx"
|
||||
#include "util/VarSize.hxx"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
Mutex tag_pool_lock;
|
||||
|
||||
static constexpr size_t NUM_SLOTS = 4096;
|
||||
static constexpr size_t NUM_SLOTS = 4093;
|
||||
|
||||
struct TagPoolSlot {
|
||||
TagPoolSlot *next;
|
||||
unsigned char ref;
|
||||
TagItem item;
|
||||
|
||||
static constexpr unsigned MAX_REF = std::numeric_limits<decltype(ref)>::max();
|
||||
|
||||
TagPoolSlot(TagPoolSlot *_next, TagType type,
|
||||
const char *value, size_t length)
|
||||
:next(_next), ref(1) {
|
||||
@@ -116,7 +120,7 @@ tag_pool_get_item(TagType type, const char *value, size_t length)
|
||||
if (slot->item.type == type &&
|
||||
length == strlen(slot->item.value) &&
|
||||
memcmp(value, slot->item.value, length) == 0 &&
|
||||
slot->ref < 0xff) {
|
||||
slot->ref < TagPoolSlot::MAX_REF) {
|
||||
assert(slot->ref > 0);
|
||||
++slot->ref;
|
||||
return &slot->item;
|
||||
@@ -135,19 +139,15 @@ tag_pool_dup_item(TagItem *item)
|
||||
|
||||
assert(slot->ref > 0);
|
||||
|
||||
if (slot->ref < 0xff) {
|
||||
if (slot->ref < TagPoolSlot::MAX_REF) {
|
||||
++slot->ref;
|
||||
return item;
|
||||
} else {
|
||||
/* the reference counter overflows above 0xff;
|
||||
duplicate the item, and start with 1 */
|
||||
/* the reference counter overflows above MAX_REF;
|
||||
obtain a reference to a different TagPoolSlot which
|
||||
isn't yet "full" */
|
||||
size_t length = strlen(item->value);
|
||||
auto slot_p = tag_value_slot_p(item->type,
|
||||
item->value, length);
|
||||
slot = TagPoolSlot::Create(*slot_p, item->type,
|
||||
item->value, strlen(item->value));
|
||||
*slot_p = slot;
|
||||
return &slot->item;
|
||||
return tag_pool_get_item(item->type, item->value, length);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -41,9 +41,13 @@ class PosixCond {
|
||||
pthread_cond_t cond;
|
||||
|
||||
public:
|
||||
#if defined(__NetBSD__) || defined(__BIONIC__)
|
||||
/* NetBSD's PTHREAD_COND_INITIALIZER is not compatible with
|
||||
"constexpr" */
|
||||
#ifdef __GLIBC__
|
||||
/* optimized constexpr constructor for pthread implementations
|
||||
that support it */
|
||||
constexpr PosixCond():cond(PTHREAD_COND_INITIALIZER) {}
|
||||
#else
|
||||
/* slow fallback for pthread implementations that are not
|
||||
compatible with "constexpr" */
|
||||
PosixCond() {
|
||||
pthread_cond_init(&cond, nullptr);
|
||||
}
|
||||
@@ -51,10 +55,6 @@ public:
|
||||
~PosixCond() {
|
||||
pthread_cond_destroy(&cond);
|
||||
}
|
||||
#else
|
||||
/* optimized constexpr constructor for sane POSIX
|
||||
implementations */
|
||||
constexpr PosixCond():cond(PTHREAD_COND_INITIALIZER) {}
|
||||
#endif
|
||||
|
||||
PosixCond(const PosixCond &other) = delete;
|
||||
|
@@ -41,9 +41,13 @@ class PosixMutex {
|
||||
pthread_mutex_t mutex;
|
||||
|
||||
public:
|
||||
#if defined(__NetBSD__) || defined(__BIONIC__)
|
||||
/* NetBSD's PTHREAD_MUTEX_INITIALIZER is not compatible with
|
||||
"constexpr" */
|
||||
#ifdef __GLIBC__
|
||||
/* optimized constexpr constructor for pthread implementations
|
||||
that support it */
|
||||
constexpr PosixMutex():mutex(PTHREAD_MUTEX_INITIALIZER) {}
|
||||
#else
|
||||
/* slow fallback for pthread implementations that are not
|
||||
compatible with "constexpr" */
|
||||
PosixMutex() {
|
||||
pthread_mutex_init(&mutex, nullptr);
|
||||
}
|
||||
@@ -51,10 +55,6 @@ public:
|
||||
~PosixMutex() {
|
||||
pthread_mutex_destroy(&mutex);
|
||||
}
|
||||
#else
|
||||
/* optimized constexpr constructor for sane POSIX
|
||||
implementations */
|
||||
constexpr PosixMutex():mutex(PTHREAD_MUTEX_INITIALIZER) {}
|
||||
#endif
|
||||
|
||||
PosixMutex(const PosixMutex &other) = delete;
|
||||
|
@@ -46,7 +46,7 @@ static size_t
|
||||
AlignToPageSize(size_t size)
|
||||
{
|
||||
static const long page_size = sysconf(_SC_PAGESIZE);
|
||||
if (page_size > 0)
|
||||
if (page_size == 0)
|
||||
return size;
|
||||
|
||||
size_t ps(page_size);
|
||||
|
@@ -49,9 +49,9 @@ CPPUNIT_TEST_SUITE_REGISTRATION(ByteReverseTest);
|
||||
void
|
||||
ByteReverseTest::TestByteReverse2()
|
||||
{
|
||||
static const char src[] = "123456";
|
||||
static const char src[] gcc_alignas(uint16_t, 2) = "123456";
|
||||
static const char result[] = "214365";
|
||||
static uint8_t dest[ARRAY_SIZE(src)];
|
||||
static uint8_t dest[ARRAY_SIZE(src)] gcc_alignas(uint16_t, 2);
|
||||
|
||||
reverse_bytes(dest, (const uint8_t *)src,
|
||||
(const uint8_t *)(src + ARRAY_SIZE(src) - 1), 2);
|
||||
@@ -73,9 +73,9 @@ ByteReverseTest::TestByteReverse3()
|
||||
void
|
||||
ByteReverseTest::TestByteReverse4()
|
||||
{
|
||||
static const char src[] = "12345678";
|
||||
static const char src[] gcc_alignas(uint32_t, 4) = "12345678";
|
||||
static const char result[] = "43218765";
|
||||
static uint8_t dest[ARRAY_SIZE(src)];
|
||||
static uint8_t dest[ARRAY_SIZE(src)] gcc_alignas(uint32_t, 4);
|
||||
|
||||
reverse_bytes(dest, (const uint8_t *)src,
|
||||
(const uint8_t *)(src + ARRAY_SIZE(src) - 1), 4);
|
||||
|
Reference in New Issue
Block a user