Compare commits
375 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2b97b124bd | ||
![]() |
d042ab87da | ||
![]() |
588303b78d | ||
![]() |
36704c5e18 | ||
![]() |
5834843b8a | ||
![]() |
762f3afb9d | ||
![]() |
7fb2f15a1a | ||
![]() |
7456dccd3a | ||
![]() |
245f41bb7e | ||
![]() |
9bfb844cfa | ||
![]() |
d790d3ba3c | ||
![]() |
c3dbc92766 | ||
![]() |
0bd25f1e17 | ||
![]() |
a4cd7411e8 | ||
![]() |
bf276f6235 | ||
![]() |
d916890a8f | ||
![]() |
071cacc9a4 | ||
![]() |
33f33323af | ||
![]() |
388fae2c47 | ||
![]() |
9f878b77e9 | ||
![]() |
a547d2aaba | ||
![]() |
c013026821 | ||
![]() |
96b48a2404 | ||
![]() |
9612975c2c | ||
![]() |
41bfd45a2e | ||
![]() |
bbdcbd1f08 | ||
![]() |
6b3c525a9d | ||
![]() |
83aed7051c | ||
![]() |
77c6e45e65 | ||
![]() |
8825393660 | ||
![]() |
2b9246c6ad | ||
![]() |
a9edb4de28 | ||
![]() |
a076ddf38c | ||
![]() |
cafc266e0b | ||
![]() |
a3d020eff9 | ||
![]() |
8412d94d05 | ||
![]() |
d1c5bb956a | ||
![]() |
70986bc120 | ||
![]() |
f31fe8b865 | ||
![]() |
142a9fe530 | ||
![]() |
4dd2ad9b27 | ||
![]() |
62f7375804 | ||
![]() |
543296b5ba | ||
![]() |
5fee130d00 | ||
![]() |
073facea70 | ||
![]() |
dbe3b6eee4 | ||
![]() |
df97049647 | ||
![]() |
42c5f68362 | ||
![]() |
cc19e760cf | ||
![]() |
0ff22a16fa | ||
![]() |
47360ec906 | ||
![]() |
087a9938d2 | ||
![]() |
26d8e41a6b | ||
![]() |
750ae1d3f3 | ||
![]() |
f8a9a7a108 | ||
![]() |
eb192137d6 | ||
![]() |
c25b464f37 | ||
![]() |
710b48d410 | ||
![]() |
5e77a8199d | ||
![]() |
6637db086b | ||
![]() |
a271a55da7 | ||
![]() |
6eeec6cbfa | ||
![]() |
5e3f3b0400 | ||
![]() |
923c402f69 | ||
![]() |
4fed0b991c | ||
![]() |
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 | ||
![]() |
79f2f8cddc | ||
![]() |
39fa949345 | ||
![]() |
e1d7a5cbf5 | ||
![]() |
f3cefaf043 | ||
![]() |
b3460f3f54 | ||
![]() |
1e0ad1f6bf | ||
![]() |
4abcb08cc9 | ||
![]() |
81e7833711 | ||
![]() |
82e261ad33 | ||
![]() |
cae2811762 | ||
![]() |
09112c6869 | ||
![]() |
77aaf1baee | ||
![]() |
6626c2d00d | ||
![]() |
315f9d98f6 | ||
![]() |
f087518e7a | ||
![]() |
db9997a106 | ||
![]() |
0cbfb610f2 | ||
![]() |
f901cd042b | ||
![]() |
5719207dfa | ||
![]() |
a84fbbe327 | ||
![]() |
93c97972b9 | ||
![]() |
ac61d43720 | ||
![]() |
1958f78cc1 | ||
![]() |
a7ee64a25b | ||
![]() |
2a58f22649 | ||
![]() |
f066bb7716 | ||
![]() |
4e3d182189 | ||
![]() |
205fba74cf | ||
![]() |
a9bcf8d50d | ||
![]() |
b0ff3bc7a3 | ||
![]() |
06301e279c | ||
![]() |
6d6f274648 | ||
![]() |
9acefcb256 | ||
![]() |
e4d0293a31 | ||
![]() |
ae77542a11 | ||
![]() |
980187f856 | ||
![]() |
327a8e6c59 | ||
![]() |
d11e2724c4 | ||
![]() |
f768ca3a2d | ||
![]() |
947e902288 | ||
![]() |
3436a646b5 | ||
![]() |
aed0af1e00 | ||
![]() |
0d7ee2b014 | ||
![]() |
2f5fd91bd8 | ||
![]() |
5761800197 | ||
![]() |
0eebacc521 | ||
![]() |
4a5528697d | ||
![]() |
d38034bb5c | ||
![]() |
b3fe3e8b3d | ||
![]() |
5489dec28d | ||
![]() |
8a6b4db19f | ||
![]() |
df43b6a05c | ||
![]() |
3adca3c2fa | ||
![]() |
39abd3ecb4 | ||
![]() |
a4f4fc50b9 | ||
![]() |
7bf638b0de | ||
![]() |
56662a703c | ||
![]() |
8b5f47d3a3 | ||
![]() |
a289dcb9ee | ||
![]() |
023b9c1e7e | ||
![]() |
4c61662644 | ||
![]() |
ad1b6ef0ac | ||
![]() |
ed5c6be2f1 | ||
![]() |
30cb082932 | ||
![]() |
645554d12f | ||
![]() |
212b0faf0c | ||
![]() |
276a0d9500 | ||
![]() |
384b6c8288 | ||
![]() |
a2af158fd3 | ||
![]() |
f33d2fb2e7 | ||
![]() |
a9eec35aff | ||
![]() |
8534f2d1e2 | ||
![]() |
00740fb23b | ||
![]() |
37e9010887 | ||
![]() |
4bd2c75056 | ||
![]() |
b9ed850b98 | ||
![]() |
11cea17496 | ||
![]() |
163597ef69 | ||
![]() |
95f84afd33 | ||
![]() |
9f7fd1fbfb | ||
![]() |
940cab8620 | ||
![]() |
5b84c99d79 | ||
![]() |
b295024574 | ||
![]() |
34180f1745 | ||
![]() |
665031467a | ||
![]() |
df33171107 | ||
![]() |
53f4044890 | ||
![]() |
a5049136ff | ||
![]() |
705b3c6b63 | ||
![]() |
6b4ac66962 | ||
![]() |
0964b06240 | ||
![]() |
92eeca3ba7 | ||
![]() |
2a86554ac4 | ||
![]() |
805caa30ce | ||
![]() |
a56949e9fa | ||
![]() |
43da4c0eca | ||
![]() |
b9c7771830 | ||
![]() |
35db88affe | ||
![]() |
e38faca455 | ||
![]() |
0255e8710c | ||
![]() |
6d89020f80 | ||
![]() |
9c56c49e73 | ||
![]() |
acb798e544 | ||
![]() |
c5720a15c7 | ||
![]() |
90709b332a | ||
![]() |
81f17d10c8 | ||
![]() |
773de38bd9 | ||
![]() |
a48704925d | ||
![]() |
fa4beeee75 | ||
![]() |
d8351772d3 | ||
![]() |
68d1abdb85 | ||
![]() |
7e8474a85a | ||
![]() |
82da364b8b | ||
![]() |
7fa91ec175 | ||
![]() |
1d3a09d377 | ||
![]() |
02563a35f0 | ||
![]() |
d653f35bb7 | ||
![]() |
a543627abd | ||
![]() |
80f2ba7fca | ||
![]() |
32bca64920 | ||
![]() |
7fa1a84ec3 | ||
![]() |
ab4bb26a0a | ||
![]() |
4b8d258cff | ||
![]() |
3c29aa6271 | ||
![]() |
51464b4317 | ||
![]() |
2fec463542 | ||
![]() |
1affc641c4 | ||
![]() |
0cfd4fff62 | ||
![]() |
8904127c10 | ||
![]() |
c46f48abec | ||
![]() |
4acbf7b90d | ||
![]() |
cbc1a58e93 | ||
![]() |
1b5f33a435 | ||
![]() |
41b4a63f2b | ||
![]() |
d8fc2db910 | ||
![]() |
dc11dea7cc | ||
![]() |
811af02f56 | ||
![]() |
8780e23ed3 | ||
![]() |
be492ed108 | ||
![]() |
24da14f4f7 | ||
![]() |
03d2fb450f | ||
![]() |
67cba251c8 | ||
![]() |
0bc511715b | ||
![]() |
27ce80544f | ||
![]() |
04f627c2af | ||
![]() |
e72eef421b | ||
![]() |
016063c810 | ||
![]() |
38f19981b2 | ||
![]() |
40dd968f13 | ||
![]() |
3cef348f30 | ||
![]() |
b293b16007 | ||
![]() |
f5f43db2da | ||
![]() |
029555d192 | ||
![]() |
fa4d202e71 | ||
![]() |
a8ebfd7a92 | ||
![]() |
b19e5720cc | ||
![]() |
a254f5a3a8 | ||
![]() |
143c735f96 | ||
![]() |
951bad46e0 | ||
![]() |
716225cd2f | ||
![]() |
bbc618b8f9 | ||
![]() |
11ead56d6d | ||
![]() |
e972ae4afa | ||
![]() |
0709065f50 | ||
![]() |
d6bc5c35a7 | ||
![]() |
dc03f003ac | ||
![]() |
7aa2104596 | ||
![]() |
460cfba6ff | ||
![]() |
c8b93d6573 | ||
![]() |
3f5f96ac91 | ||
![]() |
7c6b991de7 | ||
![]() |
82460aa49f | ||
![]() |
7e7b403043 | ||
![]() |
e5217e6ce9 | ||
![]() |
c98cb1d6f9 | ||
![]() |
ba6f2b0467 | ||
![]() |
23465ad985 | ||
![]() |
7886a14b74 | ||
![]() |
466b6a23cd | ||
![]() |
4a04f73434 | ||
![]() |
134cb6a017 | ||
![]() |
8d036c4b7c | ||
![]() |
c64ad78c7b | ||
![]() |
4a043a915f | ||
![]() |
8ff0d99092 | ||
![]() |
2e47cb12c4 | ||
![]() |
ff6f1655f0 | ||
![]() |
b5ba94f1de | ||
![]() |
cbf79769d3 | ||
![]() |
125eb01e03 | ||
![]() |
ccb13205f4 | ||
![]() |
6f23e91e33 | ||
![]() |
1bd8a322f5 | ||
![]() |
362e73bea8 | ||
![]() |
9f8c2b3b56 | ||
![]() |
6a7f6cdacd | ||
![]() |
5715342fe0 | ||
![]() |
38a0d15190 | ||
![]() |
56f763a4a8 | ||
![]() |
a2eb14f3b3 | ||
![]() |
05c63af7c4 | ||
![]() |
1f59701c46 | ||
![]() |
ec3191f502 | ||
![]() |
32b5654a6e | ||
![]() |
674091424e | ||
![]() |
6ad336743d | ||
![]() |
c882568ccd | ||
![]() |
f6b2899dd2 | ||
![]() |
bccd4ef2f7 | ||
![]() |
94c240a026 | ||
![]() |
c50a0cf7bf | ||
![]() |
c37f7abb79 | ||
![]() |
432ce9b1de | ||
![]() |
054323c2bc | ||
![]() |
a8770aa606 | ||
![]() |
7d5442e103 | ||
![]() |
eab32f2e5d | ||
![]() |
d42c0f1dc5 | ||
![]() |
6ad1e4d99a | ||
![]() |
7350144ab3 | ||
![]() |
54c591bd9d | ||
![]() |
217d88f21f | ||
![]() |
394e3be482 | ||
![]() |
d7f024c510 | ||
![]() |
bc5a53574c | ||
![]() |
30df709736 | ||
![]() |
8cd17ce045 | ||
![]() |
1bfbced258 | ||
![]() |
6ac5980a17 | ||
![]() |
2e24adae89 | ||
![]() |
188b94cb3e | ||
![]() |
c48733e34f | ||
![]() |
f36db9bb04 | ||
![]() |
30dd29e251 | ||
![]() |
6cf1acfb48 | ||
![]() |
a7b09d3d1c | ||
![]() |
8fc3768166 | ||
![]() |
b07bddf742 | ||
![]() |
220f957cd8 | ||
![]() |
8ce48d83eb | ||
![]() |
200cdb6b0a | ||
![]() |
d9fb40203a | ||
![]() |
2d9e972195 |
.gitignoreINSTALLMakefile.amNEWS
android
configure.acdoc
m4
src
Compiler.hDetachedSong.hxxIdle.cxxLogBackend.cxxLogInit.cxxMain.cxxMain.hxxPlayerThread.cxxPlaylistFile.cxxSongFilter.cxxSongLoader.cxxTagStream.cxxls.cxx
archive
client
command
db
decoder
DecoderAPI.cxxDecoderBuffer.hxxDecoderList.cxxDecoderPlugin.cxxDecoderThread.cxx
plugins
AudiofileDecoderPlugin.cxxDsdLib.cxxDsdiffDecoderPlugin.cxxFaadDecoderPlugin.cxxFfmpegDecoderPlugin.cxxFfmpegIo.cxxFfmpegIo.hxxFfmpegMetaData.cxxFfmpegMetaData.hxxFlacCommon.cxxFlacCommon.hxxFlacDecoderPlugin.cxxFlacIOHandle.cxxGmeDecoderPlugin.cxxMadDecoderPlugin.cxxMp4v2DecoderPlugin.cxxMpcdecDecoderPlugin.cxxOpusDecoderPlugin.cxxOpusReader.hxxSidplayDecoderPlugin.cxx
encoder
event
filter
plugins
fs
input
AsyncInputStream.cxxAsyncInputStream.hxxDomain.cxxDomain.hxxInit.cxxInputStream.cxxInputStream.hxxOpen.cxxRegistry.cxxTextInputStream.cxx
plugins
java
lib
despotify
ffmpeg
icu
nfs
Blocking.cxxBlocking.hxxCancellable.hxxConnection.cxxConnection.hxxFileReader.cxxFileReader.hxxManager.cxxManager.hxx
upnp
mixer
notify.hxxoutput
pcm
ChannelsConverter.cxxFormatConverter.cxxInterleave.cxxInterleave.hxxPcmConvert.cxxSilence.cxxSilence.hxxSoxrResampler.cxxVolume.cxx
playlist
protocol
queue
storage
plugins
system
tag
thread
unix
util
systemd
test
2
.gitignore
vendored
2
.gitignore
vendored
@@ -77,6 +77,8 @@ tags
|
||||
/test/test_vorbis_encoder
|
||||
/test/DumpDatabase
|
||||
|
||||
/lib/
|
||||
|
||||
/*.tar.gz
|
||||
/*.tar.bz2
|
||||
/*.tar.xz
|
||||
|
8
INSTALL
8
INSTALL
@@ -12,7 +12,7 @@ install MPD. If more information is desired, read the user manual:
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
gcc 4.6 or later - http://gcc.gnu.org/
|
||||
gcc 4.7 or later - http://gcc.gnu.org/
|
||||
clang 3.2 or later - http://clang.llvm.org/
|
||||
Any other C++11 compliant compiler should also work.
|
||||
|
||||
@@ -116,12 +116,6 @@ For WavPack playback.
|
||||
libadplug - http://adplug.sourceforge.net/
|
||||
For AdLib playback.
|
||||
|
||||
despotify - https://github.com/SimonKagstrom/despotify
|
||||
For Spotify playback.
|
||||
|
||||
MP4v2 - https://code.google.com/p/mp4v2/
|
||||
For MP4 playback. You will need FAAD2.
|
||||
|
||||
|
||||
Optional Miscellaneous Dependencies
|
||||
-----------------------------------
|
||||
|
77
Makefile.am
77
Makefile.am
@@ -28,8 +28,6 @@ noinst_LIBRARIES = \
|
||||
libmixer_plugins.a \
|
||||
liboutput_plugins.a
|
||||
|
||||
libmpd_a_DEPENDENCIES =
|
||||
|
||||
libmpd_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(LIBMPDCLIENT_CFLAGS) \
|
||||
$(AVAHI_CFLAGS) \
|
||||
@@ -130,7 +128,6 @@ libmpd_a_SOURCES = \
|
||||
src/IOThread.cxx src/IOThread.hxx \
|
||||
src/Instance.cxx src/Instance.hxx \
|
||||
src/win32/Win32Main.cxx \
|
||||
src/osx/OSXMain.cxx \
|
||||
src/GlobalEvents.cxx src/GlobalEvents.hxx \
|
||||
src/MixRampInfo.hxx \
|
||||
src/MusicBuffer.cxx src/MusicBuffer.hxx \
|
||||
@@ -284,13 +281,13 @@ android/build/build.xml: android/AndroidManifest.xml
|
||||
ln -s $(abs_srcdir)/android/res/values android/build/res
|
||||
$(ANDROID_SDK)/tools/android update project --path android/build --target android-17
|
||||
|
||||
android/build/bin/classes/org/musicpd/Bridge.class: android/src/Bridge.java android/build/build.xml
|
||||
android/build/bin/classes/org/musicpd/Bridge.class: android/src/Bridge.java android/build/build.xml android/build/res/drawable/icon.png
|
||||
cd android/build && ant compile-jni-classes
|
||||
|
||||
android/build/include/org_musicpd_Bridge.h: android/build/bin/classes/org/musicpd/Bridge.class
|
||||
javah -classpath $(ANDROID_SDK)/platforms/android-17/android.jar:android/build/bin/classes -d $(@D) org.musicpd.Bridge
|
||||
|
||||
libmpd_a_DEPENDENCIES += android/build/include/org_musicpd_Bridge.h
|
||||
BUILT_SOURCES = android/build/include/org_musicpd_Bridge.h
|
||||
|
||||
android/build/libs/armeabi-v7a/libmpd.so: libmpd.so android/build/build.xml
|
||||
mkdir -p $(@D)
|
||||
@@ -362,6 +359,7 @@ libutil_a_SOURCES = \
|
||||
src/util/Clamp.hxx \
|
||||
src/util/Alloc.cxx src/util/Alloc.hxx \
|
||||
src/util/VarSize.hxx \
|
||||
src/util/ScopeExit.hxx \
|
||||
src/util/Error.cxx src/util/Error.hxx \
|
||||
src/util/Domain.hxx \
|
||||
src/util/ReusableArray.hxx \
|
||||
@@ -465,11 +463,13 @@ ICU_LDADD = libicu.a $(ICU_LIBS)
|
||||
libpcm_a_SOURCES = \
|
||||
src/pcm/Domain.cxx src/pcm/Domain.hxx \
|
||||
src/pcm/Traits.hxx \
|
||||
src/pcm/Interleave.cxx src/pcm/Interleave.hxx \
|
||||
src/pcm/PcmBuffer.cxx src/pcm/PcmBuffer.hxx \
|
||||
src/pcm/PcmExport.cxx src/pcm/PcmExport.hxx \
|
||||
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 \
|
||||
@@ -530,7 +530,7 @@ libfs_a_SOURCES = \
|
||||
src/fs/Traits.cxx src/fs/Traits.hxx \
|
||||
src/fs/Config.cxx src/fs/Config.hxx \
|
||||
src/fs/Charset.cxx src/fs/Charset.hxx \
|
||||
src/fs/Path.cxx src/fs/Path.hxx \
|
||||
src/fs/Path.cxx src/fs/Path2.cxx src/fs/Path.hxx \
|
||||
src/fs/AllocatedPath.cxx src/fs/AllocatedPath.hxx \
|
||||
src/fs/FileSystem.cxx src/fs/FileSystem.hxx \
|
||||
src/fs/StandardDirectory.cxx src/fs/StandardDirectory.hxx \
|
||||
@@ -801,6 +801,11 @@ endif
|
||||
if HAVE_FFMPEG
|
||||
noinst_LIBRARIES += libffmpeg.a
|
||||
libffmpeg_a_SOURCES = \
|
||||
src/lib/ffmpeg/Init.cxx src/lib/ffmpeg/Init.hxx \
|
||||
src/lib/ffmpeg/Time.hxx \
|
||||
src/lib/ffmpeg/Buffer.hxx \
|
||||
src/lib/ffmpeg/LogError.cxx src/lib/ffmpeg/LogError.hxx \
|
||||
src/lib/ffmpeg/LogCallback.cxx src/lib/ffmpeg/LogCallback.hxx \
|
||||
src/lib/ffmpeg/Error.cxx src/lib/ffmpeg/Error.hxx \
|
||||
src/lib/ffmpeg/Domain.cxx src/lib/ffmpeg/Domain.hxx
|
||||
libffmpeg_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
@@ -829,7 +834,6 @@ libdecoder_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(WAVPACK_CFLAGS) \
|
||||
$(MAD_CFLAGS) \
|
||||
$(MPG123_CFLAGS) \
|
||||
$(MP4V2_CFLAGS) \
|
||||
$(OPUS_CFLAGS) \
|
||||
$(FFMPEG_CFLAGS) \
|
||||
$(MPCDEC_CFLAGS) \
|
||||
@@ -849,7 +853,6 @@ DECODER_LIBS = \
|
||||
$(WAVPACK_LIBS) \
|
||||
$(MAD_LIBS) \
|
||||
$(MPG123_LIBS) \
|
||||
$(MP4V2_LIBS) \
|
||||
$(OPUS_LIBS) \
|
||||
$(FFMPEG_LIBS2) \
|
||||
$(MPCDEC_LIBS) \
|
||||
@@ -964,12 +967,6 @@ noinst_LIBRARIES += libmodplug_decoder_plugin.a
|
||||
DECODER_LIBS += libmodplug_decoder_plugin.a $(MODPLUG_LIBS)
|
||||
endif
|
||||
|
||||
if HAVE_MP4V2
|
||||
libdecoder_a_SOURCES += \
|
||||
src/decoder/plugins/Mp4v2DecoderPlugin.cxx \
|
||||
src/decoder/plugins/Mp4v2DecoderPlugin.hxx
|
||||
endif
|
||||
|
||||
if ENABLE_SIDPLAY
|
||||
libdecoder_a_SOURCES += \
|
||||
src/decoder/plugins/SidplayDecoderPlugin.cxx \
|
||||
@@ -990,6 +987,8 @@ endif
|
||||
|
||||
if HAVE_FFMPEG
|
||||
libdecoder_a_SOURCES += \
|
||||
src/decoder/plugins/FfmpegIo.cxx \
|
||||
src/decoder/plugins/FfmpegIo.hxx \
|
||||
src/decoder/plugins/FfmpegMetaData.cxx \
|
||||
src/decoder/plugins/FfmpegMetaData.hxx \
|
||||
src/decoder/plugins/FfmpegDecoderPlugin.cxx \
|
||||
@@ -1113,6 +1112,7 @@ endif
|
||||
#
|
||||
|
||||
libinput_a_SOURCES = \
|
||||
src/input/Domain.cxx src/input/Domain.hxx \
|
||||
src/input/Init.cxx src/input/Init.hxx \
|
||||
src/input/Registry.cxx src/input/Registry.hxx \
|
||||
src/input/Open.cxx \
|
||||
@@ -1133,7 +1133,6 @@ libinput_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(NFS_CFLAGS) \
|
||||
$(CDIO_PARANOIA_CFLAGS) \
|
||||
$(FFMPEG_CFLAGS) \
|
||||
$(DESPOTIFY_CFLAGS) \
|
||||
$(MMS_CFLAGS)
|
||||
|
||||
INPUT_LIBS = \
|
||||
@@ -1143,7 +1142,6 @@ INPUT_LIBS = \
|
||||
$(NFS_LIBS) \
|
||||
$(CDIO_PARANOIA_LIBS) \
|
||||
$(FFMPEG_LIBS2) \
|
||||
$(DESPOTIFY_LIBS) \
|
||||
$(MMS_LIBS)
|
||||
|
||||
if HAVE_ALSA
|
||||
@@ -1189,15 +1187,6 @@ libinput_a_SOURCES += \
|
||||
src/input/plugins/MmsInputPlugin.cxx src/input/plugins/MmsInputPlugin.hxx
|
||||
endif
|
||||
|
||||
if ENABLE_DESPOTIFY
|
||||
libinput_a_SOURCES += \
|
||||
src/lib/despotify/DespotifyUtils.cxx \
|
||||
src/lib/despotify/DespotifyUtils.hxx \
|
||||
src/input/plugins/DespotifyInputPlugin.cxx \
|
||||
src/input/plugins/DespotifyInputPlugin.hxx
|
||||
endif
|
||||
|
||||
|
||||
liboutput_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(AO_CFLAGS) \
|
||||
$(ALSA_CFLAGS) \
|
||||
@@ -1240,6 +1229,7 @@ liboutput_plugins_a_SOURCES = \
|
||||
|
||||
MIXER_LIBS = \
|
||||
libmixer_plugins.a \
|
||||
$(ALSA_LIBS) \
|
||||
$(PULSE_LIBS)
|
||||
|
||||
MIXER_API_SRC = \
|
||||
@@ -1402,14 +1392,6 @@ PLAYLIST_LIBS = \
|
||||
$(EXPAT_LIBS) \
|
||||
$(FLAC_LIBS)
|
||||
|
||||
if ENABLE_DESPOTIFY
|
||||
libplaylist_plugins_a_SOURCES += \
|
||||
src/lib/despotify/DespotifyUtils.cxx \
|
||||
src/lib/despotify/DespotifyUtils.hxx \
|
||||
src/playlist/plugins/DespotifyPlaylistPlugin.cxx \
|
||||
src/playlist/plugins/DespotifyPlaylistPlugin.hxx
|
||||
endif
|
||||
|
||||
if ENABLE_SOUNDCLOUD
|
||||
libplaylist_plugins_a_SOURCES += \
|
||||
src/playlist/plugins/SoundCloudPlaylistPlugin.cxx \
|
||||
@@ -1646,12 +1628,6 @@ if HAVE_LIBUPNP
|
||||
test_run_neighbor_explorer_SOURCES += src/lib/expat/ExpatParser.cxx
|
||||
endif
|
||||
|
||||
if ENABLE_DESPOTIFY
|
||||
test_run_neighbor_explorer_SOURCES += \
|
||||
src/lib/despotify/DespotifyUtils.cxx \
|
||||
src/lib/despotify/DespotifyUtils.hxx
|
||||
endif
|
||||
|
||||
endif
|
||||
|
||||
if ENABLE_ARCHIVE
|
||||
@@ -1900,6 +1876,7 @@ test_run_convert_SOURCES = test/run_convert.cxx \
|
||||
src/AudioParser.cxx
|
||||
test_run_convert_LDADD = \
|
||||
$(PCM_LIBS) \
|
||||
libconf.a \
|
||||
libutil.a \
|
||||
$(GLIB_LIBS)
|
||||
|
||||
@@ -2141,19 +2118,13 @@ user_DATA = $(wildcard doc/user/*.html)
|
||||
developerdir = $(docdir)/developer
|
||||
developer_DATA = $(wildcard doc/developer/*.html)
|
||||
|
||||
if HAVE_XMLTO
|
||||
|
||||
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 $<
|
||||
|
||||
else
|
||||
|
||||
DOCBOOK_HTML =
|
||||
|
||||
endif
|
||||
|
||||
doc/api/html/index.html: doc/doxygen.conf
|
||||
@$(MKDIR_P) $(@D)
|
||||
$(DOXYGEN) $<
|
||||
@@ -2192,7 +2163,15 @@ 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 \
|
||||
android/custom_rules.xml \
|
||||
android/res/values/strings.xml \
|
||||
android/src/Bridge.java \
|
||||
android/src/Loader.java \
|
||||
android/src/Main.java \
|
||||
src/win32/mpd_win32_rc.rc.in src/win32/mpd.ico
|
||||
|
223
NEWS
223
NEWS
@@ -1,3 +1,188 @@
|
||||
ver 0.19.18 (2016/08/05)
|
||||
* decoder
|
||||
- ffmpeg: fix crash with older FFmpeg versions (< 3.0)
|
||||
- ffmpeg: log detailed error message
|
||||
- ffmpeg: support FFmpeg 3.1
|
||||
- sidplay: detect libsidplay2 with pkg-config
|
||||
- sidplay: log detailed error message
|
||||
- sidplay: read the "date" tag
|
||||
- sidplay: allow building with libsidplayfp instead of libsidplay2
|
||||
* output
|
||||
- shout: recognize setting "encoder" instead of "encoding"
|
||||
* fix memory leak after stream failure
|
||||
* fix build failure with Boost 1.61
|
||||
* require gcc 4.7 or newer
|
||||
|
||||
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
|
||||
* decoder
|
||||
- ffmpeg: support the TAK codec
|
||||
* fix disappearing duration of remote songs during playback
|
||||
* initialize supplementary groups with glibc 2.19+
|
||||
|
||||
ver 0.19.12 (2015/12/15)
|
||||
* fix assertion failure on malformed UTF-8 tag
|
||||
* fix build failure on non-Linux systems
|
||||
* fix LimitRTTIME in systemd unit file
|
||||
|
||||
ver 0.19.11 (2015/10/27)
|
||||
* tags
|
||||
- ape: fix buffer overflow
|
||||
* decoder
|
||||
- ffmpeg: fix crash due to wrong avio_alloc_context() call
|
||||
- gme: don't loop forever, fall back to GME's default play length
|
||||
* encoder
|
||||
- flac: fix crash with 32 bit playback
|
||||
* mixer
|
||||
- fix mixer lag after enabling/disabling output
|
||||
|
||||
ver 0.19.10 (2015/06/21)
|
||||
* input
|
||||
- curl: fix deadlock on small responses
|
||||
- smbclient: fix DFF playback
|
||||
* decoder
|
||||
- ffmpeg: improve seeking accuracy
|
||||
- fix stuck stream tags
|
||||
* encoder
|
||||
- opus: fix bogus granulepos
|
||||
* output
|
||||
- fix failure to open device right after booting
|
||||
* neighbor
|
||||
- nfs: fix deadlock when connecting
|
||||
* fix "single" mode breakage due to queue edits
|
||||
|
||||
ver 0.19.9 (2015/02/06)
|
||||
* decoder
|
||||
- dsdiff, dsf: raise ID3 tag limit to 1 MB
|
||||
* playlist: fix loading duplicate tag types from state file
|
||||
* despotify: remove defunct plugin
|
||||
* fix clock integer overflow on OS X
|
||||
* fix gcc 5.0 warnings
|
||||
* fix build failure with uClibc
|
||||
* fix build failure on non-POSIX operating systems
|
||||
* fix dependency issue on parallel Android build
|
||||
* fix database/state file saving on Windows
|
||||
|
||||
ver 0.19.8 (2015/01/14)
|
||||
* input
|
||||
- curl: fix bug after rewinding from end-of-file
|
||||
- mms: reduce delay at the beginning of playback
|
||||
* decoder
|
||||
- dsdiff, dsf: allow ID3 tags larger than 4 kB
|
||||
- ffmpeg: support interleaved floating point
|
||||
* fix clang 3.6 warnings
|
||||
* fix build failure on NetBSD
|
||||
|
||||
ver 0.19.7 (2014/12/17)
|
||||
* input
|
||||
- nfs: fix crash while canceling a failing file open operation
|
||||
- nfs: fix memory leak on connection failure
|
||||
- nfs: fix reconnect after mount failure
|
||||
- nfs: implement mount timeout (60 seconds)
|
||||
* storage
|
||||
- nfs: implement I/O timeout (60 seconds)
|
||||
* playlist
|
||||
- embcue: fix filename suffix detection
|
||||
- don't skip non-existent songs in "listplaylist"
|
||||
* decoder
|
||||
- ffmpeg: fix time stamp underflow
|
||||
* fix memory allocator bug on Windows
|
||||
|
||||
ver 0.19.6 (2014/12/08)
|
||||
* decoder
|
||||
- ffmpeg: support FFmpeg 2.5
|
||||
* fix build failure with musl
|
||||
* android
|
||||
- update libFLAC to 1.3.1
|
||||
- update FFmpeg to 2.5
|
||||
|
||||
ver 0.19.5 (2014/11/26)
|
||||
* input
|
||||
- nfs: fix crash on connection failure
|
||||
* archive
|
||||
- zzip: fix crash after seeking
|
||||
* decoder
|
||||
- dsdiff, dsf, opus: fix deadlock while seeking
|
||||
- mp4v2: remove because of incompatible license
|
||||
|
||||
ver 0.19.4 (2014/11/18)
|
||||
* protocol
|
||||
- workaround for buggy clients that send "add /"
|
||||
* decoder
|
||||
- ffmpeg: support opus
|
||||
- opus: add MIME types audio/ogg and application/ogg
|
||||
* fix crash on failed filename charset conversion
|
||||
* fix local socket detection from uid=0 (root)
|
||||
|
||||
ver 0.19.3 (2014/11/11)
|
||||
* protocol
|
||||
- fix "(null)" result string to "list" when AlbumArtist is disabled
|
||||
* database
|
||||
- upnp: fix breakage due to malformed URIs
|
||||
* input
|
||||
- curl: another fix for redirected streams
|
||||
* decoder
|
||||
- audiofile: fix crash while playing streams
|
||||
- audiofile: fix bit rate calculation
|
||||
- ffmpeg: support opus
|
||||
- opus: fix bogus duration on streams
|
||||
- opus: support chained streams
|
||||
- opus: improved error logging
|
||||
* fix distorted audio with soxr resampler
|
||||
* fix build failure on Mac OS X with non-Apple compilers
|
||||
|
||||
ver 0.19.2 (2014/11/02)
|
||||
* input
|
||||
- curl: fix redirected streams
|
||||
* playlist
|
||||
- don't allow empty playlist name
|
||||
- m3u: don't ignore unterminated last line
|
||||
- m3u: recognize the file suffix ".m3u8"
|
||||
* decoder
|
||||
- ignore URI query string for plugin detection
|
||||
- faad: remove workaround for ancient libfaad2 ABI bug
|
||||
- ffmpeg: recognize MIME type audio/aacp
|
||||
- mad: fix negative replay gain values
|
||||
* output
|
||||
- fix memory leak after filter initialization error
|
||||
- fall back to PCM if given DSD sample rate is not supported
|
||||
* fix assertion failure on unsupported PCM conversion
|
||||
* auto-disable plugins that require GLib when --disable-glib is used
|
||||
|
||||
ver 0.19.1 (2014/10/19)
|
||||
* input
|
||||
- mms: fix deadlock bug
|
||||
@@ -85,6 +270,44 @@ ver 0.19 (2014/10/10)
|
||||
* install systemd unit for socket activation
|
||||
* Android port
|
||||
|
||||
ver 0.18.23 (2015/02/06)
|
||||
* despotify: remove defunct plugin
|
||||
* fix clock integer overflow on OS X
|
||||
* fix gcc 5.0 warnings
|
||||
|
||||
ver 0.18.22 (2015/01/14)
|
||||
* fix clang 3.6 warnings
|
||||
|
||||
ver 0.18.21 (2014/12/17)
|
||||
* playlist
|
||||
- embcue: fix filename suffix detection
|
||||
* decoder
|
||||
- ffmpeg: fix time stamp underflow
|
||||
|
||||
ver 0.18.20 (2014/12/08)
|
||||
* decoder
|
||||
- ffmpeg: support FFmpeg 2.5
|
||||
* fix build failure with musl
|
||||
|
||||
ver 0.18.19 (2014/11/26)
|
||||
* archive
|
||||
- zzip: fix crash after seeking
|
||||
|
||||
ver 0.18.18 (2014/11/18)
|
||||
* decoder
|
||||
- ffmpeg: support opus
|
||||
* fix crash on failed filename charset conversion
|
||||
* fix local socket detection from uid=0 (root)
|
||||
|
||||
ver 0.18.17 (2014/11/02)
|
||||
* playlist
|
||||
- don't allow empty playlist name
|
||||
- m3u: recognize the file suffix ".m3u8"
|
||||
* decoder
|
||||
- ignore URI query string for plugin detection
|
||||
- faad: remove workaround for ancient libfaad2 ABI bug
|
||||
- ffmpeg: recognize MIME type audio/aacp
|
||||
|
||||
ver 0.18.16 (2014/09/26)
|
||||
* fix DSD breakage due to typo in configure.ac
|
||||
|
||||
|
@@ -2,8 +2,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="7"
|
||||
android:versionName="0.19.1">
|
||||
android:versionCode="13"
|
||||
android:versionName="0.19.9">
|
||||
|
||||
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="17"/>
|
||||
|
||||
|
@@ -23,7 +23,7 @@ if not os.path.isdir(ndk_path):
|
||||
sys.exit(1)
|
||||
|
||||
# the path to the MPD sources
|
||||
mpd_path = os.path.dirname(os.path.dirname(sys.argv[0]))
|
||||
mpd_path = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]) or '.', '..'))
|
||||
|
||||
# output directories
|
||||
lib_path = os.path.abspath('lib')
|
||||
@@ -40,8 +40,8 @@ build_arch = 'linux-x86_64'
|
||||
os.environ['PKG_CONFIG_LIBDIR'] = os.path.join(root_path, 'lib/pkgconfig')
|
||||
|
||||
# select the NDK compiler
|
||||
gcc_version = '4.8'
|
||||
llvm_version = '3.3'
|
||||
gcc_version = '4.9'
|
||||
llvm_version = '3.5'
|
||||
|
||||
# select the NDK target
|
||||
ndk_arch = 'arm'
|
||||
@@ -314,8 +314,8 @@ thirdparty_libs = [
|
||||
),
|
||||
|
||||
AutotoolsProject(
|
||||
'https://svn.xiph.org/releases/flac/flac-1.3.0.tar.xz',
|
||||
'13b5c214cee8373464d3d65dee362cdd',
|
||||
'http://downloads.xiph.org/releases/flac/flac-1.3.1.tar.xz',
|
||||
'b9922c9a0378c88d3e901b234f852698',
|
||||
'lib/libFLAC.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -341,8 +341,8 @@ thirdparty_libs = [
|
||||
),
|
||||
|
||||
FfmpegProject(
|
||||
'http://www.ffmpeg.org/releases/ffmpeg-2.2.3.tar.bz2',
|
||||
'dbb5b6b69bd010916f17df0ae596e0b1',
|
||||
'http://ffmpeg.org/releases/ffmpeg-2.5.tar.bz2',
|
||||
'4346fe710cc6bdd981f6534d2420d1ab',
|
||||
'lib/libavcodec.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -366,8 +366,8 @@ thirdparty_libs = [
|
||||
),
|
||||
|
||||
AutotoolsProject(
|
||||
'http://curl.haxx.se/download/curl-7.37.0.tar.lzma',
|
||||
'54bfd1eb5214f604186d6f5ac61c7781',
|
||||
'http://curl.haxx.se/download/curl-7.39.0.tar.lzma',
|
||||
'e9aa6dec29920eba8ef706ea5823bad7',
|
||||
'lib/libcurl.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
|
117
configure.ac
117
configure.ac
@@ -1,10 +1,10 @@
|
||||
AC_PREREQ(2.60)
|
||||
|
||||
AC_INIT(mpd, 0.19.1, musicpd-dev-team@lists.sourceforge.net)
|
||||
AC_INIT(mpd, 0.19.18, musicpd-dev-team@lists.sourceforge.net)
|
||||
|
||||
VERSION_MAJOR=0
|
||||
VERSION_MINOR=19
|
||||
VERSION_REVISION=0
|
||||
VERSION_REVISION=18
|
||||
VERSION_EXTRA=0
|
||||
|
||||
AC_CONFIG_SRCDIR([src/Main.cxx])
|
||||
@@ -206,6 +206,8 @@ if test x$host_is_linux = xyes; then
|
||||
fi
|
||||
|
||||
AC_CHECK_FUNCS(getpwnam_r getpwuid_r)
|
||||
AC_CHECK_FUNCS(initgroups)
|
||||
AC_CHECK_FUNCS(strndup)
|
||||
|
||||
if test x$host_is_linux = xyes; then
|
||||
MPD_OPTIONAL_FUNC(eventfd, eventfd, USE_EVENTFD)
|
||||
@@ -293,7 +295,9 @@ fi
|
||||
AC_ARG_ENABLE(libmpdclient,
|
||||
AS_HELP_STRING([--enable-libmpdclient],
|
||||
[enable support for the MPD client]),,
|
||||
enable_libmpdclient=$database_auto)
|
||||
enable_libmpdclient=auto)
|
||||
MPD_DEPENDS([enable_libmpdclient], [enable_database],
|
||||
[Cannot use --enable-libmpdclient with --disable-database])
|
||||
|
||||
AC_ARG_ENABLE(expat,
|
||||
AS_HELP_STRING([--enable-expat],
|
||||
@@ -303,7 +307,9 @@ AC_ARG_ENABLE(expat,
|
||||
AC_ARG_ENABLE(upnp,
|
||||
AS_HELP_STRING([--enable-upnp],
|
||||
[enable UPnP client support (default: auto)]),,
|
||||
enable_upnp=$database_auto)
|
||||
enable_upnp=auto)
|
||||
MPD_DEPENDS([enable_upnp], [enable_database],
|
||||
[Cannot use --enable-upnp with --disable-database])
|
||||
|
||||
AC_ARG_ENABLE(adplug,
|
||||
AS_HELP_STRING([--enable-adplug],
|
||||
@@ -323,6 +329,8 @@ AC_ARG_ENABLE(ao,
|
||||
AS_HELP_STRING([--enable-ao],
|
||||
[enable support for libao]),,
|
||||
enable_ao=auto)
|
||||
MPD_DEPENDS([enable_ao], [enable_glib],
|
||||
[Cannot use --enable-ao with --disable-glib])
|
||||
|
||||
AC_ARG_ENABLE(audiofile,
|
||||
AS_HELP_STRING([--enable-audiofile],
|
||||
@@ -343,6 +351,8 @@ AC_ARG_ENABLE(cdio-paranoia,
|
||||
AS_HELP_STRING([--enable-cdio-paranoia],
|
||||
[enable support for audio CD support]),,
|
||||
enable_cdio_paranoia=auto)
|
||||
MPD_DEPENDS([enable_cdio_paranoia], [enable_glib],
|
||||
[Cannot use --enable-cdio-paranoia with --disable-glib])
|
||||
|
||||
AC_ARG_ENABLE(curl,
|
||||
AS_HELP_STRING([--enable-curl],
|
||||
@@ -398,11 +408,15 @@ AC_ARG_ENABLE(gme,
|
||||
AS_HELP_STRING([--enable-gme],
|
||||
[enable Blargg's game music emulator plugin]),,
|
||||
enable_gme=auto)
|
||||
MPD_DEPENDS([enable_gme], [enable_glib],
|
||||
[Cannot use --enable-gme with --disable-glib])
|
||||
|
||||
AC_ARG_ENABLE(httpd-output,
|
||||
AS_HELP_STRING([--enable-httpd-output],
|
||||
[enables the HTTP server output]),,
|
||||
[enable_httpd_output=auto])
|
||||
MPD_DEPENDS([enable_httpd_output], [enable_glib],
|
||||
[Cannot use --enable-httpd-output with --disable-glib])
|
||||
|
||||
AC_ARG_ENABLE(id3,
|
||||
AS_HELP_STRING([--enable-id3],
|
||||
@@ -428,18 +442,17 @@ AC_ARG_ENABLE(jack,
|
||||
AS_HELP_STRING([--enable-jack],
|
||||
[enable jack support]),,
|
||||
enable_jack=auto)
|
||||
MPD_DEPENDS([enable_jack], [enable_glib],
|
||||
[Cannot use --enable-jack with --disable-glib])
|
||||
|
||||
AC_SYS_LARGEFILE
|
||||
|
||||
AC_ARG_ENABLE(despotify,
|
||||
AS_HELP_STRING([--enable-despotify],
|
||||
[enable support for despotify (default: disable)]),,
|
||||
[enable_despotify=no])
|
||||
|
||||
AC_ARG_ENABLE(soundcloud,
|
||||
AS_HELP_STRING([--enable-soundcloud],
|
||||
[enable support for soundcloud.com]),,
|
||||
[enable_soundcloud=auto])
|
||||
MPD_DEPENDS([enable_soundcloud], [enable_glib],
|
||||
[Cannot use --enable-soundcloud with --disable-glib])
|
||||
|
||||
AC_ARG_ENABLE(lame-encoder,
|
||||
AS_HELP_STRING([--enable-lame-encoder],
|
||||
@@ -480,11 +493,6 @@ AC_ARG_ENABLE(modplug,
|
||||
[enable modplug decoder plugin]),,
|
||||
enable_modplug=auto)
|
||||
|
||||
AC_ARG_ENABLE(mp4v2,
|
||||
AS_HELP_STRING([--enable-mp4v2],
|
||||
[enable libmp4v2 decoder plugin]),,
|
||||
enable_mp4v2=auto)
|
||||
|
||||
AC_ARG_ENABLE(mpc,
|
||||
AS_HELP_STRING([--enable-mpc],
|
||||
[disable musepack (MPC) support (default: auto)]),,
|
||||
@@ -559,6 +567,8 @@ AC_ARG_ENABLE(sqlite,
|
||||
AS_HELP_STRING([--enable-sqlite],
|
||||
[enable support for the SQLite database]),,
|
||||
[enable_sqlite=$database_auto])
|
||||
MPD_DEPENDS([enable_sqlite], [enable_glib],
|
||||
[Cannot use --enable-sqlite with --disable-glib])
|
||||
|
||||
AC_ARG_ENABLE(systemd-daemon,
|
||||
AS_HELP_STRING([--enable-systemd-daemon],
|
||||
@@ -599,6 +609,8 @@ AC_ARG_ENABLE(vorbis-encoder,
|
||||
AS_HELP_STRING([--enable-vorbis-encoder],
|
||||
[enable the Ogg Vorbis encoder]),,
|
||||
[enable_vorbis_encoder=auto])
|
||||
MPD_DEPENDS([enable_vorbis_encoder], [enable_glib],
|
||||
[Cannot use --enable-vorbis-encoder with --disable-glib])
|
||||
|
||||
AC_ARG_ENABLE(wave-encoder,
|
||||
AS_HELP_STRING([--enable-wave-encoder],
|
||||
@@ -609,6 +621,8 @@ AC_ARG_ENABLE(wavpack,
|
||||
AS_HELP_STRING([--enable-wavpack],
|
||||
[enable WavPack support]),,
|
||||
enable_wavpack=auto)
|
||||
MPD_DEPENDS([enable_wavpack], [enable_glib],
|
||||
[Cannot use --enable-wavpack with --disable-glib])
|
||||
|
||||
AC_ARG_ENABLE(werror,
|
||||
AS_HELP_STRING([--enable-werror],
|
||||
@@ -687,12 +701,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)
|
||||
@@ -960,14 +968,6 @@ if test x$enable_nfs = xyes; then
|
||||
fi
|
||||
AM_CONDITIONAL(ENABLE_NFS, test x$enable_nfs = xyes)
|
||||
|
||||
dnl --------------------------------- Despotify ---------------------------------
|
||||
MPD_AUTO_PKG(despotify, DESPOTIFY, [despotify],
|
||||
[Despotify support], [despotify not found])
|
||||
if test x$enable_despotify = xyes; then
|
||||
AC_DEFINE(ENABLE_DESPOTIFY, 1, [Define when despotify is enabled])
|
||||
fi
|
||||
AM_CONDITIONAL(ENABLE_DESPOTIFY, test x$enable_despotify = xyes)
|
||||
|
||||
dnl --------------------------------- Soundcloud ------------------------------
|
||||
if test x$enable_soundcloud != xno; then
|
||||
PKG_CHECK_MODULES([YAJL], [yajl >= 2.0],
|
||||
@@ -1240,24 +1240,6 @@ if test x$enable_modplug = xyes; then
|
||||
fi
|
||||
AM_CONDITIONAL(HAVE_MODPLUG, test x$enable_modplug = xyes)
|
||||
|
||||
dnl -------------------------------- libmp4v2 ---------------------------------
|
||||
if test x$enable_aac = xyes; then
|
||||
MPD_AUTO_LIB(mp4v2, MP4V2, mp4v2, MP4Create, [-lmp4v2], [],
|
||||
[mp4v2], [libmp4v2 not found])
|
||||
|
||||
if test x$enable_mp4v2 = xyes; then
|
||||
AC_DEFINE(HAVE_MP4V2, 1, [Define to use libmp4v2 for MP4 decoding])
|
||||
fi
|
||||
else
|
||||
if test x$enable_mp4v2 = xyes; then
|
||||
AC_MSG_ERROR([MP4V2 requires AAC!])
|
||||
fi
|
||||
|
||||
enable_mp4v2=no
|
||||
fi
|
||||
|
||||
AM_CONDITIONAL(HAVE_MP4V2, test x$enable_mp4v2 = xyes)
|
||||
|
||||
dnl -------------------------------- libopus ----------------------------------
|
||||
MPD_AUTO_PKG(opus, OPUS, [opus ogg],
|
||||
[opus decoder plugin], [libopus not found])
|
||||
@@ -1351,31 +1333,36 @@ AM_CONDITIONAL(ENABLE_VORBIS_DECODER, test x$enable_vorbis = xyes || test x$enab
|
||||
|
||||
dnl --------------------------------- sidplay ---------------------------------
|
||||
if test x$enable_sidplay != xno; then
|
||||
# we're not using pkg-config here
|
||||
# because libsidplay2's .pc file requires libtool
|
||||
AC_CHECK_LIB([sidplay2],[main],[found_sidplay=yes],[found_sidplay=no],[])
|
||||
dnl Check for libsidplayfp first
|
||||
PKG_CHECK_MODULES([SIDPLAY], [libsidplayfp libsidutils],
|
||||
[found_sidplayfp=yes],
|
||||
[found_sidplayfp=no])
|
||||
found_sidplay=$found_sidplayfp
|
||||
fi
|
||||
|
||||
if test x$enable_sidplay != xno && test x$found_sidplayfp = xno; then
|
||||
PKG_CHECK_MODULES([SIDPLAY], [libsidplay2 libsidutils],
|
||||
[found_sidplay=yes],
|
||||
[found_sidplay=no])
|
||||
|
||||
MPD_AUTO_PRE(sidplay, [sidplay decoder plugin],
|
||||
[libsidplay2 not found])
|
||||
fi
|
||||
|
||||
if test x$enable_sidplay != xno; then
|
||||
if test x$enable_sidplay != xno && test x$found_sidplayfp = xno; then
|
||||
AC_CHECK_LIB([resid-builder], [main],
|
||||
[found_sidplay=yes], [found_sidplay=no])
|
||||
|
||||
if test x$found_sidplay = xyes; then
|
||||
AC_CHECK_LIB([sidutils],[main],[:],[found_sidplay=no],[])
|
||||
fi
|
||||
|
||||
MPD_AUTO_RESULT(sidplay, [sidplay decoder plugin],
|
||||
[libresid-builder or libsidutils not found])
|
||||
[libresid-builder not found])
|
||||
fi
|
||||
|
||||
if test x$enable_sidplay = xyes; then
|
||||
AC_SUBST(SIDPLAY_LIBS,"-lsidplay2 -lresid-builder -lsidutils")
|
||||
AC_SUBST(SIDPLAY_CFLAGS,)
|
||||
|
||||
SIDPLAY_LIBS="$SIDPLAY_LIBS -lresid-builder"
|
||||
AC_DEFINE(ENABLE_SIDPLAY, 1, [Define for libsidplay2 support])
|
||||
if test x$found_sidplayfp = xyes; then
|
||||
AC_DEFINE(HAVE_SIDPLAYFP, 1, [Define if libsidplayfp is used instead of libsidplay2])
|
||||
fi
|
||||
fi
|
||||
|
||||
AM_CONDITIONAL(ENABLE_SIDPLAY, test x$enable_sidplay = xyes)
|
||||
@@ -1709,8 +1696,11 @@ dnl Documentation
|
||||
dnl ---------------------------------------------------------------------------
|
||||
if test x$enable_documentation = xyes; then
|
||||
AC_PATH_PROG(XMLTO, xmlto)
|
||||
if test x$XMLTO = x; then
|
||||
AC_MSG_ERROR([xmlto not found])
|
||||
fi
|
||||
|
||||
AC_SUBST(XMLTO)
|
||||
AM_CONDITIONAL(HAVE_XMLTO, test x$XMLTO != x)
|
||||
|
||||
AC_PATH_PROG(DOXYGEN, doxygen)
|
||||
if test x$DOXYGEN = x; then
|
||||
@@ -1718,8 +1708,6 @@ if test x$enable_documentation = xyes; then
|
||||
fi
|
||||
|
||||
AC_SUBST(DOXYGEN)
|
||||
else
|
||||
AM_CONDITIONAL(HAVE_XMLTO, false)
|
||||
fi
|
||||
|
||||
AM_CONDITIONAL(ENABLE_DOCUMENTATION, test x$enable_documentation = xyes)
|
||||
@@ -1826,9 +1814,14 @@ results(ipv6, "IPv6")
|
||||
results(tcp, "TCP")
|
||||
results(un,[UNIX Domain Sockets])
|
||||
|
||||
printf '\nStorage support:\n\t'
|
||||
results(nfs, [NFS])
|
||||
results(smbclient, [SMB])
|
||||
|
||||
printf '\nFile format support:\n\t'
|
||||
results(aac, [AAC])
|
||||
results(adplug, [AdPlug])
|
||||
results(dsd, [DSD])
|
||||
results(sidplay, [C64 SID])
|
||||
results(ffmpeg, [FFMPEG])
|
||||
results(flac, [FLAC])
|
||||
@@ -1838,7 +1831,6 @@ printf '\n\t'
|
||||
results(sndfile, [libsndfile])
|
||||
results(mikmod, [MikMod])
|
||||
results(modplug, [MODPLUG])
|
||||
results(mp4v2, [MP4V2])
|
||||
results(mad, [MAD])
|
||||
results(mpg123, [MPG123])
|
||||
results(mpc, [Musepack])
|
||||
@@ -1897,7 +1889,6 @@ printf '\nStreaming support:\n\t'
|
||||
results(cdio_paranoia, [CDIO_PARANOIA])
|
||||
results(curl,[CURL])
|
||||
results(smbclient,[SMBCLIENT])
|
||||
results(despotify,[Despotify])
|
||||
results(soundcloud,[Soundcloud])
|
||||
printf '\n\t'
|
||||
results(mms,[MMS])
|
||||
|
@@ -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>
|
||||
|
||||
@@ -40,7 +41,7 @@
|
||||
<listitem>
|
||||
<para>
|
||||
the code should be C++11 compliant, and must compile with
|
||||
<application>GCC</application> 4.6 and
|
||||
<application>GCC</application> 4.7 and
|
||||
<application>clang</application> 3.2
|
||||
</para>
|
||||
</listitem>
|
||||
|
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
|
||||
@@ -195,16 +183,6 @@ of database.
|
||||
Limit the depth of the directories being watched, 0 means only watch
|
||||
the music directory itself. There is no limit by default.
|
||||
.TP
|
||||
.B despotify_user <name>
|
||||
This specifies the user to use when logging in to Spotify using the despotify plugins.
|
||||
.TP
|
||||
.B despotify_password <name>
|
||||
This specifies the password to use when logging in to Spotify using the despotify plugins.
|
||||
.TP
|
||||
.B despotify_high_bitrate <yes or no>
|
||||
This specifies if the requested bitrate for Spotify should be high or not. Higher sounds
|
||||
better but requires more processing and higher bandwidth. Default is yes.
|
||||
.TP
|
||||
.SH REQUIRED AUDIO OUTPUT PARAMETERS
|
||||
.TP
|
||||
.B type <type>
|
||||
|
@@ -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
|
||||
@@ -231,7 +231,7 @@ input {
|
||||
#
|
||||
#audio_output {
|
||||
# type "shout"
|
||||
# encoding "ogg" # optional
|
||||
# encoder "vorbis" # optional
|
||||
# name "My Shout Stream"
|
||||
# host "localhost"
|
||||
# port "8000"
|
||||
|
@@ -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">
|
||||
@@ -1141,7 +1161,7 @@ OK
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Searches case-sensitively for partial matches in the
|
||||
Searches case-insensitively for partial matches in the
|
||||
current playlist.
|
||||
</para>
|
||||
</listitem>
|
||||
@@ -1533,6 +1553,7 @@ OK
|
||||
<command>count</command>
|
||||
<arg choice="req"><replaceable>TAG</replaceable></arg>
|
||||
<arg choice="req"><replaceable>NEEDLE</replaceable></arg>
|
||||
<arg choice="opt"><replaceable>...</replaceable></arg>
|
||||
<arg choice="opt">group</arg>
|
||||
<arg choice="opt"><replaceable>GROUPTYPE</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
|
241
doc/user.xml
241
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>
|
||||
@@ -89,7 +90,7 @@ cd mpd-version</programlisting>
|
||||
</para>
|
||||
|
||||
<programlisting>
|
||||
apt-get install g++ automake autoconf \
|
||||
apt-get install g++ \
|
||||
libmad0-dev libmpg123-dev libid3tag0-dev \
|
||||
libflac-dev libvorbis-dev libopus-dev \
|
||||
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
|
||||
@@ -98,19 +99,21 @@ apt-get install g++ automake autoconf \
|
||||
libsidplay2-dev libsidutils-dev libresid-builder-dev \
|
||||
libavcodec-dev libavformat-dev \
|
||||
libmp3lame-dev \
|
||||
libsamplerate0-dev \
|
||||
libsamplerate0-dev libsoxr-dev \
|
||||
libbz2-dev libcdio-paranoia-dev libiso9660-dev libmms-dev \
|
||||
libzzip-dev \
|
||||
libcurl4-gnutls-dev libyajl-dev \
|
||||
libcurl4-gnutls-dev libyajl-dev libexpat-dev \
|
||||
libasound2-dev libao-dev libjack-jackd2-dev libopenal-dev \
|
||||
libpulse-dev libroar-dev libshout3-dev \
|
||||
libmpdclient-dev \
|
||||
libnfs-dev libsmbclient-dev \
|
||||
libupnp-dev \
|
||||
libavahi-client-dev \
|
||||
libsqlite3-dev \
|
||||
libsystemd-daemon-dev libwrap0-dev \
|
||||
libcppunit-dev xmlto \
|
||||
libboost-dev \
|
||||
libglib2.0-dev
|
||||
libglib2.0-dev libicu-dev
|
||||
</programlisting>
|
||||
|
||||
<para>
|
||||
@@ -313,9 +316,8 @@ systemctl start mpd.socket</programlisting>
|
||||
</para>
|
||||
|
||||
<programlisting>input {
|
||||
plugin "despotify"
|
||||
user "foo"
|
||||
password "bar"
|
||||
plugin "curl"
|
||||
proxy "proxy.local"
|
||||
}
|
||||
</programlisting>
|
||||
|
||||
@@ -943,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>
|
||||
|
||||
@@ -1189,6 +1218,58 @@ database {
|
||||
plugin).
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="realtime">
|
||||
<title>Real-Time Scheduling</title>
|
||||
|
||||
<para>
|
||||
On Linux, <application>MPD</application> attempts to configure
|
||||
<ulink
|
||||
url="https://en.wikipedia.org/wiki/Real-time_computing">real-time
|
||||
scheduling</ulink> for some threads that benefit from it.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
This is only possible you allow <application>MPD</application>
|
||||
to do it. This privilege is controlled by
|
||||
<varname>RLIMIT_RTPRIO</varname>
|
||||
<varname>RLIMIT_RTTIME</varname>. You can configure this
|
||||
privilege with <command>ulimit</command> before launching
|
||||
<application>MPD</application>:
|
||||
</para>
|
||||
|
||||
<programlisting>ulimit -HS -r 50; mpd</programlisting>
|
||||
|
||||
<para>
|
||||
Or you can use the <command>prlimit</command> program from the
|
||||
<application>util-linux</application> package:
|
||||
</para>
|
||||
|
||||
<programlisting>prlimit --rtprio=50 --rttime=unlimited mpd</programlisting>
|
||||
|
||||
<para>
|
||||
The <application>systemd</application> service file shipped
|
||||
with <application>MPD</application> comes with this setting.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
This works only if the Linux kernel was compiled with
|
||||
<varname>CONFIG_RT_GROUP_SCHED</varname> disabled. Use the
|
||||
following command to check this option for your current
|
||||
kernel:
|
||||
</para>
|
||||
|
||||
<programlisting>zgrep ^CONFIG_RT_GROUP_SCHED /proc/config.gz</programlisting>
|
||||
|
||||
<note>
|
||||
<para>
|
||||
There is a rumor that real-time scheduling improves audio
|
||||
quality. That is not true. All it does is reduce the
|
||||
probability of skipping (audio buffer xruns) when the
|
||||
computer is under heavy load.
|
||||
</para>
|
||||
</note>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
<chapter id="use">
|
||||
@@ -1244,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>
|
||||
|
||||
@@ -1738,66 +1832,6 @@ buffer_size: 16384</programlisting>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>despotify</varname></title>
|
||||
|
||||
<para>
|
||||
Plays <ulink url="http://www.spotify.com">Spotify</ulink> tracks using the despotify
|
||||
library. The despotify plugin uses a <filename>spt://</filename> URI and a Spotify
|
||||
URL. So for example, you can add a song with:
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<filename>mpc add spt://spotify:track:5qENVY0YEdZ7fiuOax70x1</filename>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
You need a Spotify premium account to use this plugin, and you need
|
||||
to setup username and password in the configuration file. The
|
||||
configuration settings are global since the despotify playlist plugin
|
||||
use the same settings.
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Setting</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>despotify_user</varname>
|
||||
</entry>
|
||||
<entry>
|
||||
Sets up the Spotify username (required)
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>despotify_password</varname>
|
||||
</entry>
|
||||
<entry>
|
||||
Sets up the Spotify password (required)
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>despotify_high_bitrate</varname>
|
||||
</entry>
|
||||
<entry>
|
||||
Set up if high bitrate should be used for Spotify tunes.
|
||||
High bitrate sounds better but slow systems can have problems
|
||||
with playback (default yes).
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>file</varname></title>
|
||||
|
||||
@@ -2654,7 +2688,8 @@ buffer_size: 16384</programlisting>
|
||||
/ <ulink
|
||||
url="http://icecast.org/"><application>IceCast</application></ulink>.
|
||||
HTTP streaming clients like
|
||||
<application>mplayer</application> can connect to it.
|
||||
<application>mplayer</application>, <application>VLC</application>,
|
||||
and <application>mpv</application> can connect to it.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
@@ -3284,70 +3319,6 @@ buffer_size: 16384</programlisting>
|
||||
playlist files.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>despotify</varname></title>
|
||||
|
||||
<para>
|
||||
Adds <ulink url="http://www.spotify.com/">Spotify</ulink>
|
||||
playlists. Spotify playlists use the <filename>spt://</filename> URI,
|
||||
and a Spotify playlist URL. So for example, you can load a playlist
|
||||
with
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<filename>mpc load spt://spotify:user:simon.kagstrom:playlist:3SUwkOe5VbVHysZcidEZtH</filename>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
See the despotify input plugin for configuration options (username
|
||||
and password needs to be setup)
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>soundcloud</varname></title>
|
||||
|
||||
<para>
|
||||
Adds <ulink url="https://www.soundcloud.com/">Soundcloud</ulink>
|
||||
playlists. SoundCloud playlists use the <filename>soundcloud://</filename> URI,
|
||||
and with a number of arguments, you may load different playlists with
|
||||
</para>
|
||||
|
||||
<programlisting>
|
||||
mpc load soundcloud://track/TRACK_ID
|
||||
mpc load soundcloud://playlist/PLAYLIST_ID
|
||||
mpc load soundcloud://user/USERNAME
|
||||
mpc load soundcloud://search/SEARCH_QUERY
|
||||
mpc load soundcloud://url/https://soundcloud.com/ARTIST/TRACK-NAME
|
||||
</programlisting>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Setting</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>apikey</varname>
|
||||
<parameter>client_id</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
User apikey/client_id can override the
|
||||
<application>MPD</application> token provided by
|
||||
SoundCloud.
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
|
||||
</section>
|
||||
|
||||
</section>
|
||||
</chapter>
|
||||
</book>
|
||||
|
31
m4/faad.m4
31
m4/faad.m4
@@ -62,36 +62,7 @@ int main() {
|
||||
CPPFLAGS=$oldcppflags
|
||||
fi
|
||||
|
||||
if test x$enable_aac = xyes; then
|
||||
oldcflags=$CFLAGS
|
||||
oldlibs=$LIBS
|
||||
oldcppflags=$CPPFLAGS
|
||||
CFLAGS="$CFLAGS $FAAD_CFLAGS -Werror"
|
||||
LIBS="$LIBS $FAAD_LIBS"
|
||||
CPPFLAGS=$CFLAGS
|
||||
|
||||
AC_MSG_CHECKING(for broken libfaad headers)
|
||||
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
|
||||
#include <faad.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
int main() {
|
||||
unsigned char channels;
|
||||
uint32_t sample_rate;
|
||||
|
||||
NeAACDecInit2(NULL, NULL, 0, &sample_rate, &channels);
|
||||
return 0;
|
||||
}
|
||||
])],
|
||||
[AC_MSG_RESULT(correct)],
|
||||
[AC_MSG_RESULT(broken);
|
||||
AC_DEFINE(HAVE_FAAD_LONG, 1, [Define if faad.h uses the broken "unsigned long" pointers])])
|
||||
|
||||
CFLAGS=$oldcflags
|
||||
LIBS=$oldlibs
|
||||
CPPFLAGS=$oldcppflags
|
||||
else
|
||||
if test x$enable_aac = xno; then
|
||||
FAAD_LIBS=""
|
||||
FAAD_CFLAGS=""
|
||||
fi
|
||||
|
9
m4/mpd_depends.m4
Normal file
9
m4/mpd_depends.m4
Normal file
@@ -0,0 +1,9 @@
|
||||
AC_DEFUN([MPD_DEPENDS], [
|
||||
if test x$$2 = xno; then
|
||||
if test x$$1 = xauto; then
|
||||
$1=no
|
||||
elif test x$$1 = xyes; then
|
||||
AC_MSG_ERROR([$3])
|
||||
fi
|
||||
fi
|
||||
])
|
@@ -20,33 +20,45 @@
|
||||
#ifndef COMPILER_H
|
||||
#define COMPILER_H
|
||||
|
||||
#define GCC_CHECK_VERSION(major, minor) \
|
||||
(defined(__GNUC__) && \
|
||||
(__GNUC__ > (major) || (__GNUC__ == (major) && __GNUC_MINOR__ >= (minor))))
|
||||
#define GCC_MAKE_VERSION(major, minor, patchlevel) ((major) * 10000 + (minor) * 100 + patchlevel)
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define GCC_VERSION (__GNUC__ * 10000 \
|
||||
+ __GNUC_MINOR__ * 100 \
|
||||
+ __GNUC_PATCHLEVEL__)
|
||||
#define GCC_VERSION GCC_MAKE_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)
|
||||
#else
|
||||
#define GCC_VERSION 0
|
||||
#endif
|
||||
|
||||
#define GCC_CHECK_VERSION(major, minor) \
|
||||
(defined(__GNUC__) && GCC_VERSION >= GCC_MAKE_VERSION(major, minor, 0))
|
||||
|
||||
/**
|
||||
* Are we building with gcc (not clang or any other compiler) and a
|
||||
* version older than the specified one?
|
||||
*/
|
||||
#define GCC_OLDER_THAN(major, minor) \
|
||||
(defined(__GNUC__) && !defined(__clang__) && \
|
||||
GCC_VERSION < GCC_MAKE_VERSION(major, minor, 0))
|
||||
|
||||
#ifdef __clang__
|
||||
# define CLANG_VERSION (__clang_major__ * 10000 \
|
||||
+ __clang_minor__ * 100 \
|
||||
+ __clang_patchlevel__)
|
||||
# define CLANG_VERSION GCC_MAKE_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__)
|
||||
# if __clang_major__ < 3
|
||||
# error Sorry, your clang version is too old. You need at least version 3.1.
|
||||
# endif
|
||||
#elif defined(__GNUC__)
|
||||
# if !GCC_CHECK_VERSION(4,6)
|
||||
# if GCC_OLDER_THAN(4,7)
|
||||
# error Sorry, your gcc version is too old. You need at least version 4.6.
|
||||
# endif
|
||||
#else
|
||||
# warning Untested compiler. Use at your own risk!
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Are we building with the specified version of clang or newer?
|
||||
*/
|
||||
#define CLANG_CHECK_VERSION(major, minor) \
|
||||
(defined(__clang__) && \
|
||||
CLANG_VERSION >= GCC_MAKE_VERSION(major, minor, 0))
|
||||
|
||||
#if GCC_CHECK_VERSION(4,0)
|
||||
|
||||
/* GCC 4.x */
|
||||
@@ -141,7 +153,7 @@
|
||||
#if defined(__cplusplus)
|
||||
|
||||
/* support for C++11 "override" was added in gcc 4.7 */
|
||||
#if !defined(__clang__) && !GCC_CHECK_VERSION(4,7)
|
||||
#if GCC_OLDER_THAN(4,7)
|
||||
#define override
|
||||
#define final
|
||||
#endif
|
||||
|
@@ -188,6 +188,14 @@ public:
|
||||
tag = std::move(other.tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to the MoveTagFrom(), but move only the #TagItem
|
||||
* array.
|
||||
*/
|
||||
void MoveTagItemsFrom(DetachedSong &&other) {
|
||||
tag.MoveItemsFrom(std::move(other.tag));
|
||||
}
|
||||
|
||||
time_t GetLastModified() const {
|
||||
return mtime;
|
||||
}
|
||||
|
@@ -76,7 +76,10 @@ idle_get_names(void)
|
||||
unsigned
|
||||
idle_parse_name(const char *name)
|
||||
{
|
||||
#if !CLANG_CHECK_VERSION(3,6)
|
||||
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||
assert(name != nullptr);
|
||||
#endif
|
||||
|
||||
for (unsigned i = 0; idle_names[i] != nullptr; ++i)
|
||||
if (StringEqualsCaseASCII(name, idle_names[i]))
|
||||
|
@@ -194,6 +194,12 @@ FileLog(const Domain &domain, const char *message)
|
||||
domain.GetName(),
|
||||
chomp_length(message), message);
|
||||
|
||||
#ifdef WIN32
|
||||
/* force-flush the log file, because setvbuf() does not seem
|
||||
to have an effect on WIN32 */
|
||||
fflush(stderr);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GLIB
|
||||
g_free(converted);
|
||||
#endif
|
||||
|
@@ -111,6 +111,9 @@ log_early_init(bool verbose)
|
||||
#ifdef ANDROID
|
||||
(void)verbose;
|
||||
#else
|
||||
/* force stderr to be line-buffered */
|
||||
setvbuf(stderr, nullptr, _IOLBF, 0);
|
||||
|
||||
if (verbose)
|
||||
SetLogThreshold(LogLevel::DEBUG);
|
||||
#endif
|
||||
|
34
src/Main.cxx
34
src/Main.cxx
@@ -54,7 +54,6 @@
|
||||
#include "system/FatalError.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "thread/Id.hxx"
|
||||
#include "thread/Slack.hxx"
|
||||
#include "lib/icu/Init.hxx"
|
||||
@@ -114,13 +113,15 @@
|
||||
#include <ws2tcpip.h>
|
||||
#endif
|
||||
|
||||
#ifdef __BLOCKS__
|
||||
#include <dispatch/dispatch.h>
|
||||
#endif
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
static constexpr unsigned DEFAULT_BUFFER_SIZE = 4096;
|
||||
static constexpr unsigned DEFAULT_BUFFER_BEFORE_PLAY = 10;
|
||||
|
||||
static constexpr Domain main_domain("main");
|
||||
|
||||
#ifdef ANDROID
|
||||
Context *context;
|
||||
#endif
|
||||
@@ -401,8 +402,6 @@ int main(int argc, char *argv[])
|
||||
{
|
||||
#ifdef WIN32
|
||||
return win32_main(argc, argv);
|
||||
#elif __APPLE__
|
||||
return osx_main(argc, argv);
|
||||
#else
|
||||
return mpd_main(argc, argv);
|
||||
#endif
|
||||
@@ -410,6 +409,8 @@ int main(int argc, char *argv[])
|
||||
|
||||
#endif
|
||||
|
||||
static int mpd_main_after_fork(struct options);
|
||||
|
||||
#ifdef ANDROID
|
||||
static inline
|
||||
#endif
|
||||
@@ -513,6 +514,27 @@ int mpd_main(int argc, char *argv[])
|
||||
daemonize_begin(options.daemon);
|
||||
#endif
|
||||
|
||||
#ifdef __BLOCKS__
|
||||
/* Runs the OS X native event loop in the main thread, and runs
|
||||
the rest of mpd_main on a new thread. This lets CoreAudio receive
|
||||
route change notifications (e.g. plugging or unplugging headphones).
|
||||
All hardware output on OS X ultimately uses CoreAudio internally.
|
||||
This must be run after forking; if dispatch is called before forking,
|
||||
the child process will have a broken internal dispatch state. */
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
exit(mpd_main_after_fork(options));
|
||||
});
|
||||
dispatch_main();
|
||||
return EXIT_FAILURE; // unreachable, because dispatch_main never returns
|
||||
#else
|
||||
return mpd_main_after_fork(options);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int mpd_main_after_fork(struct options options)
|
||||
{
|
||||
Error error;
|
||||
|
||||
GlobalEvents::Initialize(*instance->event_loop);
|
||||
GlobalEvents::Register(GlobalEvents::IDLE, idle_event_emitted);
|
||||
#ifdef WIN32
|
||||
@@ -608,7 +630,7 @@ int mpd_main(int argc, char *argv[])
|
||||
config_get_unsigned(CONF_AUTO_UPDATE_DEPTH,
|
||||
INT_MAX));
|
||||
#else
|
||||
FormatWarning(main_domain,
|
||||
FormatWarning(config_domain,
|
||||
"inotify: auto_update was disabled. enable during compilation phase");
|
||||
#endif
|
||||
}
|
||||
|
11
src/Main.hxx
11
src/Main.hxx
@@ -75,15 +75,4 @@ win32_app_stopping(void);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
/* Runs the OS X native event loop in the main thread, and runs
|
||||
* mpd_main on a new thread. This lets CoreAudio receive route
|
||||
* change notifications (e.g. plugging or unplugging headphones).
|
||||
* All hardware output on OS X ultimately uses CoreAudio internally.
|
||||
*/
|
||||
int osx_main(int argc, char *argv[]);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
@@ -612,6 +617,12 @@ Player::ProcessCommand()
|
||||
|
||||
queued = true;
|
||||
pc.CommandFinished();
|
||||
|
||||
pc.Unlock();
|
||||
if (dc.LockIsIdle())
|
||||
StartDecoder(*new MusicPipe());
|
||||
pc.Lock();
|
||||
|
||||
break;
|
||||
|
||||
case PlayerCommand::PAUSE:
|
||||
|
@@ -64,6 +64,10 @@ spl_global_init(void)
|
||||
bool
|
||||
spl_valid_name(const char *name_utf8)
|
||||
{
|
||||
if (*name_utf8 == 0)
|
||||
/* empty name not allowed */
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Not supporting '/' was done out of laziness, and we should
|
||||
* really strive to support it in the future.
|
||||
|
@@ -77,7 +77,10 @@ SongFilter::Item::Item(unsigned _tag, time_t _time)
|
||||
bool
|
||||
SongFilter::Item::StringMatch(const char *s) const
|
||||
{
|
||||
#if !CLANG_CHECK_VERSION(3,6)
|
||||
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||
assert(s != nullptr);
|
||||
#endif
|
||||
|
||||
if (fold_case) {
|
||||
const std::string folded = IcuCaseFold(s);
|
||||
|
@@ -77,7 +77,10 @@ SongLoader::LoadFile(const char *path_utf8, Error &error) const
|
||||
DetachedSong *
|
||||
SongLoader::LoadSong(const char *uri_utf8, Error &error) const
|
||||
{
|
||||
#if !CLANG_CHECK_VERSION(3,6)
|
||||
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||
assert(uri_utf8 != nullptr);
|
||||
#endif
|
||||
|
||||
if (memcmp(uri_utf8, "file:///", 8) == 0)
|
||||
/* absolute path */
|
||||
|
@@ -46,7 +46,8 @@ tag_stream_scan(InputStream &is, const tag_handler &handler, void *ctx)
|
||||
{
|
||||
assert(is.IsReady());
|
||||
|
||||
const char *const suffix = uri_get_suffix(is.GetURI());
|
||||
UriSuffixBuffer suffix_buffer;
|
||||
const char *const suffix = uri_get_suffix(is.GetURI(), suffix_buffer);
|
||||
const char *const mime = is.GetMimeType();
|
||||
|
||||
if (suffix == nullptr && mime == nullptr)
|
||||
|
@@ -53,7 +53,7 @@ public:
|
||||
|
||||
Bzip2ArchiveFile(Path path, InputStream *_is)
|
||||
:ArchiveFile(bz2_archive_plugin),
|
||||
name(PathTraitsFS::GetBase(path.c_str())),
|
||||
name(path.GetBase().c_str()),
|
||||
istream(_is) {
|
||||
// remove .bz2 suffix
|
||||
const size_t len = name.length();
|
||||
|
@@ -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 */
|
||||
|
@@ -168,12 +168,13 @@ bool
|
||||
ZzipInputStream::Seek(offset_type new_offset, Error &error)
|
||||
{
|
||||
zzip_off_t ofs = zzip_seek(file, new_offset, SEEK_SET);
|
||||
if (ofs != -1) {
|
||||
if (ofs < 0) {
|
||||
error.Set(zzip_domain, "zzip_seek() has failed");
|
||||
offset = ofs;
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
offset = ofs;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* exported structures */
|
||||
|
@@ -127,7 +127,7 @@ public:
|
||||
* a local (UNIX domain) socket?
|
||||
*/
|
||||
bool IsLocal() const {
|
||||
return uid > 0;
|
||||
return uid >= 0;
|
||||
}
|
||||
|
||||
unsigned GetPermission() const {
|
||||
|
@@ -41,7 +41,7 @@ Client::AllowFile(Path path_fs, Error &error) const
|
||||
instance */
|
||||
return true;
|
||||
|
||||
if (uid <= 0) {
|
||||
if (uid < 0) {
|
||||
/* unauthenticated client */
|
||||
error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied");
|
||||
return false;
|
||||
|
@@ -41,7 +41,7 @@ client_process_command_list(Client &client, bool list_ok,
|
||||
|
||||
FormatDebug(client_domain, "process command \"%s\"", cmd);
|
||||
ret = command_process(client, num++, cmd);
|
||||
FormatDebug(client_domain, "command returned %i", ret);
|
||||
FormatDebug(client_domain, "command returned %i", int(ret));
|
||||
if (ret != CommandResult::OK || client.IsExpired())
|
||||
break;
|
||||
else if (list_ok)
|
||||
@@ -90,7 +90,7 @@ client_process_line(Client &client, char *line)
|
||||
std::move(cmd_list));
|
||||
FormatDebug(client_domain,
|
||||
"[%u] process command "
|
||||
"list returned %i", client.num, ret);
|
||||
"list returned %i", client.num, int(ret));
|
||||
|
||||
if (ret == CommandResult::CLOSE ||
|
||||
client.IsExpired())
|
||||
@@ -126,7 +126,7 @@ client_process_line(Client &client, char *line)
|
||||
ret = command_process(client, 0, line);
|
||||
FormatDebug(client_domain,
|
||||
"[%u] command returned %i",
|
||||
client.num, ret);
|
||||
client.num, int(ret));
|
||||
|
||||
if (ret == CommandResult::CLOSE ||
|
||||
client.IsExpired())
|
||||
|
@@ -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:
|
||||
|
@@ -61,7 +61,16 @@ translate_uri(Client &client, const char *uri)
|
||||
CommandResult
|
||||
handle_add(Client &client, gcc_unused unsigned argc, char *argv[])
|
||||
{
|
||||
const char *const uri = translate_uri(client, argv[1]);
|
||||
const char *uri = argv[1];
|
||||
if (memcmp(uri, "/", 2) == 0)
|
||||
/* this URI is malformed, but some clients are buggy
|
||||
and use "add /" to add the whole database, which
|
||||
was never intended to work, but once did; in order
|
||||
to retain backwards compatibility, work around this
|
||||
here */
|
||||
uri = "";
|
||||
|
||||
uri = translate_uri(client, uri);
|
||||
if (uri == nullptr)
|
||||
return CommandResult::ERROR;
|
||||
|
||||
|
@@ -23,7 +23,7 @@
|
||||
#include "Interface.hxx"
|
||||
#include "client/Client.hxx"
|
||||
#include "LightSong.hxx"
|
||||
#include "tag/Set.hxx"
|
||||
#include "tag/Tag.hxx"
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
|
@@ -43,7 +43,7 @@ public:
|
||||
|
||||
virtual const LightSong *GetSong(const char *uri_utf8,
|
||||
Error &error) const override;
|
||||
virtual void ReturnSong(const LightSong *song) const;
|
||||
void ReturnSong(const LightSong *song) const override;
|
||||
|
||||
virtual bool Visit(const DatabaseSelection &selection,
|
||||
VisitDirectory visit_directory,
|
||||
|
@@ -103,7 +103,7 @@ public:
|
||||
virtual void Close() override;
|
||||
virtual const LightSong *GetSong(const char *uri_utf8,
|
||||
Error &error) const override;
|
||||
virtual void ReturnSong(const LightSong *song) const;
|
||||
void ReturnSong(const LightSong *song) const override;
|
||||
|
||||
virtual bool Visit(const DatabaseSelection &selection,
|
||||
VisitDirectory visit_directory,
|
||||
@@ -731,7 +731,7 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
|
||||
{
|
||||
// TODO: eliminate the const_cast
|
||||
if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
|
||||
return nullptr;
|
||||
return false;
|
||||
|
||||
if (!visit_directory && !visit_playlist && selection.recursive &&
|
||||
(ServerSupportsSearchBase(connection)
|
||||
@@ -757,7 +757,7 @@ ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
|
||||
{
|
||||
// TODO: eliminate the const_cast
|
||||
if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
|
||||
return nullptr;
|
||||
return false;
|
||||
|
||||
enum mpd_tag_type tag_type2 = Convert(tag_type);
|
||||
if (tag_type2 == MPD_TAG_COUNT) {
|
||||
@@ -810,7 +810,7 @@ ProxyDatabase::GetStats(const DatabaseSelection &selection,
|
||||
|
||||
// TODO: eliminate the const_cast
|
||||
if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
|
||||
return nullptr;
|
||||
return false;
|
||||
|
||||
struct mpd_stats *stats2 =
|
||||
mpd_run_stats(connection);
|
||||
|
@@ -435,9 +435,12 @@ SimpleDatabase::Save(Error &error)
|
||||
bool
|
||||
SimpleDatabase::Mount(const char *uri, Database *db, Error &error)
|
||||
{
|
||||
#if !CLANG_CHECK_VERSION(3,6)
|
||||
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||
assert(uri != nullptr);
|
||||
assert(*uri != 0);
|
||||
assert(db != nullptr);
|
||||
#endif
|
||||
assert(*uri != 0);
|
||||
|
||||
ScopeDatabaseLock protect;
|
||||
|
||||
@@ -445,13 +448,13 @@ SimpleDatabase::Mount(const char *uri, Database *db, Error &error)
|
||||
if (r.uri == nullptr) {
|
||||
error.Format(db_domain, DB_CONFLICT,
|
||||
"Already exists: %s", uri);
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strchr(r.uri, '/') != nullptr) {
|
||||
error.Format(db_domain, DB_NOT_FOUND,
|
||||
"Parent not found: %s", uri);
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
Directory *mnt = r.directory->CreateChild(r.uri);
|
||||
@@ -478,7 +481,7 @@ SimpleDatabase::Mount(const char *local_uri, const char *storage_uri,
|
||||
if (cache_path.IsNull()) {
|
||||
error.Format(db_domain, DB_NOT_FOUND,
|
||||
"No 'cache_directory' configured");
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string name(storage_uri);
|
||||
|
@@ -110,9 +110,9 @@ public:
|
||||
virtual bool Open(Error &error) override;
|
||||
virtual void Close() override;
|
||||
|
||||
virtual const LightSong *GetSong(const char *uri_utf8,
|
||||
Error &error) const override;
|
||||
virtual void ReturnSong(const LightSong *song) const;
|
||||
const LightSong *GetSong(const char *uri_utf8,
|
||||
Error &error) const override;
|
||||
void ReturnSong(const LightSong *song) const override;
|
||||
|
||||
virtual bool Visit(const DatabaseSelection &selection,
|
||||
VisitDirectory visit_directory,
|
||||
|
@@ -129,6 +129,7 @@ public:
|
||||
state(NONE),
|
||||
tag_type(TAG_NUM_OF_ITEM_TYPES)
|
||||
{
|
||||
m_tobj.clear();
|
||||
}
|
||||
|
||||
protected:
|
||||
|
@@ -85,7 +85,7 @@ public:
|
||||
virtual void Close() override;
|
||||
virtual const LightSong *GetSong(const char *uri_utf8,
|
||||
Error &error) const override;
|
||||
virtual void ReturnSong(const LightSong *song) const;
|
||||
void ReturnSong(const LightSong *song) const override;
|
||||
|
||||
virtual bool Visit(const DatabaseSelection &selection,
|
||||
VisitDirectory visit_directory,
|
||||
@@ -101,7 +101,9 @@ public:
|
||||
virtual bool GetStats(const DatabaseSelection &selection,
|
||||
DatabaseStats &stats,
|
||||
Error &error) const override;
|
||||
virtual time_t GetUpdateStamp() const {return 0;}
|
||||
time_t GetUpdateStamp() const override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool Configure(const config_param ¶m, Error &error);
|
||||
|
@@ -34,7 +34,9 @@
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifdef HAVE_GLIB
|
||||
static constexpr Domain exclude_list_domain("exclude_list");
|
||||
#endif
|
||||
|
||||
bool
|
||||
ExcludeList::LoadFile(Path path_fs)
|
||||
|
@@ -334,7 +334,7 @@ UpdateWalk::UpdateDirectory(Directory &directory, const FileInfo &info)
|
||||
directory_set_stat(directory, info);
|
||||
|
||||
Error error;
|
||||
const std::auto_ptr<StorageDirectoryReader> reader(storage.OpenDirectory(directory.GetPath(), error));
|
||||
const std::unique_ptr<StorageDirectoryReader> reader(storage.OpenDirectory(directory.GetPath(), error));
|
||||
if (reader.get() == nullptr) {
|
||||
LogError(error);
|
||||
return false;
|
||||
|
@@ -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;
|
||||
@@ -433,8 +434,11 @@ update_stream_tag(Decoder &decoder, InputStream *is)
|
||||
|
||||
/* no stream tag present - submit the song tag
|
||||
instead */
|
||||
decoder.song_tag = nullptr;
|
||||
}
|
||||
} else
|
||||
/* discard the song tag; we don't need it */
|
||||
delete decoder.song_tag;
|
||||
|
||||
decoder.song_tag = nullptr;
|
||||
|
||||
delete decoder.stream_tag;
|
||||
decoder.stream_tag = tag;
|
||||
@@ -566,7 +570,7 @@ decoder_tag(Decoder &decoder, InputStream *is,
|
||||
/* save the tag */
|
||||
|
||||
delete decoder.decoder_tag;
|
||||
decoder.decoder_tag = new Tag(tag);
|
||||
decoder.decoder_tag = new Tag(std::move(tag));
|
||||
|
||||
/* check for a new stream tag */
|
||||
|
||||
|
@@ -25,6 +25,7 @@
|
||||
#include "util/ConstBuffer.hxx"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct Decoder;
|
||||
class InputStream;
|
||||
|
@@ -37,7 +37,6 @@
|
||||
#include "plugins/MadDecoderPlugin.hxx"
|
||||
#include "plugins/SndfileDecoderPlugin.hxx"
|
||||
#include "plugins/Mpg123DecoderPlugin.hxx"
|
||||
#include "plugins/Mp4v2DecoderPlugin.hxx"
|
||||
#include "plugins/WildmidiDecoderPlugin.hxx"
|
||||
#include "plugins/MikmodDecoderPlugin.hxx"
|
||||
#include "plugins/ModplugDecoderPlugin.hxx"
|
||||
@@ -55,9 +54,6 @@ const struct DecoderPlugin *const decoder_plugins[] = {
|
||||
#ifdef HAVE_MPG123
|
||||
&mpg123_decoder_plugin,
|
||||
#endif
|
||||
#ifdef HAVE_MP4V2
|
||||
&mp4v2_decoder_plugin,
|
||||
#endif
|
||||
#ifdef ENABLE_VORBIS_DECODER
|
||||
&vorbis_decoder_plugin,
|
||||
#endif
|
||||
|
@@ -26,7 +26,10 @@
|
||||
bool
|
||||
DecoderPlugin::SupportsSuffix(const char *suffix) const
|
||||
{
|
||||
#if !CLANG_CHECK_VERSION(3,6)
|
||||
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||
assert(suffix != nullptr);
|
||||
#endif
|
||||
|
||||
return suffixes != nullptr && string_array_contains(suffixes, suffix);
|
||||
|
||||
@@ -35,7 +38,10 @@ DecoderPlugin::SupportsSuffix(const char *suffix) const
|
||||
bool
|
||||
DecoderPlugin::SupportsMimeType(const char *mime_type) const
|
||||
{
|
||||
#if !CLANG_CHECK_VERSION(3,6)
|
||||
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||
assert(mime_type != nullptr);
|
||||
#endif
|
||||
|
||||
return mime_types != nullptr &&
|
||||
string_array_contains(mime_types, mime_type);
|
||||
|
@@ -98,6 +98,7 @@ decoder_input_stream_open(DecoderControl &dc, const char *uri)
|
||||
|
||||
if (!is->Check(error)) {
|
||||
dc.Unlock();
|
||||
delete is;
|
||||
|
||||
LogError(error);
|
||||
return nullptr;
|
||||
@@ -237,7 +238,8 @@ static bool
|
||||
decoder_run_stream_locked(Decoder &decoder, InputStream &is,
|
||||
const char *uri, bool &tried_r)
|
||||
{
|
||||
const char *const suffix = uri_get_suffix(uri);
|
||||
UriSuffixBuffer suffix_buffer;
|
||||
const char *const suffix = uri_get_suffix(uri, suffix_buffer);
|
||||
|
||||
using namespace std::placeholders;
|
||||
const auto f = std::bind(decoder_run_stream_plugin,
|
||||
@@ -254,7 +256,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);
|
||||
}
|
||||
@@ -379,7 +385,11 @@ decoder_run_song(DecoderControl &dc,
|
||||
const DetachedSong &song, const char *uri, Path path_fs)
|
||||
{
|
||||
Decoder decoder(dc, dc.start_time.IsPositive(),
|
||||
new Tag(song.GetTag()));
|
||||
/* pass the song tag only if it's
|
||||
authoritative, i.e. if it's a local file -
|
||||
tags on "stream" songs are just remembered
|
||||
from the last time we played it*/
|
||||
song.IsFile() ? new Tag(song.GetTag()) : nullptr);
|
||||
int ret;
|
||||
|
||||
dc.state = DecoderState::START;
|
||||
|
@@ -209,7 +209,7 @@ audiofile_stream_decode(Decoder &decoder, InputStream &is)
|
||||
const auto total_time = audiofile_get_duration(fh);
|
||||
|
||||
const uint16_t kbit_rate = (uint16_t)
|
||||
(is.GetSize() * uint64_t(8000) / total_time.ToMS());
|
||||
(is.GetSize() * uint64_t(8) / total_time.ToMS());
|
||||
|
||||
const unsigned frame_size = (unsigned)
|
||||
afGetVirtualFrameSize(fh, AF_DEFAULT_TRACK, true);
|
||||
|
@@ -29,8 +29,10 @@
|
||||
#include "input/InputStream.hxx"
|
||||
#include "tag/TagId3.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Alloc.hxx"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef HAVE_ID3TAG
|
||||
#include <id3tag.h>
|
||||
@@ -53,7 +55,7 @@ dsdlib_skip_to(Decoder *decoder, InputStream &is,
|
||||
offset_type offset)
|
||||
{
|
||||
if (is.IsSeekable())
|
||||
return is.Seek(offset, IgnoreError());
|
||||
return is.LockSeek(offset, IgnoreError());
|
||||
|
||||
if (is.GetOffset() > offset)
|
||||
return false;
|
||||
@@ -72,7 +74,7 @@ dsdlib_skip(Decoder *decoder, InputStream &is,
|
||||
return true;
|
||||
|
||||
if (is.IsSeekable())
|
||||
return is.Seek(is.GetOffset() + delta, IgnoreError());
|
||||
return is.LockSeek(is.GetOffset() + delta, IgnoreError());
|
||||
|
||||
if (delta > 1024 * 1024)
|
||||
/* don't skip more than one megabyte; it would be too
|
||||
@@ -123,22 +125,26 @@ dsdlib_tag_id3(InputStream &is,
|
||||
|
||||
const id3_length_t count = size - offset;
|
||||
|
||||
/* Check and limit id3 tag size to prevent a stack overflow */
|
||||
id3_byte_t dsdid3[4096];
|
||||
if (count == 0 || count > sizeof(dsdid3))
|
||||
if (count < 10 || count > 1024 * 1024)
|
||||
return;
|
||||
|
||||
if (!decoder_read_full(nullptr, is, dsdid3, count))
|
||||
id3_byte_t *const id3_buf = new id3_byte_t[count];
|
||||
if (id3_buf == nullptr)
|
||||
return;
|
||||
|
||||
struct id3_tag *id3_tag = id3_tag_parse(dsdid3, count);
|
||||
if (!decoder_read_full(nullptr, is, id3_buf, count)) {
|
||||
delete[] id3_buf;
|
||||
return;
|
||||
}
|
||||
|
||||
struct id3_tag *id3_tag = id3_tag_parse(id3_buf, count);
|
||||
delete[] id3_buf;
|
||||
if (id3_tag == nullptr)
|
||||
return;
|
||||
|
||||
scan_id3_tag(id3_tag, handler, handler_ctx);
|
||||
|
||||
id3_tag_delete(id3_tag);
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -255,20 +255,12 @@ faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer &buffer,
|
||||
}
|
||||
|
||||
uint8_t channels;
|
||||
uint32_t sample_rate;
|
||||
#ifdef HAVE_FAAD_LONG
|
||||
/* neaacdec.h declares all arguments as "unsigned long", but
|
||||
internally expects uint32_t pointers. To avoid gcc
|
||||
warnings, use this workaround. */
|
||||
unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
|
||||
#else
|
||||
uint32_t *sample_rate_p = &sample_rate;
|
||||
#endif
|
||||
unsigned long sample_rate;
|
||||
long nbytes = NeAACDecInit(decoder,
|
||||
/* deconst hack, libfaad requires this */
|
||||
const_cast<unsigned char *>(data.data),
|
||||
data.size,
|
||||
sample_rate_p, &channels);
|
||||
&sample_rate, &channels);
|
||||
if (nbytes < 0) {
|
||||
error.Set(faad_decoder_domain, "Not an AAC stream");
|
||||
return false;
|
||||
|
File diff suppressed because it is too large
Load Diff
100
src/decoder/plugins/FfmpegIo.cxx
Normal file
100
src/decoder/plugins/FfmpegIo.cxx
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* necessary because libavutil/common.h uses UINT64_C */
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
|
||||
#include "config.h"
|
||||
#include "FfmpegIo.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "util/Error.hxx"
|
||||
|
||||
AvioStream::~AvioStream()
|
||||
{
|
||||
if (io != nullptr) {
|
||||
av_free(io->buffer);
|
||||
av_free(io);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size)
|
||||
{
|
||||
AvioStream *stream = (AvioStream *)opaque;
|
||||
|
||||
return decoder_read(stream->decoder, stream->input,
|
||||
(void *)buf, size);
|
||||
}
|
||||
|
||||
static int64_t
|
||||
mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
|
||||
{
|
||||
AvioStream *stream = (AvioStream *)opaque;
|
||||
|
||||
switch (whence) {
|
||||
case SEEK_SET:
|
||||
break;
|
||||
|
||||
case SEEK_CUR:
|
||||
pos += stream->input.GetOffset();
|
||||
break;
|
||||
|
||||
case SEEK_END:
|
||||
if (!stream->input.KnownSize())
|
||||
return -1;
|
||||
|
||||
pos += stream->input.GetSize();
|
||||
break;
|
||||
|
||||
case AVSEEK_SIZE:
|
||||
if (!stream->input.KnownSize())
|
||||
return -1;
|
||||
|
||||
return stream->input.GetSize();
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!stream->input.LockSeek(pos, IgnoreError()))
|
||||
return -1;
|
||||
|
||||
return stream->input.GetOffset();
|
||||
}
|
||||
|
||||
bool
|
||||
AvioStream::Open()
|
||||
{
|
||||
constexpr size_t BUFFER_SIZE = 8192;
|
||||
auto buffer = (unsigned char *)av_malloc(BUFFER_SIZE);
|
||||
if (buffer == nullptr)
|
||||
return false;
|
||||
|
||||
io = avio_alloc_context(buffer, BUFFER_SIZE,
|
||||
false, this,
|
||||
mpd_ffmpeg_stream_read, nullptr,
|
||||
input.IsSeekable()
|
||||
? mpd_ffmpeg_stream_seek : nullptr);
|
||||
/* If avio_alloc_context() fails, who frees the buffer? The
|
||||
libavformat API documentation does not specify this, it
|
||||
only says that AVIOContext.buffer must be freed in the end,
|
||||
however no AVIOContext exists in that failure code path. */
|
||||
return io != nullptr;
|
||||
}
|
48
src/decoder/plugins/FfmpegIo.hxx
Normal file
48
src/decoder/plugins/FfmpegIo.hxx
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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_FFMPEG_IO_HXX
|
||||
#define MPD_FFMPEG_IO_HXX
|
||||
|
||||
#include "check.h"
|
||||
|
||||
extern "C" {
|
||||
#include "libavformat/avio.h"
|
||||
}
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class InputStream;
|
||||
struct Decoder;
|
||||
|
||||
struct AvioStream {
|
||||
Decoder *const decoder;
|
||||
InputStream &input;
|
||||
|
||||
AVIOContext *io;
|
||||
|
||||
AvioStream(Decoder *_decoder, InputStream &_input)
|
||||
:decoder(_decoder), input(_input), io(nullptr) {}
|
||||
|
||||
~AvioStream();
|
||||
|
||||
bool Open();
|
||||
};
|
||||
|
||||
#endif
|
@@ -36,9 +36,9 @@ static const struct tag_table ffmpeg_tags[] = {
|
||||
};
|
||||
|
||||
static void
|
||||
ffmpeg_copy_metadata(TagType type,
|
||||
AVDictionary *m, const char *name,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
FfmpegScanTag(TagType type,
|
||||
AVDictionary *m, const char *name,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
AVDictionaryEntry *mt = nullptr;
|
||||
|
||||
@@ -48,8 +48,8 @@ ffmpeg_copy_metadata(TagType type,
|
||||
}
|
||||
|
||||
static void
|
||||
ffmpeg_scan_pairs(AVDictionary *dict,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
FfmpegScanPairs(AVDictionary *dict,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
AVDictionaryEntry *i = nullptr;
|
||||
|
||||
@@ -59,18 +59,20 @@ ffmpeg_scan_pairs(AVDictionary *dict,
|
||||
}
|
||||
|
||||
void
|
||||
ffmpeg_scan_dictionary(AVDictionary *dict,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
FfmpegScanDictionary(AVDictionary *dict,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
|
||||
ffmpeg_copy_metadata(TagType(i), dict, tag_item_names[i],
|
||||
handler, handler_ctx);
|
||||
if (handler->tag != nullptr) {
|
||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
|
||||
FfmpegScanTag(TagType(i), dict, tag_item_names[i],
|
||||
handler, handler_ctx);
|
||||
|
||||
for (const struct tag_table *i = ffmpeg_tags;
|
||||
i->name != nullptr; ++i)
|
||||
ffmpeg_copy_metadata(i->type, dict, i->name,
|
||||
handler, handler_ctx);
|
||||
for (const struct tag_table *i = ffmpeg_tags;
|
||||
i->name != nullptr; ++i)
|
||||
FfmpegScanTag(i->type, dict, i->name,
|
||||
handler, handler_ctx);
|
||||
}
|
||||
|
||||
if (handler->pair != nullptr)
|
||||
ffmpeg_scan_pairs(dict, handler, handler_ctx);
|
||||
FfmpegScanPairs(dict, handler, handler_ctx);
|
||||
}
|
||||
|
@@ -32,7 +32,7 @@ extern "C" {
|
||||
struct tag_handler;
|
||||
|
||||
void
|
||||
ffmpeg_scan_dictionary(AVDictionary *dict,
|
||||
const tag_handler *handler, void *handler_ctx);
|
||||
FfmpegScanDictionary(AVDictionary *dict,
|
||||
const tag_handler *handler, void *handler_ctx);
|
||||
|
||||
#endif
|
||||
|
@@ -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
|
||||
|
@@ -156,8 +156,11 @@ gme_file_decode(Decoder &decoder, Path path_fs)
|
||||
return;
|
||||
}
|
||||
|
||||
const SignedSongTime song_len = ti->length > 0
|
||||
? SignedSongTime::FromMS(ti->length)
|
||||
const int length = ti->play_length;
|
||||
gme_free_info(ti);
|
||||
|
||||
const SignedSongTime song_len = length > 0
|
||||
? SignedSongTime::FromMS(length)
|
||||
: SignedSongTime::Negative();
|
||||
|
||||
/* initialize the MPD decoder */
|
||||
@@ -168,7 +171,6 @@ gme_file_decode(Decoder &decoder, Path path_fs)
|
||||
SampleFormat::S16, GME_CHANNELS,
|
||||
error)) {
|
||||
LogError(error);
|
||||
gme_free_info(ti);
|
||||
gme_delete(emu);
|
||||
return;
|
||||
}
|
||||
@@ -179,8 +181,8 @@ gme_file_decode(Decoder &decoder, Path path_fs)
|
||||
if (gme_err != nullptr)
|
||||
LogWarning(gme_domain, gme_err);
|
||||
|
||||
if (ti->length > 0)
|
||||
gme_set_fade(emu, ti->length);
|
||||
if (length > 0)
|
||||
gme_set_fade(emu, length);
|
||||
|
||||
/* play */
|
||||
DecoderCommand cmd;
|
||||
@@ -196,16 +198,17 @@ gme_file_decode(Decoder &decoder, Path path_fs)
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
unsigned where = decoder_seek_time(decoder).ToMS();
|
||||
gme_err = gme_seek(emu, where);
|
||||
if (gme_err != nullptr)
|
||||
if (gme_err != nullptr) {
|
||||
LogWarning(gme_domain, gme_err);
|
||||
decoder_command_finished(decoder);
|
||||
decoder_seek_error(decoder);
|
||||
} else
|
||||
decoder_command_finished(decoder);
|
||||
}
|
||||
|
||||
if (gme_track_ended(emu))
|
||||
break;
|
||||
} while (cmd != DecoderCommand::STOP);
|
||||
|
||||
gme_free_info(ti);
|
||||
gme_delete(emu);
|
||||
}
|
||||
|
||||
@@ -236,9 +239,9 @@ gme_scan_file(Path path_fs,
|
||||
|
||||
assert(ti != nullptr);
|
||||
|
||||
if (ti->length > 0)
|
||||
if (ti->play_length > 0)
|
||||
tag_handler_invoke_duration(handler, handler_ctx,
|
||||
SongTime::FromMS(ti->length));
|
||||
SongTime::FromMS(ti->play_length));
|
||||
|
||||
if (ti->song != nullptr) {
|
||||
if (gme_track_count(emu) > 1) {
|
||||
|
@@ -657,7 +657,7 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
|
||||
unsigned name = mad_bit_read(ptr, 3); /* gain name */
|
||||
unsigned orig = mad_bit_read(ptr, 3); /* gain originator */
|
||||
unsigned sign = mad_bit_read(ptr, 1); /* sign bit */
|
||||
unsigned gain = mad_bit_read(ptr, 9); /* gain*10 */
|
||||
int gain = mad_bit_read(ptr, 9); /* gain*10 */
|
||||
if (gain && name == 1 && orig != 0) {
|
||||
lame->track_gain = ((sign ? -gain : gain) / 10.0) + adj;
|
||||
FormatDebug(mad_domain, "LAME track gain found: %f",
|
||||
|
@@ -1,338 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "config.h" /* must be first for large file support */
|
||||
#include "Mp4v2DecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "fs/Path.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <mp4v2/mp4v2.h>
|
||||
#include <neaacdec.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
static constexpr Domain mp4v2_decoder_domain("mp4v2");
|
||||
|
||||
static MP4TrackId
|
||||
mp4_get_aac_track(MP4FileHandle handle, NeAACDecHandle decoder,
|
||||
AudioFormat &audio_format, Error &error)
|
||||
{
|
||||
uint32_t sample_rate;
|
||||
#ifdef HAVE_FAAD_LONG
|
||||
/* neaacdec.h declares all arguments as "unsigned long", but
|
||||
internally expects uint32_t pointers. To avoid gcc
|
||||
warnings, use this workaround. */
|
||||
unsigned long *sample_rate_r = (unsigned long*)&sample_rate;
|
||||
#else
|
||||
uint32_t *sample_rate_r = sample_rate;
|
||||
#endif
|
||||
|
||||
const MP4TrackId tracks = MP4GetNumberOfTracks(handle);
|
||||
|
||||
for (MP4TrackId id = 1; id <= tracks; id++) {
|
||||
const char* track_type = MP4GetTrackType(handle, id);
|
||||
|
||||
if (track_type == 0)
|
||||
continue;
|
||||
|
||||
const auto obj_type = MP4GetTrackEsdsObjectTypeId(handle, id);
|
||||
|
||||
if (obj_type == MP4_INVALID_AUDIO_TYPE)
|
||||
continue;
|
||||
if (obj_type == MP4_MPEG4_AUDIO_TYPE) {
|
||||
const auto mpeg_type = MP4GetTrackAudioMpeg4Type(handle, id);
|
||||
if (!MP4_IS_MPEG4_AAC_AUDIO_TYPE(mpeg_type))
|
||||
continue;
|
||||
} else if (!MP4_IS_AAC_AUDIO_TYPE(obj_type))
|
||||
continue;
|
||||
|
||||
if (decoder == nullptr)
|
||||
/* found audio track, no decoder */
|
||||
return id;
|
||||
|
||||
unsigned char *buff = nullptr;
|
||||
unsigned buff_size = 0;
|
||||
|
||||
if (!MP4GetTrackESConfiguration(handle, id, &buff, &buff_size))
|
||||
continue;
|
||||
|
||||
uint8_t channels;
|
||||
int32_t nbytes = NeAACDecInit(decoder, buff, buff_size,
|
||||
sample_rate_r, &channels);
|
||||
|
||||
free(buff);
|
||||
|
||||
if (nbytes < 0)
|
||||
/* invalid stream */
|
||||
continue;
|
||||
|
||||
if (!audio_format_init_checked(audio_format, sample_rate,
|
||||
SampleFormat::S16,
|
||||
channels,
|
||||
error))
|
||||
continue;
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
error.Set(mp4v2_decoder_domain, "no valid aac track found");
|
||||
|
||||
return MP4_INVALID_TRACK_ID;
|
||||
}
|
||||
|
||||
static NeAACDecHandle
|
||||
mp4_faad_new(MP4FileHandle handle, AudioFormat &audio_format, Error &error)
|
||||
{
|
||||
const NeAACDecHandle decoder = NeAACDecOpen();
|
||||
const NeAACDecConfigurationPtr config =
|
||||
NeAACDecGetCurrentConfiguration(decoder);
|
||||
config->outputFormat = FAAD_FMT_16BIT;
|
||||
config->downMatrix = 1;
|
||||
config->dontUpSampleImplicitSBR = 0;
|
||||
NeAACDecSetConfiguration(decoder, config);
|
||||
|
||||
const auto track = mp4_get_aac_track(handle, decoder, audio_format, error);
|
||||
|
||||
if (track == MP4_INVALID_TRACK_ID) {
|
||||
NeAACDecClose(decoder);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return decoder;
|
||||
}
|
||||
|
||||
static void
|
||||
mp4_file_decode(Decoder &mpd_decoder, Path path_fs)
|
||||
{
|
||||
const MP4FileHandle handle = MP4Read(path_fs.c_str());
|
||||
|
||||
if (handle == MP4_INVALID_FILE_HANDLE) {
|
||||
FormatError(mp4v2_decoder_domain,
|
||||
"unable to open file");
|
||||
return;
|
||||
}
|
||||
|
||||
AudioFormat audio_format;
|
||||
Error error;
|
||||
const NeAACDecHandle decoder = mp4_faad_new(handle, audio_format, error);
|
||||
|
||||
if (decoder == nullptr) {
|
||||
LogError(error);
|
||||
MP4Close(handle);
|
||||
return;
|
||||
}
|
||||
|
||||
const MP4TrackId track = mp4_get_aac_track(handle, nullptr, audio_format, error);
|
||||
|
||||
/* initialize the MPD core */
|
||||
|
||||
const MP4Timestamp scale = MP4GetTrackTimeScale(handle, track);
|
||||
const SongTime duration = SongTime::FromScale<uint64_t>(MP4GetTrackDuration(handle, track),
|
||||
scale);
|
||||
const MP4SampleId num_samples = MP4GetTrackNumberOfSamples(handle, track);
|
||||
|
||||
decoder_initialized(mpd_decoder, audio_format, true, duration);
|
||||
|
||||
/* the decoder loop */
|
||||
|
||||
DecoderCommand cmd = DecoderCommand::NONE;
|
||||
|
||||
for (MP4SampleId sample = 1;
|
||||
sample < num_samples && cmd != DecoderCommand::STOP;
|
||||
sample++) {
|
||||
unsigned char *data = nullptr;
|
||||
unsigned int data_length = 0;
|
||||
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
const MP4Timestamp offset =
|
||||
decoder_seek_time(mpd_decoder).ToScale(scale);
|
||||
|
||||
sample = MP4GetSampleIdFromTime(handle, track, offset,
|
||||
false);
|
||||
decoder_command_finished(mpd_decoder);
|
||||
}
|
||||
|
||||
/* read */
|
||||
if (MP4ReadSample(handle, track, sample, &data, &data_length) == 0) {
|
||||
FormatError(mp4v2_decoder_domain, "unable to read sample");
|
||||
break;
|
||||
}
|
||||
|
||||
/* decode it */
|
||||
NeAACDecFrameInfo frame_info;
|
||||
const void *const decoded = NeAACDecDecode(decoder, &frame_info, data, data_length);
|
||||
|
||||
if (frame_info.error > 0) {
|
||||
FormatWarning(mp4v2_decoder_domain,
|
||||
"error decoding AAC stream: %s",
|
||||
NeAACDecGetErrorMessage(frame_info.error));
|
||||
break;
|
||||
}
|
||||
|
||||
if (frame_info.channels != audio_format.channels) {
|
||||
FormatDefault(mp4v2_decoder_domain,
|
||||
"channel count changed from %u to %u",
|
||||
audio_format.channels, frame_info.channels);
|
||||
break;
|
||||
}
|
||||
|
||||
if (frame_info.samplerate != audio_format.sample_rate) {
|
||||
FormatDefault(mp4v2_decoder_domain,
|
||||
"sample rate changed from %u to %lu",
|
||||
audio_format.sample_rate,
|
||||
(unsigned long)frame_info.samplerate);
|
||||
break;
|
||||
}
|
||||
|
||||
/* update bit rate and position */
|
||||
unsigned bit_rate = 0;
|
||||
|
||||
if (frame_info.samples > 0) {
|
||||
bit_rate = frame_info.bytesconsumed * 8.0 *
|
||||
frame_info.channels * audio_format.sample_rate /
|
||||
frame_info.samples / 1000 + 0.5;
|
||||
}
|
||||
|
||||
/* send PCM samples to MPD */
|
||||
|
||||
cmd = decoder_data(mpd_decoder, nullptr, decoded,
|
||||
(size_t)frame_info.samples * 2,
|
||||
bit_rate);
|
||||
|
||||
free(data);
|
||||
}
|
||||
|
||||
/* cleanup */
|
||||
NeAACDecClose(decoder);
|
||||
MP4Close(handle);
|
||||
}
|
||||
|
||||
static inline void
|
||||
mp4_safe_invoke_tag(const struct tag_handler *handler, void *handler_ctx,
|
||||
TagType tag, const char *value)
|
||||
{
|
||||
if (value != nullptr)
|
||||
tag_handler_invoke_tag(handler, handler_ctx, tag, value);
|
||||
}
|
||||
|
||||
static bool
|
||||
mp4_scan_file(Path path_fs,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
const MP4FileHandle handle = MP4Read(path_fs.c_str());
|
||||
|
||||
if (handle == MP4_INVALID_FILE_HANDLE)
|
||||
return false;
|
||||
|
||||
AudioFormat tmp_audio_format;
|
||||
Error error;
|
||||
const MP4TrackId id = mp4_get_aac_track(handle, nullptr, tmp_audio_format, error);
|
||||
|
||||
if (id == MP4_INVALID_TRACK_ID) {
|
||||
LogError(error);
|
||||
MP4Close(handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
const MP4Timestamp scale = MP4GetTrackTimeScale(handle, id);
|
||||
const SongTime dur =
|
||||
SongTime::FromScale<uint64_t>(MP4GetTrackDuration(handle, id),
|
||||
scale);
|
||||
tag_handler_invoke_duration(handler, handler_ctx, dur);
|
||||
|
||||
const MP4Tags* tags = MP4TagsAlloc();
|
||||
MP4TagsFetch(tags, handle);
|
||||
|
||||
static constexpr struct {
|
||||
const char *MP4Tags::*p;
|
||||
TagType tag_type;
|
||||
} mp4v2_tags[] = {
|
||||
{ &MP4Tags::name, TAG_NAME },
|
||||
{ &MP4Tags::artist, TAG_ARTIST },
|
||||
{ &MP4Tags::albumArtist, TAG_ALBUM_ARTIST },
|
||||
{ &MP4Tags::album, TAG_ALBUM },
|
||||
{ &MP4Tags::composer, TAG_COMPOSER },
|
||||
{ &MP4Tags::comments, TAG_COMMENT },
|
||||
{ &MP4Tags::genre, TAG_GENRE },
|
||||
{ &MP4Tags::releaseDate, TAG_DATE },
|
||||
{ &MP4Tags::sortArtist, TAG_ARTIST_SORT },
|
||||
{ &MP4Tags::sortAlbumArtist, TAG_ALBUM_ARTIST_SORT },
|
||||
};
|
||||
|
||||
for (const auto &i : mp4v2_tags)
|
||||
mp4_safe_invoke_tag(handler, handler_ctx,
|
||||
i.tag_type, tags->*i.p);
|
||||
|
||||
char buff[8]; /* tmp buffer for index to string. */
|
||||
if (tags->track != nullptr) {
|
||||
sprintf(buff, "%d", tags->track->index);
|
||||
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, buff);
|
||||
}
|
||||
|
||||
if (tags->disk != nullptr) {
|
||||
sprintf(buff, "%d", tags->disk->index);
|
||||
tag_handler_invoke_tag(handler, handler_ctx, TAG_DISC, buff);
|
||||
}
|
||||
|
||||
MP4TagsFree(tags);
|
||||
MP4Close(handle);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *const mp4_suffixes[] = {
|
||||
"mp4",
|
||||
"m4a",
|
||||
/* "m4p", encrypted */
|
||||
/* "m4b", audio book */
|
||||
/* "m4r", ring tones */
|
||||
/* "m4v", video */
|
||||
nullptr
|
||||
};
|
||||
|
||||
static const char *const mp4_mime_types[] = {
|
||||
"application/mp4",
|
||||
"application/m4a",
|
||||
"audio/mp4",
|
||||
"audio/m4a",
|
||||
/* "audio/m4p", */
|
||||
/* "audio/m4b", */
|
||||
/* "audio/m4r", */
|
||||
/* "audio/m4v", */
|
||||
nullptr
|
||||
};
|
||||
|
||||
const struct DecoderPlugin mp4v2_decoder_plugin = {
|
||||
"mp4v2",
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
mp4_file_decode,
|
||||
mp4_scan_file,
|
||||
nullptr,
|
||||
nullptr,
|
||||
mp4_suffixes,
|
||||
mp4_mime_types
|
||||
};
|
@@ -22,10 +22,12 @@
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "pcm/Traits.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/Macros.hxx"
|
||||
#include "util/Clamp.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <mpc/mpcdec.h>
|
||||
@@ -42,6 +44,9 @@ struct mpc_decoder_data {
|
||||
|
||||
static constexpr Domain mpcdec_domain("mpcdec");
|
||||
|
||||
static constexpr SampleFormat mpcdec_sample_format = SampleFormat::S24_P32;
|
||||
typedef SampleTraits<mpcdec_sample_format> MpcdecSampleTraits;
|
||||
|
||||
static mpc_int32_t
|
||||
mpc_read_cb(mpc_reader *reader, void *ptr, mpc_int32_t size)
|
||||
{
|
||||
@@ -91,18 +96,15 @@ mpc_getsize_cb(mpc_reader *reader)
|
||||
}
|
||||
|
||||
/* this _looks_ performance-critical, don't de-inline -- eric */
|
||||
static inline int32_t
|
||||
static inline MpcdecSampleTraits::value_type
|
||||
mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample)
|
||||
{
|
||||
/* only doing 16-bit audio for now */
|
||||
int32_t val;
|
||||
MpcdecSampleTraits::value_type val;
|
||||
|
||||
enum {
|
||||
bits = 24,
|
||||
};
|
||||
|
||||
const int clip_min = -1 << (bits - 1);
|
||||
const int clip_max = (1 << (bits - 1)) - 1;
|
||||
constexpr int bits = MpcdecSampleTraits::BITS;
|
||||
constexpr auto clip_min = MpcdecSampleTraits::MIN;
|
||||
constexpr auto clip_max = MpcdecSampleTraits::MAX;
|
||||
|
||||
#ifdef MPC_FIXED_POINT
|
||||
const int shift = bits - MPC_FIXED_POINT_SCALE_SHIFT;
|
||||
@@ -117,16 +119,12 @@ mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample)
|
||||
val = sample * float_scale;
|
||||
#endif
|
||||
|
||||
if (val < clip_min)
|
||||
val = clip_min;
|
||||
else if (val > clip_max)
|
||||
val = clip_max;
|
||||
|
||||
return val;
|
||||
return Clamp(val, clip_min, clip_max);
|
||||
}
|
||||
|
||||
static void
|
||||
mpc_to_mpd_buffer(int32_t *dest, const MPC_SAMPLE_FORMAT *src,
|
||||
mpc_to_mpd_buffer(MpcdecSampleTraits::pointer_type dest,
|
||||
const MPC_SAMPLE_FORMAT *src,
|
||||
unsigned num_samples)
|
||||
{
|
||||
while (num_samples-- > 0)
|
||||
@@ -162,7 +160,7 @@ mpcdec_decode(Decoder &mpd_decoder, InputStream &is)
|
||||
Error error;
|
||||
AudioFormat audio_format;
|
||||
if (!audio_format_init_checked(audio_format, info.sample_freq,
|
||||
SampleFormat::S24_P32,
|
||||
mpcdec_sample_format,
|
||||
info.channels, error)) {
|
||||
LogError(error);
|
||||
mpc_demux_exit(demux);
|
||||
@@ -214,7 +212,7 @@ mpcdec_decode(Decoder &mpd_decoder, InputStream &is)
|
||||
mpc_uint32_t ret = frame.samples;
|
||||
ret *= info.channels;
|
||||
|
||||
int32_t chunk[ARRAY_SIZE(sample_buffer)];
|
||||
MpcdecSampleTraits::value_type chunk[ARRAY_SIZE(sample_buffer)];
|
||||
mpc_to_mpd_buffer(chunk, sample_buffer, ret);
|
||||
|
||||
long bit_rate = vbr_update_bits * audio_format.sample_rate
|
||||
|
@@ -40,6 +40,12 @@
|
||||
|
||||
static constexpr opus_int32 opus_sample_rate = 48000;
|
||||
|
||||
/**
|
||||
* Allocate an output buffer for 16 bit PCM samples big enough to hold
|
||||
* a quarter second, larger than 120ms required by libopus.
|
||||
*/
|
||||
static constexpr unsigned opus_output_buffer_frames = opus_sample_rate / 4;
|
||||
|
||||
gcc_pure
|
||||
static bool
|
||||
IsOpusHead(const ogg_packet &packet)
|
||||
@@ -70,10 +76,16 @@ class MPDOpusDecoder {
|
||||
|
||||
OpusDecoder *opus_decoder;
|
||||
opus_int16 *output_buffer;
|
||||
unsigned output_size;
|
||||
|
||||
/**
|
||||
* If non-zero, then a previous Opus stream has been found
|
||||
* already with this number of channels. If opus_decoder is
|
||||
* nullptr, then its end-of-stream packet has been found
|
||||
* already.
|
||||
*/
|
||||
unsigned previous_channels;
|
||||
|
||||
bool os_initialized;
|
||||
bool found_opus;
|
||||
|
||||
int opus_serialno;
|
||||
|
||||
@@ -86,8 +98,9 @@ public:
|
||||
InputStream &_input_stream)
|
||||
:decoder(_decoder), input_stream(_input_stream),
|
||||
opus_decoder(nullptr),
|
||||
output_buffer(nullptr), output_size(0),
|
||||
os_initialized(false), found_opus(false) {}
|
||||
output_buffer(nullptr),
|
||||
previous_channels(0),
|
||||
os_initialized(false) {}
|
||||
~MPDOpusDecoder();
|
||||
|
||||
bool ReadFirstPage(OggSyncState &oy);
|
||||
@@ -96,6 +109,7 @@ public:
|
||||
DecoderCommand HandlePackets();
|
||||
DecoderCommand HandlePacket(const ogg_packet &packet);
|
||||
DecoderCommand HandleBOS(const ogg_packet &packet);
|
||||
DecoderCommand HandleEOS();
|
||||
DecoderCommand HandleTags(const ogg_packet &packet);
|
||||
DecoderCommand HandleAudio(const ogg_packet &packet);
|
||||
|
||||
@@ -159,12 +173,14 @@ inline DecoderCommand
|
||||
MPDOpusDecoder::HandlePacket(const ogg_packet &packet)
|
||||
{
|
||||
if (packet.e_o_s)
|
||||
return DecoderCommand::STOP;
|
||||
return HandleEOS();
|
||||
|
||||
if (packet.b_o_s)
|
||||
return HandleBOS(packet);
|
||||
else if (!found_opus)
|
||||
else if (opus_decoder == nullptr) {
|
||||
LogDebug(opus_domain, "BOS packet expected");
|
||||
return DecoderCommand::STOP;
|
||||
}
|
||||
|
||||
if (IsOpusTags(packet))
|
||||
return HandleTags(packet);
|
||||
@@ -184,7 +200,7 @@ LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno,
|
||||
/* we do this for local files only, because seeking
|
||||
around remote files is expensive and not worth the
|
||||
troubl */
|
||||
return -1;
|
||||
return false;
|
||||
|
||||
const auto old_offset = is.GetOffset();
|
||||
|
||||
@@ -198,7 +214,7 @@ LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno,
|
||||
ogg_stream_clear(&os);
|
||||
|
||||
/* restore the previous file position */
|
||||
is.Seek(old_offset, IgnoreError());
|
||||
is.LockSeek(old_offset, IgnoreError());
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -225,19 +241,29 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
|
||||
{
|
||||
assert(packet.b_o_s);
|
||||
|
||||
if (found_opus || !IsOpusHead(packet))
|
||||
if (opus_decoder != nullptr || !IsOpusHead(packet)) {
|
||||
LogDebug(opus_domain, "BOS packet must be OpusHead");
|
||||
return DecoderCommand::STOP;
|
||||
}
|
||||
|
||||
unsigned channels;
|
||||
if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
|
||||
!audio_valid_channel_count(channels))
|
||||
!audio_valid_channel_count(channels)) {
|
||||
LogDebug(opus_domain, "Malformed BOS packet");
|
||||
return DecoderCommand::STOP;
|
||||
}
|
||||
|
||||
assert(opus_decoder == nullptr);
|
||||
assert(output_buffer == nullptr);
|
||||
assert((previous_channels == 0) == (output_buffer == nullptr));
|
||||
|
||||
if (previous_channels != 0 && channels != previous_channels) {
|
||||
FormatWarning(opus_domain,
|
||||
"Next stream has different channels (%u -> %u)",
|
||||
previous_channels, channels);
|
||||
return DecoderCommand::STOP;
|
||||
}
|
||||
|
||||
opus_serialno = os.serialno;
|
||||
found_opus = true;
|
||||
|
||||
/* TODO: parse attributes from the OpusHead (sample rate,
|
||||
channels, ...) */
|
||||
@@ -251,6 +277,13 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
|
||||
return DecoderCommand::STOP;
|
||||
}
|
||||
|
||||
if (previous_channels != 0) {
|
||||
/* decoder was already initialized by the previous
|
||||
stream; skip the rest of this method */
|
||||
LogDebug(opus_domain, "Found another stream");
|
||||
return decoder_get_command(decoder);
|
||||
}
|
||||
|
||||
eos_granulepos = LoadEOSGranulePos(input_stream, &decoder,
|
||||
opus_serialno);
|
||||
const auto duration = eos_granulepos >= 0
|
||||
@@ -258,21 +291,36 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
|
||||
opus_sample_rate)
|
||||
: SignedSongTime::Negative();
|
||||
|
||||
previous_channels = channels;
|
||||
const AudioFormat audio_format(opus_sample_rate,
|
||||
SampleFormat::S16, channels);
|
||||
decoder_initialized(decoder, audio_format,
|
||||
eos_granulepos > 0, duration);
|
||||
frame_size = audio_format.GetFrameSize();
|
||||
|
||||
/* allocate an output buffer for 16 bit PCM samples big enough
|
||||
to hold a quarter second, larger than 120ms required by
|
||||
libopus */
|
||||
output_size = audio_format.sample_rate / 4;
|
||||
output_buffer = new opus_int16[output_size * audio_format.channels];
|
||||
output_buffer = new opus_int16[opus_output_buffer_frames
|
||||
* audio_format.channels];
|
||||
|
||||
return decoder_get_command(decoder);
|
||||
}
|
||||
|
||||
inline DecoderCommand
|
||||
MPDOpusDecoder::HandleEOS()
|
||||
{
|
||||
if (eos_granulepos < 0 && previous_channels != 0) {
|
||||
/* allow chaining of (unseekable) streams */
|
||||
assert(opus_decoder != nullptr);
|
||||
assert(output_buffer != nullptr);
|
||||
|
||||
opus_decoder_destroy(opus_decoder);
|
||||
opus_decoder = nullptr;
|
||||
|
||||
return decoder_get_command(decoder);
|
||||
}
|
||||
|
||||
return DecoderCommand::STOP;
|
||||
}
|
||||
|
||||
inline DecoderCommand
|
||||
MPDOpusDecoder::HandleTags(const ogg_packet &packet)
|
||||
{
|
||||
@@ -304,10 +352,11 @@ MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
|
||||
int nframes = opus_decode(opus_decoder,
|
||||
(const unsigned char*)packet.packet,
|
||||
packet.bytes,
|
||||
output_buffer, output_size,
|
||||
output_buffer, opus_output_buffer_frames,
|
||||
0);
|
||||
if (nframes < 0) {
|
||||
LogError(opus_domain, opus_strerror(nframes));
|
||||
FormatError(opus_domain, "libopus error: %s",
|
||||
opus_strerror(nframes));
|
||||
return DecoderCommand::STOP;
|
||||
}
|
||||
|
||||
@@ -392,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;
|
||||
@@ -417,6 +468,8 @@ mpd_opus_scan_stream(InputStream &is,
|
||||
continue;
|
||||
}
|
||||
|
||||
--remaining_packets;
|
||||
|
||||
if (packet.b_o_s) {
|
||||
if (!IsOpusHead(packet))
|
||||
break;
|
||||
@@ -461,6 +514,13 @@ static const char *const opus_suffixes[] = {
|
||||
};
|
||||
|
||||
static const char *const opus_mime_types[] = {
|
||||
/* the official MIME type (RFC 5334) */
|
||||
"audio/ogg",
|
||||
|
||||
/* deprecated (RFC 5334) */
|
||||
"application/ogg",
|
||||
|
||||
/* deprecated; from an early draft */
|
||||
"audio/opus",
|
||||
nullptr
|
||||
};
|
||||
|
@@ -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);
|
||||
|
@@ -22,67 +22,61 @@
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "fs/Path.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "util/Macros.hxx"
|
||||
#include "util/FormatString.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "system/FatalError.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <glib.h>
|
||||
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
#include <sidplayfp/sidplayfp.h>
|
||||
#include <sidplayfp/SidInfo.h>
|
||||
#include <sidplayfp/SidConfig.h>
|
||||
#include <sidplayfp/SidTune.h>
|
||||
#include <sidplayfp/SidTuneInfo.h>
|
||||
#include <sidplayfp/builders/resid.h>
|
||||
#include <sidplayfp/builders/residfp.h>
|
||||
#include <sidplayfp/SidDatabase.h>
|
||||
#else
|
||||
#include <sidplay/sidplay2.h>
|
||||
#include <sidplay/builders/resid.h>
|
||||
#include <sidplay/utils/SidTuneMod.h>
|
||||
#include <sidplay/utils/SidDatabase.h>
|
||||
#endif
|
||||
|
||||
#define SUBTUNE_PREFIX "tune_"
|
||||
|
||||
static constexpr Domain sidplay_domain("sidplay");
|
||||
|
||||
static GPatternSpec *path_with_subtune;
|
||||
static const char *songlength_file;
|
||||
static GKeyFile *songlength_database;
|
||||
static SidDatabase *songlength_database;
|
||||
|
||||
static bool all_files_are_containers;
|
||||
static unsigned default_songlength;
|
||||
|
||||
static bool filter_setting;
|
||||
|
||||
static GKeyFile *
|
||||
sidplay_load_songlength_db(const char *path)
|
||||
static SidDatabase *
|
||||
sidplay_load_songlength_db(const Path path)
|
||||
{
|
||||
GError *error = nullptr;
|
||||
gchar *data;
|
||||
gsize size;
|
||||
|
||||
if (!g_file_get_contents(path, &data, &size, &error)) {
|
||||
SidDatabase *db = new SidDatabase();
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
bool error = !db->open(path.c_str());
|
||||
#else
|
||||
bool error = db->open(path.c_str()) < 0;
|
||||
#endif
|
||||
if (error) {
|
||||
FormatError(sidplay_domain,
|
||||
"unable to read songlengths file %s: %s",
|
||||
path, error->message);
|
||||
g_error_free(error);
|
||||
path.c_str(), db->error());
|
||||
delete db;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* replace any ; comment characters with # */
|
||||
for (gsize i = 0; i < size; i++)
|
||||
if (data[i] == ';')
|
||||
data[i] = '#';
|
||||
|
||||
GKeyFile *db = g_key_file_new();
|
||||
bool success = g_key_file_load_from_data(db, data, size,
|
||||
G_KEY_FILE_NONE, &error);
|
||||
g_free(data);
|
||||
if (!success) {
|
||||
FormatError(sidplay_domain,
|
||||
"unable to parse songlengths file %s: %s",
|
||||
path, error->message);
|
||||
g_error_free(error);
|
||||
g_key_file_free(db);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
g_key_file_set_list_separator(db, ' ');
|
||||
return db;
|
||||
}
|
||||
|
||||
@@ -90,18 +84,18 @@ static bool
|
||||
sidplay_init(const config_param ¶m)
|
||||
{
|
||||
/* read the songlengths database file */
|
||||
songlength_file = param.GetBlockValue("songlength_database");
|
||||
if (songlength_file != nullptr)
|
||||
songlength_database = sidplay_load_songlength_db(songlength_file);
|
||||
Error error;
|
||||
const auto database_path = param.GetBlockPath("songlength_database", error);
|
||||
if (!database_path.IsNull())
|
||||
songlength_database = sidplay_load_songlength_db(database_path);
|
||||
else if (error.IsDefined())
|
||||
FatalError(error);
|
||||
|
||||
default_songlength = param.GetBlockValue("default_songlength", 0u);
|
||||
|
||||
all_files_are_containers =
|
||||
param.GetBlockValue("all_files_are_containers", true);
|
||||
|
||||
path_with_subtune=g_pattern_spec_new(
|
||||
"*/" SUBTUNE_PREFIX "???.sid");
|
||||
|
||||
filter_setting = param.GetBlockValue("filter", true);
|
||||
|
||||
return true;
|
||||
@@ -110,98 +104,81 @@ sidplay_init(const config_param ¶m)
|
||||
static void
|
||||
sidplay_finish()
|
||||
{
|
||||
g_pattern_spec_free(path_with_subtune);
|
||||
|
||||
if(songlength_database)
|
||||
g_key_file_free(songlength_database);
|
||||
delete songlength_database;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the file path stripped of any /tune_xxx.sid subtune
|
||||
* suffix
|
||||
*/
|
||||
static char *
|
||||
get_container_name(Path path_fs)
|
||||
{
|
||||
char *path_container = strdup(path_fs.c_str());
|
||||
struct SidplayContainerPath {
|
||||
AllocatedPath path;
|
||||
unsigned track;
|
||||
};
|
||||
|
||||
if(!g_pattern_match(path_with_subtune,
|
||||
strlen(path_container), path_container, nullptr))
|
||||
return path_container;
|
||||
|
||||
char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX);
|
||||
if(ptr) *ptr='\0';
|
||||
|
||||
return path_container;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns tune number from file.sid/tune_xxx.sid style path or 1 if
|
||||
* no subtune is appended
|
||||
*/
|
||||
gcc_pure
|
||||
static unsigned
|
||||
get_song_num(const char *path_fs)
|
||||
ParseSubtuneName(const char *base)
|
||||
{
|
||||
if(g_pattern_match(path_with_subtune,
|
||||
strlen(path_fs), path_fs, nullptr)) {
|
||||
char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
|
||||
if(!sub) return 1;
|
||||
if (memcmp(base, SUBTUNE_PREFIX, sizeof(SUBTUNE_PREFIX) - 1) != 0)
|
||||
return 0;
|
||||
|
||||
sub+=strlen("/" SUBTUNE_PREFIX);
|
||||
int song_num=strtol(sub, nullptr, 10);
|
||||
base += sizeof(SUBTUNE_PREFIX) - 1;
|
||||
|
||||
if (errno == EINVAL)
|
||||
return 1;
|
||||
else
|
||||
return song_num;
|
||||
} else
|
||||
return 1;
|
||||
char *endptr;
|
||||
auto track = strtoul(base, &endptr, 10);
|
||||
if (endptr == base || *endptr != '.')
|
||||
return 0;
|
||||
|
||||
return track;
|
||||
}
|
||||
|
||||
/* get the song length in seconds */
|
||||
/**
|
||||
* returns the file path stripped of any /tune_xxx.* subtune suffix
|
||||
* and the track number (or 1 if no "tune_xxx" suffix is present).
|
||||
*/
|
||||
static SidplayContainerPath
|
||||
ParseContainerPath(Path path_fs)
|
||||
{
|
||||
const Path base = path_fs.GetBase();
|
||||
unsigned track;
|
||||
if (base.IsNull() ||
|
||||
(track = ParseSubtuneName(base.c_str())) < 1)
|
||||
return { AllocatedPath(path_fs), 1 };
|
||||
|
||||
return { path_fs.GetDirectoryName(), track };
|
||||
}
|
||||
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
|
||||
static SignedSongTime
|
||||
get_song_length(Path path_fs)
|
||||
get_song_length(SidTune &tune)
|
||||
{
|
||||
if (songlength_database == nullptr)
|
||||
return SignedSongTime::Negative();
|
||||
|
||||
char *sid_file = get_container_name(path_fs);
|
||||
SidTuneMod tune(sid_file);
|
||||
free(sid_file);
|
||||
if(!tune) {
|
||||
LogWarning(sidplay_domain,
|
||||
"failed to load file for calculating md5 sum");
|
||||
const auto length = songlength_database->length(tune);
|
||||
if (length < 0)
|
||||
return SignedSongTime::Negative();
|
||||
}
|
||||
char md5sum[SIDTUNE_MD5_LENGTH+1];
|
||||
tune.createMD5(md5sum);
|
||||
|
||||
const unsigned song_num = get_song_num(path_fs.c_str());
|
||||
|
||||
gsize num_items;
|
||||
gchar **values=g_key_file_get_string_list(songlength_database,
|
||||
"Database", md5sum, &num_items, nullptr);
|
||||
if(!values || song_num>num_items) {
|
||||
g_strfreev(values);
|
||||
return SignedSongTime::Negative();
|
||||
}
|
||||
|
||||
int minutes=strtol(values[song_num-1], nullptr, 10);
|
||||
if(errno==EINVAL) minutes=0;
|
||||
|
||||
int seconds;
|
||||
char *ptr=strchr(values[song_num-1], ':');
|
||||
if(ptr) {
|
||||
seconds=strtol(ptr+1, nullptr, 10);
|
||||
if(errno==EINVAL) seconds=0;
|
||||
} else
|
||||
seconds=0;
|
||||
|
||||
g_strfreev(values);
|
||||
|
||||
return SignedSongTime::FromS((minutes * 60) + seconds);
|
||||
return SignedSongTime::FromS(length);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static SignedSongTime
|
||||
get_song_length(SidTuneMod &tune)
|
||||
{
|
||||
assert(tune);
|
||||
|
||||
if (songlength_database == nullptr)
|
||||
return SignedSongTime::Negative();
|
||||
|
||||
const auto length = songlength_database->length(tune);
|
||||
if (length < 0)
|
||||
return SignedSongTime::Negative();
|
||||
|
||||
return SignedSongTime::FromS(length);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void
|
||||
sidplay_file_decode(Decoder &decoder, Path path_fs)
|
||||
{
|
||||
@@ -209,26 +186,43 @@ sidplay_file_decode(Decoder &decoder, Path path_fs)
|
||||
|
||||
/* load the tune */
|
||||
|
||||
char *path_container=get_container_name(path_fs);
|
||||
SidTune tune(path_container, nullptr, true);
|
||||
free(path_container);
|
||||
if (!tune) {
|
||||
LogWarning(sidplay_domain, "failed to load file");
|
||||
const auto container = ParseContainerPath(path_fs);
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
SidTune tune(container.path.c_str());
|
||||
#else
|
||||
SidTuneMod tune(container.path.c_str());
|
||||
#endif
|
||||
if (!tune.getStatus()) {
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
const char *error = tune.statusString();
|
||||
#else
|
||||
const char *error = tune.getInfo().statusString;
|
||||
#endif
|
||||
FormatWarning(sidplay_domain, "failed to load file: %s",
|
||||
error);
|
||||
return;
|
||||
}
|
||||
|
||||
const int song_num = get_song_num(path_fs.c_str());
|
||||
const int song_num = container.track;
|
||||
tune.selectSong(song_num);
|
||||
|
||||
auto duration = get_song_length(path_fs);
|
||||
auto duration = get_song_length(tune);
|
||||
if (duration.IsNegative() && default_songlength > 0)
|
||||
duration = SongTime::FromS(default_songlength);
|
||||
|
||||
/* initialize the player */
|
||||
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
sidplayfp player;
|
||||
#else
|
||||
sidplay2 player;
|
||||
int iret = player.load(&tune);
|
||||
if (iret != 0) {
|
||||
#endif
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
bool error = !player.load(&tune);
|
||||
#else
|
||||
bool error = player.load(&tune) < 0;
|
||||
#endif
|
||||
if (error) {
|
||||
FormatWarning(sidplay_domain,
|
||||
"sidplay2.load() failed: %s", player.error());
|
||||
return;
|
||||
@@ -236,53 +230,104 @@ sidplay_file_decode(Decoder &decoder, Path path_fs)
|
||||
|
||||
/* initialize the builder */
|
||||
|
||||
ReSIDBuilder builder("ReSID");
|
||||
if (!builder) {
|
||||
LogWarning(sidplay_domain,
|
||||
"failed to initialize ReSIDBuilder");
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
ReSIDfpBuilder builder("ReSID");
|
||||
if (!builder.getStatus()) {
|
||||
FormatWarning(sidplay_domain,
|
||||
"failed to initialize ReSIDfpBuilder: %s",
|
||||
builder.error());
|
||||
return;
|
||||
}
|
||||
|
||||
builder.create(player.info().maxsids());
|
||||
if (!builder.getStatus()) {
|
||||
FormatWarning(sidplay_domain,
|
||||
"ReSIDfpBuilder.create() failed: %s",
|
||||
builder.error());
|
||||
return;
|
||||
}
|
||||
#else
|
||||
ReSIDBuilder builder("ReSID");
|
||||
builder.create(player.info().maxsids);
|
||||
if (!builder) {
|
||||
LogWarning(sidplay_domain, "ReSIDBuilder.create() failed");
|
||||
FormatWarning(sidplay_domain, "ReSIDBuilder.create() failed: %s",
|
||||
builder.error());
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
builder.filter(filter_setting);
|
||||
if (!builder) {
|
||||
LogWarning(sidplay_domain, "ReSIDBuilder.filter() failed");
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
if (!builder.getStatus()) {
|
||||
FormatWarning(sidplay_domain,
|
||||
"ReSIDfpBuilder.filter() failed: %s",
|
||||
builder.error());
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if (!builder) {
|
||||
FormatWarning(sidplay_domain, "ReSIDBuilder.filter() failed: %s",
|
||||
builder.error());
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* configure the player */
|
||||
|
||||
sid2_config_t config = player.config();
|
||||
auto config = player.config();
|
||||
|
||||
#ifndef HAVE_SIDPLAYFP
|
||||
config.clockDefault = SID2_CLOCK_PAL;
|
||||
config.clockForced = true;
|
||||
config.clockSpeed = SID2_CLOCK_CORRECT;
|
||||
#endif
|
||||
config.frequency = 48000;
|
||||
#ifndef HAVE_SIDPLAYFP
|
||||
config.optimisation = SID2_DEFAULT_OPTIMISATION;
|
||||
|
||||
config.precision = 16;
|
||||
config.sidDefault = SID2_MOS6581;
|
||||
#endif
|
||||
config.sidEmulation = &builder;
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
config.samplingMethod = SidConfig::INTERPOLATE;
|
||||
config.fastSampling = false;
|
||||
#else
|
||||
config.sidModel = SID2_MODEL_CORRECT;
|
||||
config.sidSamples = true;
|
||||
config.sampleFormat = IsLittleEndian()
|
||||
? SID2_LITTLE_SIGNED
|
||||
: SID2_BIG_SIGNED;
|
||||
if (tune.isStereo()) {
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
const bool stereo = tune.getInfo()->sidChips() >= 2;
|
||||
#else
|
||||
const bool stereo = tune.isStereo();
|
||||
#endif
|
||||
|
||||
if (stereo) {
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
config.playback = SidConfig::STEREO;
|
||||
#else
|
||||
config.playback = sid2_stereo;
|
||||
#endif
|
||||
channels = 2;
|
||||
} else {
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
config.playback = SidConfig::MONO;
|
||||
#else
|
||||
config.playback = sid2_mono;
|
||||
#endif
|
||||
channels = 1;
|
||||
}
|
||||
|
||||
iret = player.config(config);
|
||||
if (iret != 0) {
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
error = !player.config(config);
|
||||
#else
|
||||
error = player.config(config) < 0;
|
||||
#endif
|
||||
if (error) {
|
||||
FormatWarning(sidplay_domain,
|
||||
"sidplay2.config() failed: %s", player.error());
|
||||
return;
|
||||
@@ -297,17 +342,21 @@ sidplay_file_decode(Decoder &decoder, Path path_fs)
|
||||
|
||||
/* .. and play */
|
||||
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
constexpr unsigned timebase = 1;
|
||||
#else
|
||||
const unsigned timebase = player.timebase();
|
||||
#endif
|
||||
const unsigned end = duration.IsNegative()
|
||||
? 0u
|
||||
: duration.ToScale<uint64_t>(timebase);
|
||||
|
||||
DecoderCommand cmd;
|
||||
do {
|
||||
char buffer[4096];
|
||||
short buffer[4096];
|
||||
size_t nbytes;
|
||||
|
||||
nbytes = player.play(buffer, sizeof(buffer));
|
||||
nbytes = player.play(buffer, ARRAY_SIZE(buffer));
|
||||
if (nbytes == 0)
|
||||
break;
|
||||
|
||||
@@ -328,7 +377,7 @@ sidplay_file_decode(Decoder &decoder, Path path_fs)
|
||||
|
||||
/* ignore data until target time is reached */
|
||||
while(data_time<target_time) {
|
||||
nbytes=player.play(buffer, sizeof(buffer));
|
||||
nbytes=player.play(buffer, ARRAY_SIZE(buffer));
|
||||
if(nbytes==0)
|
||||
break;
|
||||
data_time = player.time();
|
||||
@@ -343,41 +392,72 @@ sidplay_file_decode(Decoder &decoder, Path path_fs)
|
||||
} while (cmd != DecoderCommand::STOP);
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
static const char *
|
||||
GetInfoString(const SidTuneInfo &info, unsigned i)
|
||||
{
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
return info.numberOfInfoStrings() > i
|
||||
? info.infoString(i)
|
||||
: nullptr;
|
||||
#else
|
||||
return info.numberOfInfoStrings > i
|
||||
? info.infoString[i]
|
||||
: nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool
|
||||
sidplay_scan_file(Path path_fs,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
const int song_num = get_song_num(path_fs.c_str());
|
||||
char *path_container=get_container_name(path_fs);
|
||||
const auto container = ParseContainerPath(path_fs);
|
||||
const unsigned song_num = container.track;
|
||||
|
||||
SidTune tune(path_container, nullptr, true);
|
||||
free(path_container);
|
||||
if (!tune)
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
SidTune tune(container.path.c_str());
|
||||
#else
|
||||
SidTuneMod tune(container.path.c_str());
|
||||
#endif
|
||||
if (!tune.getStatus())
|
||||
return false;
|
||||
|
||||
tune.selectSong(song_num);
|
||||
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
const SidTuneInfo &info = *tune.getInfo();
|
||||
const unsigned n_tracks = info.songs();
|
||||
#else
|
||||
const SidTuneInfo &info = tune.getInfo();
|
||||
const unsigned n_tracks = info.songs;
|
||||
#endif
|
||||
|
||||
/* title */
|
||||
const char *title;
|
||||
if (info.numberOfInfoStrings > 0 && info.infoString[0] != nullptr)
|
||||
title=info.infoString[0];
|
||||
else
|
||||
title="";
|
||||
const char *title = GetInfoString(info, 0);
|
||||
if (title == nullptr)
|
||||
title = "";
|
||||
|
||||
if(info.songs>1) {
|
||||
if (n_tracks > 1) {
|
||||
char tag_title[1024];
|
||||
snprintf(tag_title, sizeof(tag_title),
|
||||
"%s (%d/%d)",
|
||||
title, song_num, info.songs);
|
||||
"%s (%d/%u)",
|
||||
title, song_num, n_tracks);
|
||||
tag_handler_invoke_tag(handler, handler_ctx,
|
||||
TAG_TITLE, tag_title);
|
||||
} else
|
||||
tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, title);
|
||||
|
||||
/* artist */
|
||||
if (info.numberOfInfoStrings > 1 && info.infoString[1] != nullptr)
|
||||
const char *artist = GetInfoString(info, 1);
|
||||
if (artist != nullptr)
|
||||
tag_handler_invoke_tag(handler, handler_ctx, TAG_ARTIST,
|
||||
info.infoString[1]);
|
||||
artist);
|
||||
|
||||
/* date */
|
||||
const char *date = GetInfoString(info, 2);
|
||||
if (date != nullptr)
|
||||
tag_handler_invoke_tag(handler, handler_ctx, TAG_DATE,
|
||||
date);
|
||||
|
||||
/* track */
|
||||
char track[16];
|
||||
@@ -385,7 +465,7 @@ sidplay_scan_file(Path path_fs,
|
||||
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track);
|
||||
|
||||
/* time */
|
||||
const auto duration = get_song_length(path_fs);
|
||||
const auto duration = get_song_length(tune);
|
||||
if (!duration.IsNegative())
|
||||
tag_handler_invoke_duration(handler, handler_ctx,
|
||||
SongTime(duration));
|
||||
@@ -397,19 +477,25 @@ static char *
|
||||
sidplay_container_scan(Path path_fs, const unsigned int tnum)
|
||||
{
|
||||
SidTune tune(path_fs.c_str(), nullptr, true);
|
||||
if (!tune)
|
||||
if (!tune.getStatus())
|
||||
return nullptr;
|
||||
|
||||
const SidTuneInfo &info=tune.getInfo();
|
||||
#ifdef HAVE_SIDPLAYFP
|
||||
const SidTuneInfo &info = *tune.getInfo();
|
||||
const unsigned n_tracks = info.songs();
|
||||
#else
|
||||
const SidTuneInfo &info = tune.getInfo();
|
||||
const unsigned n_tracks = info.songs;
|
||||
#endif
|
||||
|
||||
/* Don't treat sids containing a single tune
|
||||
as containers */
|
||||
if(!all_files_are_containers && info.songs<2)
|
||||
if(!all_files_are_containers && n_tracks < 2)
|
||||
return nullptr;
|
||||
|
||||
/* Construct container/tune path names, eg.
|
||||
Delta.sid/tune_001.sid */
|
||||
if(tnum<=info.songs) {
|
||||
if (tnum <= n_tracks) {
|
||||
return FormatNew(SUBTUNE_PREFIX "%03u.sid", tnum);
|
||||
} else
|
||||
return nullptr;
|
||||
|
@@ -157,8 +157,6 @@ flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error)
|
||||
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
|
||||
unsigned bits_per_sample;
|
||||
|
||||
encoder->audio_format = audio_format;
|
||||
|
||||
/* FIXME: flac should support 32bit as well */
|
||||
switch (audio_format.format) {
|
||||
case SampleFormat::S8:
|
||||
@@ -178,6 +176,8 @@ flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error)
|
||||
audio_format.format = SampleFormat::S24_P32;
|
||||
}
|
||||
|
||||
encoder->audio_format = audio_format;
|
||||
|
||||
/* allocate the encoder */
|
||||
encoder->fse = FLAC__stream_encoder_new();
|
||||
if (encoder->fse == nullptr) {
|
||||
|
@@ -66,7 +66,7 @@ struct opus_encoder {
|
||||
|
||||
ogg_int64_t granulepos;
|
||||
|
||||
opus_encoder():encoder(opus_encoder_plugin) {}
|
||||
opus_encoder():encoder(opus_encoder_plugin), granulepos(0) {}
|
||||
};
|
||||
|
||||
static constexpr Domain opus_encoder_domain("opus_encoder");
|
||||
|
@@ -26,7 +26,6 @@
|
||||
#include "util/NumberParser.hxx"
|
||||
#include "util/DynamicFifoBuffer.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
@@ -60,8 +59,6 @@ struct ShineEncoder {
|
||||
bool WriteChunk(bool flush);
|
||||
};
|
||||
|
||||
static constexpr Domain shine_encoder_domain("shine_encoder");
|
||||
|
||||
inline bool
|
||||
ShineEncoder::Configure(const config_param ¶m,
|
||||
gcc_unused Error &error)
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -21,9 +21,6 @@
|
||||
#define MPD_SOCKET_DEFERRED_MONITOR_HXX
|
||||
|
||||
#include "check.h"
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
class EventLoop;
|
||||
|
||||
|
@@ -179,9 +179,10 @@ EventLoop::Run()
|
||||
mutex.lock();
|
||||
HandleDeferred();
|
||||
busy = false;
|
||||
const bool _again = again;
|
||||
mutex.unlock();
|
||||
|
||||
if (again)
|
||||
if (_again)
|
||||
/* re-evaluate timers because one of the
|
||||
IdleMonitors may have added a new
|
||||
timeout */
|
||||
|
@@ -130,7 +130,7 @@ get_remote_uid(int fd)
|
||||
socklen_t len = sizeof (cred);
|
||||
|
||||
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0)
|
||||
return 0;
|
||||
return -1;
|
||||
|
||||
return cred.uid;
|
||||
#else
|
||||
|
@@ -53,10 +53,11 @@ public:
|
||||
children.emplace_back(name, filter);
|
||||
}
|
||||
|
||||
virtual AudioFormat Open(AudioFormat &af, Error &error) override;
|
||||
virtual void Close();
|
||||
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||
Error &error);
|
||||
/* virtual methods from class Filter */
|
||||
AudioFormat Open(AudioFormat &af, Error &error) override;
|
||||
void Close() override;
|
||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||
Error &error) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
|
@@ -34,10 +34,11 @@ class NormalizeFilter final : public Filter {
|
||||
PcmBuffer buffer;
|
||||
|
||||
public:
|
||||
virtual AudioFormat Open(AudioFormat &af, Error &error) override;
|
||||
virtual void Close();
|
||||
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||
Error &error) override;
|
||||
/* virtual methods from class Filter */
|
||||
AudioFormat Open(AudioFormat &af, Error &error) override;
|
||||
void Close() override;
|
||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||
Error &error) override;
|
||||
};
|
||||
|
||||
static Filter *
|
||||
|
@@ -112,10 +112,11 @@ public:
|
||||
*/
|
||||
void Update();
|
||||
|
||||
virtual AudioFormat Open(AudioFormat &af, Error &error) override;
|
||||
virtual void Close();
|
||||
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||
Error &error) override;
|
||||
/* virtual methods from class Filter */
|
||||
AudioFormat Open(AudioFormat &af, Error &error) override;
|
||||
void Close() override;
|
||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||
Error &error) override;
|
||||
};
|
||||
|
||||
void
|
||||
@@ -133,8 +134,6 @@ ReplayGainFilter::Update()
|
||||
volume = pcm_float_to_volume(scale);
|
||||
}
|
||||
|
||||
pv.SetVolume(volume);
|
||||
|
||||
if (mixer != nullptr) {
|
||||
/* update the hardware mixer volume */
|
||||
|
||||
@@ -145,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 *
|
||||
@@ -173,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>
|
||||
|
||||
@@ -120,10 +122,11 @@ public:
|
||||
*/
|
||||
bool Configure(const config_param ¶m, Error &error);
|
||||
|
||||
virtual AudioFormat Open(AudioFormat &af, Error &error) override;
|
||||
virtual void Close();
|
||||
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||
Error &error) override;
|
||||
/* virtual methods from class Filter */
|
||||
AudioFormat Open(AudioFormat &af, Error &error) override;
|
||||
void Close() override;
|
||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||
Error &error) override;
|
||||
};
|
||||
|
||||
bool
|
||||
@@ -265,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
|
||||
|
@@ -26,7 +26,6 @@
|
||||
#include "AudioFormat.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
@@ -43,14 +42,13 @@ public:
|
||||
pv.SetVolume(_volume);
|
||||
}
|
||||
|
||||
virtual AudioFormat Open(AudioFormat &af, Error &error) override;
|
||||
virtual void Close();
|
||||
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||
Error &error) override;
|
||||
/* virtual methods from class Filter */
|
||||
AudioFormat Open(AudioFormat &af, Error &error) override;
|
||||
void Close() override;
|
||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||
Error &error) override;
|
||||
};
|
||||
|
||||
static constexpr Domain volume_domain("pcm_volume");
|
||||
|
||||
static Filter *
|
||||
volume_filter_init(gcc_unused const config_param ¶m,
|
||||
gcc_unused Error &error)
|
||||
|
@@ -46,7 +46,11 @@ AllocatedPath
|
||||
AllocatedPath::FromUTF8(const char *path_utf8)
|
||||
{
|
||||
#ifdef HAVE_GLIB
|
||||
return AllocatedPath(Donate(), ::PathFromUTF8(path_utf8));
|
||||
char *path = ::PathFromUTF8(path_utf8);
|
||||
if (path == nullptr)
|
||||
return AllocatedPath::Null();
|
||||
|
||||
return AllocatedPath(Donate(), path);
|
||||
#else
|
||||
return FromFS(path_utf8);
|
||||
#endif
|
||||
|
@@ -252,7 +252,7 @@ public:
|
||||
void ChopSeparators();
|
||||
|
||||
gcc_pure
|
||||
bool IsAbsolute() {
|
||||
bool IsAbsolute() const {
|
||||
return PathTraitsFS::IsAbsolute(c_str());
|
||||
}
|
||||
};
|
||||
|
@@ -34,6 +34,8 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef HAVE_GLIB
|
||||
|
||||
/**
|
||||
* Maximal number of bytes required to represent path name in UTF-8
|
||||
* (including nul-terminator).
|
||||
@@ -44,7 +46,6 @@
|
||||
*/
|
||||
static constexpr size_t MPD_PATH_MAX_UTF8 = (MPD_PATH_MAX - 1) * 4 + 1;
|
||||
|
||||
#ifdef HAVE_GLIB
|
||||
static std::string fs_charset;
|
||||
|
||||
gcc_pure
|
||||
@@ -102,7 +103,10 @@ static inline void FixSeparators(std::string &s)
|
||||
std::string
|
||||
PathToUTF8(const char *path_fs)
|
||||
{
|
||||
#if !CLANG_CHECK_VERSION(3,6)
|
||||
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||
assert(path_fs != nullptr);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GLIB
|
||||
if (fs_charset.empty()) {
|
||||
@@ -143,7 +147,10 @@ PathToUTF8(const char *path_fs)
|
||||
char *
|
||||
PathFromUTF8(const char *path_utf8)
|
||||
{
|
||||
#if !CLANG_CHECK_VERSION(3,6)
|
||||
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||
assert(path_utf8 != nullptr);
|
||||
#endif
|
||||
|
||||
if (fs_charset.empty())
|
||||
return g_strdup(path_utf8);
|
||||
|
@@ -29,6 +29,8 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
class AllocatedPath;
|
||||
|
||||
/**
|
||||
* A path name in the native file system character set.
|
||||
*
|
||||
@@ -128,6 +130,22 @@ public:
|
||||
gcc_pure
|
||||
std::string ToUTF8() const;
|
||||
|
||||
/**
|
||||
* Determine the "base" file name.
|
||||
* The return value points inside this object.
|
||||
*/
|
||||
gcc_pure
|
||||
Path GetBase() const {
|
||||
return FromFS(PathTraitsFS::GetBase(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets directory name of this path.
|
||||
* Returns a "nulled" instance on error.
|
||||
*/
|
||||
gcc_pure
|
||||
AllocatedPath GetDirectoryName() const;
|
||||
|
||||
/**
|
||||
* Determine the relative part of the given path to this
|
||||
* object, not including the directory separator. Returns an
|
||||
@@ -140,7 +158,7 @@ public:
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
bool IsAbsolute() {
|
||||
bool IsAbsolute() const {
|
||||
return PathTraitsFS::IsAbsolute(c_str());
|
||||
}
|
||||
};
|
||||
|
28
src/fs/Path2.cxx
Normal file
28
src/fs/Path2.cxx
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "Path.hxx"
|
||||
#include "AllocatedPath.hxx"
|
||||
|
||||
AllocatedPath
|
||||
Path::GetDirectoryName() const
|
||||
{
|
||||
return AllocatedPath::FromFS(PathTraitsFS::GetParent(c_str()));
|
||||
}
|
@@ -52,7 +52,10 @@ template<typename Traits>
|
||||
typename Traits::const_pointer
|
||||
GetBasePathImpl(typename Traits::const_pointer p)
|
||||
{
|
||||
#if !CLANG_CHECK_VERSION(3,6)
|
||||
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||
assert(p != nullptr);
|
||||
#endif
|
||||
|
||||
typename Traits::const_pointer sep = Traits::FindLastSeparator(p);
|
||||
return sep != nullptr
|
||||
@@ -64,7 +67,10 @@ template<typename Traits>
|
||||
typename Traits::string
|
||||
GetParentPathImpl(typename Traits::const_pointer p)
|
||||
{
|
||||
#if !CLANG_CHECK_VERSION(3,6)
|
||||
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||
assert(p != nullptr);
|
||||
#endif
|
||||
|
||||
typename Traits::const_pointer sep = Traits::FindLastSeparator(p);
|
||||
if (sep == nullptr)
|
||||
|
@@ -57,7 +57,11 @@ struct PathTraitsFS {
|
||||
|
||||
gcc_pure gcc_nonnull_all
|
||||
static const_pointer FindLastSeparator(const_pointer p) {
|
||||
#if !CLANG_CHECK_VERSION(3,6)
|
||||
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||
assert(p != nullptr);
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
const_pointer pos = p + GetLength(p);
|
||||
while (p != pos && !IsSeparator(*pos))
|
||||
@@ -77,7 +81,11 @@ struct PathTraitsFS {
|
||||
|
||||
gcc_pure gcc_nonnull_all
|
||||
static bool IsAbsolute(const_pointer p) {
|
||||
#if !CLANG_CHECK_VERSION(3,6)
|
||||
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||
assert(p != nullptr);
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
if (IsDrive(p) && IsSeparator(p[2]))
|
||||
return true;
|
||||
@@ -147,7 +155,11 @@ struct PathTraitsUTF8 {
|
||||
|
||||
gcc_pure gcc_nonnull_all
|
||||
static const_pointer FindLastSeparator(const_pointer p) {
|
||||
#if !CLANG_CHECK_VERSION(3,6)
|
||||
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||
assert(p != nullptr);
|
||||
#endif
|
||||
|
||||
return strrchr(p, SEPARATOR);
|
||||
}
|
||||
|
||||
@@ -160,7 +172,11 @@ struct PathTraitsUTF8 {
|
||||
|
||||
gcc_pure gcc_nonnull_all
|
||||
static bool IsAbsolute(const_pointer p) {
|
||||
#if !CLANG_CHECK_VERSION(3,6)
|
||||
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||
assert(p != nullptr);
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
if (IsDrive(p) && IsSeparator(p[2]))
|
||||
return true;
|
||||
|
@@ -62,6 +62,7 @@ FileOutputStream::Commit(gcc_unused Error &error)
|
||||
assert(IsDefined());
|
||||
|
||||
CloseHandle(handle);
|
||||
handle = INVALID_HANDLE_VALUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -71,6 +72,7 @@ FileOutputStream::Cancel()
|
||||
assert(IsDefined());
|
||||
|
||||
CloseHandle(handle);
|
||||
handle = INVALID_HANDLE_VALUE;
|
||||
RemoveFile(path);
|
||||
}
|
||||
|
||||
|
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "AsyncInputStream.hxx"
|
||||
#include "Domain.hxx"
|
||||
#include "tag/Tag.hxx"
|
||||
#include "event/Call.hxx"
|
||||
#include "thread/Cond.hxx"
|
||||
@@ -113,8 +114,10 @@ AsyncInputStream::Seek(offset_type new_offset, Error &error)
|
||||
/* no-op */
|
||||
return true;
|
||||
|
||||
if (!IsSeekable())
|
||||
if (!IsSeekable()) {
|
||||
error.Set(input_domain, "Not seekable");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* check if we can fast-forward the buffer */
|
||||
|
||||
@@ -157,6 +160,11 @@ AsyncInputStream::SeekDone()
|
||||
assert(io_thread_inside());
|
||||
assert(IsSeekPending());
|
||||
|
||||
/* we may have reached end-of-file previously, and the
|
||||
connection may have been closed already; however after
|
||||
seeking successfully, the connection must be alive again */
|
||||
open = true;
|
||||
|
||||
seek_state = SeekState::NONE;
|
||||
cond.broadcast();
|
||||
}
|
||||
|
@@ -83,6 +83,10 @@ protected:
|
||||
*/
|
||||
void SetTag(Tag *_tag);
|
||||
|
||||
void ClearTag() {
|
||||
SetTag(nullptr);
|
||||
}
|
||||
|
||||
void Pause();
|
||||
|
||||
bool IsPaused() const {
|
||||
|
24
src/input/Domain.cxx
Normal file
24
src/input/Domain.cxx
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "Domain.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
|
||||
const Domain input_domain("input");
|
@@ -17,9 +17,11 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef INPUT_DESPOTIFY_HXX
|
||||
#define INPUT_DESPOTIFY_HXX
|
||||
#ifndef MPD_INPUT_DOMAIN_HXX
|
||||
#define MPD_INPUT_DOMAIN_HXX
|
||||
|
||||
extern const struct InputPlugin input_plugin_despotify;
|
||||
class Domain;
|
||||
|
||||
extern const Domain input_domain;
|
||||
|
||||
#endif
|
@@ -67,7 +67,7 @@ input_stream_global_init(Error &error)
|
||||
case InputPlugin::InitResult::UNAVAILABLE:
|
||||
if (error.IsDefined()) {
|
||||
FormatError(error,
|
||||
"Input plugin '%s' is unavailable: ",
|
||||
"Input plugin '%s' is unavailable",
|
||||
plugin->name);
|
||||
error.Clear();
|
||||
}
|
||||
|
@@ -122,7 +122,10 @@ InputStream::IsAvailable()
|
||||
size_t
|
||||
InputStream::LockRead(void *ptr, size_t _size, Error &error)
|
||||
{
|
||||
#if !CLANG_CHECK_VERSION(3,6)
|
||||
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||
assert(ptr != nullptr);
|
||||
#endif
|
||||
assert(_size > 0);
|
||||
|
||||
const ScopeLock protect(mutex);
|
||||
|
@@ -200,6 +200,10 @@ public:
|
||||
return mime.empty() ? nullptr : mime.c_str();
|
||||
}
|
||||
|
||||
void ClearMimeType() {
|
||||
mime.clear();
|
||||
}
|
||||
|
||||
gcc_nonnull_all
|
||||
void SetMimeType(const char *_mime) {
|
||||
assert(!ready);
|
||||
|
@@ -22,14 +22,13 @@
|
||||
#include "Registry.hxx"
|
||||
#include "InputPlugin.hxx"
|
||||
#include "LocalOpen.hxx"
|
||||
#include "Domain.hxx"
|
||||
#include "plugins/RewindInputPlugin.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "fs/Path.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
|
||||
static constexpr Domain input_domain("input");
|
||||
|
||||
InputStream *
|
||||
InputStream::Open(const char *url,
|
||||
Mutex &mutex, Cond &cond,
|
||||
|
@@ -54,10 +54,6 @@
|
||||
#include "plugins/CdioParanoiaInputPlugin.hxx"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_DESPOTIFY
|
||||
#include "plugins/DespotifyInputPlugin.hxx"
|
||||
#endif
|
||||
|
||||
const InputPlugin *const input_plugins[] = {
|
||||
&input_plugin_file,
|
||||
#ifdef HAVE_ALSA
|
||||
@@ -83,9 +79,6 @@ const InputPlugin *const input_plugins[] = {
|
||||
#endif
|
||||
#ifdef ENABLE_CDIO_PARANOIA
|
||||
&input_plugin_cdio_paranoia,
|
||||
#endif
|
||||
#ifdef ENABLE_DESPOTIFY
|
||||
&input_plugin_despotify,
|
||||
#endif
|
||||
nullptr
|
||||
};
|
||||
|
@@ -38,8 +38,8 @@ TextInputStream::ReadLine()
|
||||
while (true) {
|
||||
auto dest = buffer.Write();
|
||||
if (dest.size < 2) {
|
||||
/* end of file (or line too long): terminate
|
||||
the current line */
|
||||
/* line too long: terminate the current
|
||||
line */
|
||||
|
||||
assert(!dest.IsEmpty());
|
||||
dest[0] = 0;
|
||||
@@ -66,7 +66,19 @@ TextInputStream::ReadLine()
|
||||
if (line != nullptr)
|
||||
return line;
|
||||
|
||||
if (nbytes == 0)
|
||||
return nullptr;
|
||||
if (nbytes == 0) {
|
||||
/* end of file: see if there's an unterminated
|
||||
line */
|
||||
|
||||
dest = buffer.Write();
|
||||
assert(!dest.IsEmpty());
|
||||
dest[0] = 0;
|
||||
|
||||
auto r = buffer.Read();
|
||||
buffer.Clear();
|
||||
return r.IsEmpty()
|
||||
? nullptr
|
||||
: r.data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user