Compare commits
307 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 | ||
![]() |
c64ad78c7b | ||
![]() |
4a043a915f |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -77,6 +77,8 @@ tags
|
|||||||
/test/test_vorbis_encoder
|
/test/test_vorbis_encoder
|
||||||
/test/DumpDatabase
|
/test/DumpDatabase
|
||||||
|
|
||||||
|
/lib/
|
||||||
|
|
||||||
/*.tar.gz
|
/*.tar.gz
|
||||||
/*.tar.bz2
|
/*.tar.bz2
|
||||||
/*.tar.xz
|
/*.tar.xz
|
||||||
|
8
INSTALL
8
INSTALL
@@ -12,7 +12,7 @@ install MPD. If more information is desired, read the user manual:
|
|||||||
Dependencies
|
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/
|
clang 3.2 or later - http://clang.llvm.org/
|
||||||
Any other C++11 compliant compiler should also work.
|
Any other C++11 compliant compiler should also work.
|
||||||
|
|
||||||
@@ -116,12 +116,6 @@ For WavPack playback.
|
|||||||
libadplug - http://adplug.sourceforge.net/
|
libadplug - http://adplug.sourceforge.net/
|
||||||
For AdLib playback.
|
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
|
Optional Miscellaneous Dependencies
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
67
Makefile.am
67
Makefile.am
@@ -28,8 +28,6 @@ noinst_LIBRARIES = \
|
|||||||
libmixer_plugins.a \
|
libmixer_plugins.a \
|
||||||
liboutput_plugins.a
|
liboutput_plugins.a
|
||||||
|
|
||||||
libmpd_a_DEPENDENCIES =
|
|
||||||
|
|
||||||
libmpd_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
libmpd_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||||
$(LIBMPDCLIENT_CFLAGS) \
|
$(LIBMPDCLIENT_CFLAGS) \
|
||||||
$(AVAHI_CFLAGS) \
|
$(AVAHI_CFLAGS) \
|
||||||
@@ -283,13 +281,13 @@ android/build/build.xml: android/AndroidManifest.xml
|
|||||||
ln -s $(abs_srcdir)/android/res/values android/build/res
|
ln -s $(abs_srcdir)/android/res/values android/build/res
|
||||||
$(ANDROID_SDK)/tools/android update project --path android/build --target android-17
|
$(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
|
cd android/build && ant compile-jni-classes
|
||||||
|
|
||||||
android/build/include/org_musicpd_Bridge.h: android/build/bin/classes/org/musicpd/Bridge.class
|
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
|
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
|
android/build/libs/armeabi-v7a/libmpd.so: libmpd.so android/build/build.xml
|
||||||
mkdir -p $(@D)
|
mkdir -p $(@D)
|
||||||
@@ -361,6 +359,7 @@ libutil_a_SOURCES = \
|
|||||||
src/util/Clamp.hxx \
|
src/util/Clamp.hxx \
|
||||||
src/util/Alloc.cxx src/util/Alloc.hxx \
|
src/util/Alloc.cxx src/util/Alloc.hxx \
|
||||||
src/util/VarSize.hxx \
|
src/util/VarSize.hxx \
|
||||||
|
src/util/ScopeExit.hxx \
|
||||||
src/util/Error.cxx src/util/Error.hxx \
|
src/util/Error.cxx src/util/Error.hxx \
|
||||||
src/util/Domain.hxx \
|
src/util/Domain.hxx \
|
||||||
src/util/ReusableArray.hxx \
|
src/util/ReusableArray.hxx \
|
||||||
@@ -464,11 +463,13 @@ ICU_LDADD = libicu.a $(ICU_LIBS)
|
|||||||
libpcm_a_SOURCES = \
|
libpcm_a_SOURCES = \
|
||||||
src/pcm/Domain.cxx src/pcm/Domain.hxx \
|
src/pcm/Domain.cxx src/pcm/Domain.hxx \
|
||||||
src/pcm/Traits.hxx \
|
src/pcm/Traits.hxx \
|
||||||
|
src/pcm/Interleave.cxx src/pcm/Interleave.hxx \
|
||||||
src/pcm/PcmBuffer.cxx src/pcm/PcmBuffer.hxx \
|
src/pcm/PcmBuffer.cxx src/pcm/PcmBuffer.hxx \
|
||||||
src/pcm/PcmExport.cxx src/pcm/PcmExport.hxx \
|
src/pcm/PcmExport.cxx src/pcm/PcmExport.hxx \
|
||||||
src/pcm/PcmConvert.cxx src/pcm/PcmConvert.hxx \
|
src/pcm/PcmConvert.cxx src/pcm/PcmConvert.hxx \
|
||||||
src/pcm/PcmDop.cxx src/pcm/PcmDop.hxx \
|
src/pcm/PcmDop.cxx src/pcm/PcmDop.hxx \
|
||||||
src/pcm/Volume.cxx src/pcm/Volume.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/PcmMix.cxx src/pcm/PcmMix.hxx \
|
||||||
src/pcm/PcmChannels.cxx src/pcm/PcmChannels.hxx \
|
src/pcm/PcmChannels.cxx src/pcm/PcmChannels.hxx \
|
||||||
src/pcm/PcmPack.cxx src/pcm/PcmPack.hxx \
|
src/pcm/PcmPack.cxx src/pcm/PcmPack.hxx \
|
||||||
@@ -529,7 +530,7 @@ libfs_a_SOURCES = \
|
|||||||
src/fs/Traits.cxx src/fs/Traits.hxx \
|
src/fs/Traits.cxx src/fs/Traits.hxx \
|
||||||
src/fs/Config.cxx src/fs/Config.hxx \
|
src/fs/Config.cxx src/fs/Config.hxx \
|
||||||
src/fs/Charset.cxx src/fs/Charset.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/AllocatedPath.cxx src/fs/AllocatedPath.hxx \
|
||||||
src/fs/FileSystem.cxx src/fs/FileSystem.hxx \
|
src/fs/FileSystem.cxx src/fs/FileSystem.hxx \
|
||||||
src/fs/StandardDirectory.cxx src/fs/StandardDirectory.hxx \
|
src/fs/StandardDirectory.cxx src/fs/StandardDirectory.hxx \
|
||||||
@@ -800,6 +801,11 @@ endif
|
|||||||
if HAVE_FFMPEG
|
if HAVE_FFMPEG
|
||||||
noinst_LIBRARIES += libffmpeg.a
|
noinst_LIBRARIES += libffmpeg.a
|
||||||
libffmpeg_a_SOURCES = \
|
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/Error.cxx src/lib/ffmpeg/Error.hxx \
|
||||||
src/lib/ffmpeg/Domain.cxx src/lib/ffmpeg/Domain.hxx
|
src/lib/ffmpeg/Domain.cxx src/lib/ffmpeg/Domain.hxx
|
||||||
libffmpeg_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
libffmpeg_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||||
@@ -828,7 +834,6 @@ libdecoder_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
|||||||
$(WAVPACK_CFLAGS) \
|
$(WAVPACK_CFLAGS) \
|
||||||
$(MAD_CFLAGS) \
|
$(MAD_CFLAGS) \
|
||||||
$(MPG123_CFLAGS) \
|
$(MPG123_CFLAGS) \
|
||||||
$(MP4V2_CFLAGS) \
|
|
||||||
$(OPUS_CFLAGS) \
|
$(OPUS_CFLAGS) \
|
||||||
$(FFMPEG_CFLAGS) \
|
$(FFMPEG_CFLAGS) \
|
||||||
$(MPCDEC_CFLAGS) \
|
$(MPCDEC_CFLAGS) \
|
||||||
@@ -848,7 +853,6 @@ DECODER_LIBS = \
|
|||||||
$(WAVPACK_LIBS) \
|
$(WAVPACK_LIBS) \
|
||||||
$(MAD_LIBS) \
|
$(MAD_LIBS) \
|
||||||
$(MPG123_LIBS) \
|
$(MPG123_LIBS) \
|
||||||
$(MP4V2_LIBS) \
|
|
||||||
$(OPUS_LIBS) \
|
$(OPUS_LIBS) \
|
||||||
$(FFMPEG_LIBS2) \
|
$(FFMPEG_LIBS2) \
|
||||||
$(MPCDEC_LIBS) \
|
$(MPCDEC_LIBS) \
|
||||||
@@ -963,12 +967,6 @@ noinst_LIBRARIES += libmodplug_decoder_plugin.a
|
|||||||
DECODER_LIBS += libmodplug_decoder_plugin.a $(MODPLUG_LIBS)
|
DECODER_LIBS += libmodplug_decoder_plugin.a $(MODPLUG_LIBS)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if HAVE_MP4V2
|
|
||||||
libdecoder_a_SOURCES += \
|
|
||||||
src/decoder/plugins/Mp4v2DecoderPlugin.cxx \
|
|
||||||
src/decoder/plugins/Mp4v2DecoderPlugin.hxx
|
|
||||||
endif
|
|
||||||
|
|
||||||
if ENABLE_SIDPLAY
|
if ENABLE_SIDPLAY
|
||||||
libdecoder_a_SOURCES += \
|
libdecoder_a_SOURCES += \
|
||||||
src/decoder/plugins/SidplayDecoderPlugin.cxx \
|
src/decoder/plugins/SidplayDecoderPlugin.cxx \
|
||||||
@@ -989,6 +987,8 @@ endif
|
|||||||
|
|
||||||
if HAVE_FFMPEG
|
if HAVE_FFMPEG
|
||||||
libdecoder_a_SOURCES += \
|
libdecoder_a_SOURCES += \
|
||||||
|
src/decoder/plugins/FfmpegIo.cxx \
|
||||||
|
src/decoder/plugins/FfmpegIo.hxx \
|
||||||
src/decoder/plugins/FfmpegMetaData.cxx \
|
src/decoder/plugins/FfmpegMetaData.cxx \
|
||||||
src/decoder/plugins/FfmpegMetaData.hxx \
|
src/decoder/plugins/FfmpegMetaData.hxx \
|
||||||
src/decoder/plugins/FfmpegDecoderPlugin.cxx \
|
src/decoder/plugins/FfmpegDecoderPlugin.cxx \
|
||||||
@@ -1133,7 +1133,6 @@ libinput_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
|||||||
$(NFS_CFLAGS) \
|
$(NFS_CFLAGS) \
|
||||||
$(CDIO_PARANOIA_CFLAGS) \
|
$(CDIO_PARANOIA_CFLAGS) \
|
||||||
$(FFMPEG_CFLAGS) \
|
$(FFMPEG_CFLAGS) \
|
||||||
$(DESPOTIFY_CFLAGS) \
|
|
||||||
$(MMS_CFLAGS)
|
$(MMS_CFLAGS)
|
||||||
|
|
||||||
INPUT_LIBS = \
|
INPUT_LIBS = \
|
||||||
@@ -1143,7 +1142,6 @@ INPUT_LIBS = \
|
|||||||
$(NFS_LIBS) \
|
$(NFS_LIBS) \
|
||||||
$(CDIO_PARANOIA_LIBS) \
|
$(CDIO_PARANOIA_LIBS) \
|
||||||
$(FFMPEG_LIBS2) \
|
$(FFMPEG_LIBS2) \
|
||||||
$(DESPOTIFY_LIBS) \
|
|
||||||
$(MMS_LIBS)
|
$(MMS_LIBS)
|
||||||
|
|
||||||
if HAVE_ALSA
|
if HAVE_ALSA
|
||||||
@@ -1189,15 +1187,6 @@ libinput_a_SOURCES += \
|
|||||||
src/input/plugins/MmsInputPlugin.cxx src/input/plugins/MmsInputPlugin.hxx
|
src/input/plugins/MmsInputPlugin.cxx src/input/plugins/MmsInputPlugin.hxx
|
||||||
endif
|
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) \
|
liboutput_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||||
$(AO_CFLAGS) \
|
$(AO_CFLAGS) \
|
||||||
$(ALSA_CFLAGS) \
|
$(ALSA_CFLAGS) \
|
||||||
@@ -1240,6 +1229,7 @@ liboutput_plugins_a_SOURCES = \
|
|||||||
|
|
||||||
MIXER_LIBS = \
|
MIXER_LIBS = \
|
||||||
libmixer_plugins.a \
|
libmixer_plugins.a \
|
||||||
|
$(ALSA_LIBS) \
|
||||||
$(PULSE_LIBS)
|
$(PULSE_LIBS)
|
||||||
|
|
||||||
MIXER_API_SRC = \
|
MIXER_API_SRC = \
|
||||||
@@ -1402,14 +1392,6 @@ PLAYLIST_LIBS = \
|
|||||||
$(EXPAT_LIBS) \
|
$(EXPAT_LIBS) \
|
||||||
$(FLAC_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
|
if ENABLE_SOUNDCLOUD
|
||||||
libplaylist_plugins_a_SOURCES += \
|
libplaylist_plugins_a_SOURCES += \
|
||||||
src/playlist/plugins/SoundCloudPlaylistPlugin.cxx \
|
src/playlist/plugins/SoundCloudPlaylistPlugin.cxx \
|
||||||
@@ -1646,12 +1628,6 @@ if HAVE_LIBUPNP
|
|||||||
test_run_neighbor_explorer_SOURCES += src/lib/expat/ExpatParser.cxx
|
test_run_neighbor_explorer_SOURCES += src/lib/expat/ExpatParser.cxx
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if ENABLE_DESPOTIFY
|
|
||||||
test_run_neighbor_explorer_SOURCES += \
|
|
||||||
src/lib/despotify/DespotifyUtils.cxx \
|
|
||||||
src/lib/despotify/DespotifyUtils.hxx
|
|
||||||
endif
|
|
||||||
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if ENABLE_ARCHIVE
|
if ENABLE_ARCHIVE
|
||||||
@@ -1900,6 +1876,7 @@ test_run_convert_SOURCES = test/run_convert.cxx \
|
|||||||
src/AudioParser.cxx
|
src/AudioParser.cxx
|
||||||
test_run_convert_LDADD = \
|
test_run_convert_LDADD = \
|
||||||
$(PCM_LIBS) \
|
$(PCM_LIBS) \
|
||||||
|
libconf.a \
|
||||||
libutil.a \
|
libutil.a \
|
||||||
$(GLIB_LIBS)
|
$(GLIB_LIBS)
|
||||||
|
|
||||||
@@ -2143,7 +2120,9 @@ developer_DATA = $(wildcard doc/developer/*.html)
|
|||||||
|
|
||||||
DOCBOOK_HTML = $(patsubst %.xml,%/index.html,$(DOCBOOK_FILES))
|
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 $<
|
$(XMLTO) -o $(@D) --stringparam chunker.output.encoding=utf-8 html --stringparam use.id.as.filename=1 $<
|
||||||
|
|
||||||
doc/api/html/index.html: doc/doxygen.conf
|
doc/api/html/index.html: doc/doxygen.conf
|
||||||
@@ -2184,7 +2163,15 @@ EXTRA_DIST = $(doc_DATA) autogen.sh \
|
|||||||
test/test_archive_bzip2.sh \
|
test/test_archive_bzip2.sh \
|
||||||
test/test_archive_iso9660.sh \
|
test/test_archive_iso9660.sh \
|
||||||
test/test_archive_zzip.sh \
|
test/test_archive_zzip.sh \
|
||||||
$(wildcard scripts/*.sh) \
|
$(wildcard $(srcdir)/scripts/*.rb) \
|
||||||
$(man_MANS) $(DOCBOOK_FILES) doc/mpdconf.example doc/doxygen.conf \
|
$(man_MANS) $(DOCBOOK_FILES) doc/mpdconf.example doc/doxygen.conf \
|
||||||
|
$(wildcard $(srcdir)/doc/include/*.xml) \
|
||||||
systemd/mpd.socket \
|
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
|
src/win32/mpd_win32_rc.rc.in src/win32/mpd.ico
|
||||||
|
179
NEWS
179
NEWS
@@ -1,3 +1,153 @@
|
|||||||
|
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)
|
ver 0.19.3 (2014/11/11)
|
||||||
* protocol
|
* protocol
|
||||||
- fix "(null)" result string to "list" when AlbumArtist is disabled
|
- fix "(null)" result string to "list" when AlbumArtist is disabled
|
||||||
@@ -120,6 +270,35 @@ ver 0.19 (2014/10/10)
|
|||||||
* install systemd unit for socket activation
|
* install systemd unit for socket activation
|
||||||
* Android port
|
* 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)
|
ver 0.18.17 (2014/11/02)
|
||||||
* playlist
|
* playlist
|
||||||
- don't allow empty playlist name
|
- don't allow empty playlist name
|
||||||
|
@@ -2,8 +2,8 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.musicpd"
|
package="org.musicpd"
|
||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:versionCode="7"
|
android:versionCode="13"
|
||||||
android:versionName="0.19.1">
|
android:versionName="0.19.9">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="17"/>
|
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="17"/>
|
||||||
|
|
||||||
|
@@ -23,7 +23,7 @@ if not os.path.isdir(ndk_path):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# the path to the MPD sources
|
# 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
|
# output directories
|
||||||
lib_path = os.path.abspath('lib')
|
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')
|
os.environ['PKG_CONFIG_LIBDIR'] = os.path.join(root_path, 'lib/pkgconfig')
|
||||||
|
|
||||||
# select the NDK compiler
|
# select the NDK compiler
|
||||||
gcc_version = '4.8'
|
gcc_version = '4.9'
|
||||||
llvm_version = '3.3'
|
llvm_version = '3.5'
|
||||||
|
|
||||||
# select the NDK target
|
# select the NDK target
|
||||||
ndk_arch = 'arm'
|
ndk_arch = 'arm'
|
||||||
@@ -314,8 +314,8 @@ thirdparty_libs = [
|
|||||||
),
|
),
|
||||||
|
|
||||||
AutotoolsProject(
|
AutotoolsProject(
|
||||||
'https://svn.xiph.org/releases/flac/flac-1.3.0.tar.xz',
|
'http://downloads.xiph.org/releases/flac/flac-1.3.1.tar.xz',
|
||||||
'13b5c214cee8373464d3d65dee362cdd',
|
'b9922c9a0378c88d3e901b234f852698',
|
||||||
'lib/libFLAC.a',
|
'lib/libFLAC.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
@@ -341,8 +341,8 @@ thirdparty_libs = [
|
|||||||
),
|
),
|
||||||
|
|
||||||
FfmpegProject(
|
FfmpegProject(
|
||||||
'http://www.ffmpeg.org/releases/ffmpeg-2.2.3.tar.bz2',
|
'http://ffmpeg.org/releases/ffmpeg-2.5.tar.bz2',
|
||||||
'dbb5b6b69bd010916f17df0ae596e0b1',
|
'4346fe710cc6bdd981f6534d2420d1ab',
|
||||||
'lib/libavcodec.a',
|
'lib/libavcodec.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
@@ -366,8 +366,8 @@ thirdparty_libs = [
|
|||||||
),
|
),
|
||||||
|
|
||||||
AutotoolsProject(
|
AutotoolsProject(
|
||||||
'http://curl.haxx.se/download/curl-7.37.0.tar.lzma',
|
'http://curl.haxx.se/download/curl-7.39.0.tar.lzma',
|
||||||
'54bfd1eb5214f604186d6f5ac61c7781',
|
'e9aa6dec29920eba8ef706ea5823bad7',
|
||||||
'lib/libcurl.a',
|
'lib/libcurl.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
|
81
configure.ac
81
configure.ac
@@ -1,10 +1,10 @@
|
|||||||
AC_PREREQ(2.60)
|
AC_PREREQ(2.60)
|
||||||
|
|
||||||
AC_INIT(mpd, 0.19.3, musicpd-dev-team@lists.sourceforge.net)
|
AC_INIT(mpd, 0.19.18, musicpd-dev-team@lists.sourceforge.net)
|
||||||
|
|
||||||
VERSION_MAJOR=0
|
VERSION_MAJOR=0
|
||||||
VERSION_MINOR=19
|
VERSION_MINOR=19
|
||||||
VERSION_REVISION=3
|
VERSION_REVISION=18
|
||||||
VERSION_EXTRA=0
|
VERSION_EXTRA=0
|
||||||
|
|
||||||
AC_CONFIG_SRCDIR([src/Main.cxx])
|
AC_CONFIG_SRCDIR([src/Main.cxx])
|
||||||
@@ -206,6 +206,8 @@ if test x$host_is_linux = xyes; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
AC_CHECK_FUNCS(getpwnam_r getpwuid_r)
|
AC_CHECK_FUNCS(getpwnam_r getpwuid_r)
|
||||||
|
AC_CHECK_FUNCS(initgroups)
|
||||||
|
AC_CHECK_FUNCS(strndup)
|
||||||
|
|
||||||
if test x$host_is_linux = xyes; then
|
if test x$host_is_linux = xyes; then
|
||||||
MPD_OPTIONAL_FUNC(eventfd, eventfd, USE_EVENTFD)
|
MPD_OPTIONAL_FUNC(eventfd, eventfd, USE_EVENTFD)
|
||||||
@@ -445,11 +447,6 @@ MPD_DEPENDS([enable_jack], [enable_glib],
|
|||||||
|
|
||||||
AC_SYS_LARGEFILE
|
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,
|
AC_ARG_ENABLE(soundcloud,
|
||||||
AS_HELP_STRING([--enable-soundcloud],
|
AS_HELP_STRING([--enable-soundcloud],
|
||||||
[enable support for soundcloud.com]),,
|
[enable support for soundcloud.com]),,
|
||||||
@@ -496,11 +493,6 @@ AC_ARG_ENABLE(modplug,
|
|||||||
[enable modplug decoder plugin]),,
|
[enable modplug decoder plugin]),,
|
||||||
enable_modplug=auto)
|
enable_modplug=auto)
|
||||||
|
|
||||||
AC_ARG_ENABLE(mp4v2,
|
|
||||||
AS_HELP_STRING([--enable-mp4v2],
|
|
||||||
[enable libmp4v2 decoder plugin]),,
|
|
||||||
enable_mp4v2=auto)
|
|
||||||
|
|
||||||
AC_ARG_ENABLE(mpc,
|
AC_ARG_ENABLE(mpc,
|
||||||
AS_HELP_STRING([--enable-mpc],
|
AS_HELP_STRING([--enable-mpc],
|
||||||
[disable musepack (MPC) support (default: auto)]),,
|
[disable musepack (MPC) support (default: auto)]),,
|
||||||
@@ -550,8 +542,6 @@ AC_ARG_ENABLE(sidplay,
|
|||||||
AS_HELP_STRING([--enable-sidplay],
|
AS_HELP_STRING([--enable-sidplay],
|
||||||
[enable C64 SID support via libsidplay2]),,
|
[enable C64 SID support via libsidplay2]),,
|
||||||
enable_sidplay=auto)
|
enable_sidplay=auto)
|
||||||
MPD_DEPENDS([enable_sidplay], [enable_glib],
|
|
||||||
[Cannot use --enable-sidplay with --disable-glib])
|
|
||||||
|
|
||||||
AC_ARG_ENABLE(shine-encoder,
|
AC_ARG_ENABLE(shine-encoder,
|
||||||
AS_HELP_STRING([--enable-shine-encoder],
|
AS_HELP_STRING([--enable-shine-encoder],
|
||||||
@@ -711,12 +701,6 @@ AC_ARG_ENABLE(glib,
|
|||||||
if test x$enable_glib = xyes; then
|
if test x$enable_glib = xyes; then
|
||||||
PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.28 gthread-2.0],,
|
PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.28 gthread-2.0],,
|
||||||
[AC_MSG_ERROR([GLib 2.28 is required])])
|
[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])
|
AC_DEFINE(HAVE_GLIB, 1, [Define if GLib is used])
|
||||||
fi
|
fi
|
||||||
AM_CONDITIONAL(HAVE_GLIB, test x$enable_glib = xyes)
|
AM_CONDITIONAL(HAVE_GLIB, test x$enable_glib = xyes)
|
||||||
@@ -984,14 +968,6 @@ if test x$enable_nfs = xyes; then
|
|||||||
fi
|
fi
|
||||||
AM_CONDITIONAL(ENABLE_NFS, test x$enable_nfs = xyes)
|
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 ------------------------------
|
dnl --------------------------------- Soundcloud ------------------------------
|
||||||
if test x$enable_soundcloud != xno; then
|
if test x$enable_soundcloud != xno; then
|
||||||
PKG_CHECK_MODULES([YAJL], [yajl >= 2.0],
|
PKG_CHECK_MODULES([YAJL], [yajl >= 2.0],
|
||||||
@@ -1264,24 +1240,6 @@ if test x$enable_modplug = xyes; then
|
|||||||
fi
|
fi
|
||||||
AM_CONDITIONAL(HAVE_MODPLUG, test x$enable_modplug = xyes)
|
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 ----------------------------------
|
dnl -------------------------------- libopus ----------------------------------
|
||||||
MPD_AUTO_PKG(opus, OPUS, [opus ogg],
|
MPD_AUTO_PKG(opus, OPUS, [opus ogg],
|
||||||
[opus decoder plugin], [libopus not found])
|
[opus decoder plugin], [libopus not found])
|
||||||
@@ -1375,31 +1333,36 @@ AM_CONDITIONAL(ENABLE_VORBIS_DECODER, test x$enable_vorbis = xyes || test x$enab
|
|||||||
|
|
||||||
dnl --------------------------------- sidplay ---------------------------------
|
dnl --------------------------------- sidplay ---------------------------------
|
||||||
if test x$enable_sidplay != xno; then
|
if test x$enable_sidplay != xno; then
|
||||||
# we're not using pkg-config here
|
dnl Check for libsidplayfp first
|
||||||
# because libsidplay2's .pc file requires libtool
|
PKG_CHECK_MODULES([SIDPLAY], [libsidplayfp libsidutils],
|
||||||
AC_CHECK_LIB([sidplay2],[main],[found_sidplay=yes],[found_sidplay=no],[])
|
[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],
|
MPD_AUTO_PRE(sidplay, [sidplay decoder plugin],
|
||||||
[libsidplay2 not found])
|
[libsidplay2 not found])
|
||||||
fi
|
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],
|
AC_CHECK_LIB([resid-builder], [main],
|
||||||
[found_sidplay=yes], [found_sidplay=no])
|
[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],
|
MPD_AUTO_RESULT(sidplay, [sidplay decoder plugin],
|
||||||
[libresid-builder or libsidutils not found])
|
[libresid-builder not found])
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if test x$enable_sidplay = xyes; then
|
if test x$enable_sidplay = xyes; then
|
||||||
AC_SUBST(SIDPLAY_LIBS,"-lsidplay2 -lresid-builder -lsidutils")
|
SIDPLAY_LIBS="$SIDPLAY_LIBS -lresid-builder"
|
||||||
AC_SUBST(SIDPLAY_CFLAGS,)
|
|
||||||
|
|
||||||
AC_DEFINE(ENABLE_SIDPLAY, 1, [Define for libsidplay2 support])
|
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
|
fi
|
||||||
|
|
||||||
AM_CONDITIONAL(ENABLE_SIDPLAY, test x$enable_sidplay = xyes)
|
AM_CONDITIONAL(ENABLE_SIDPLAY, test x$enable_sidplay = xyes)
|
||||||
@@ -1868,7 +1831,6 @@ printf '\n\t'
|
|||||||
results(sndfile, [libsndfile])
|
results(sndfile, [libsndfile])
|
||||||
results(mikmod, [MikMod])
|
results(mikmod, [MikMod])
|
||||||
results(modplug, [MODPLUG])
|
results(modplug, [MODPLUG])
|
||||||
results(mp4v2, [MP4V2])
|
|
||||||
results(mad, [MAD])
|
results(mad, [MAD])
|
||||||
results(mpg123, [MPG123])
|
results(mpg123, [MPG123])
|
||||||
results(mpc, [Musepack])
|
results(mpc, [Musepack])
|
||||||
@@ -1927,7 +1889,6 @@ printf '\nStreaming support:\n\t'
|
|||||||
results(cdio_paranoia, [CDIO_PARANOIA])
|
results(cdio_paranoia, [CDIO_PARANOIA])
|
||||||
results(curl,[CURL])
|
results(curl,[CURL])
|
||||||
results(smbclient,[SMBCLIENT])
|
results(smbclient,[SMBCLIENT])
|
||||||
results(despotify,[Despotify])
|
|
||||||
results(soundcloud,[Soundcloud])
|
results(soundcloud,[Soundcloud])
|
||||||
printf '\n\t'
|
printf '\n\t'
|
||||||
results(mms,[MMS])
|
results(mms,[MMS])
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
<?xml version='1.0' encoding="utf-8"?>
|
<?xml version='1.0' encoding="utf-8"?>
|
||||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
|
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||||
"docbook/dtd/xml/4.2/docbookx.dtd">
|
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||||
|
|
||||||
<book>
|
<book>
|
||||||
<title>The Music Player Daemon - Developer's Manual</title>
|
<title>The Music Player Daemon - Developer's Manual</title>
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@
|
|||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
the code should be C++11 compliant, and must compile with
|
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
|
<application>clang</application> 3.2
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</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
|
This specifies whether relative or absolute paths for song filenames are used
|
||||||
when saving playlists. The default is "no".
|
when saving playlists. The default is "no".
|
||||||
.TP
|
.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>
|
.B auto_update <yes or no>
|
||||||
This specifies the whether to support automatic update of music database when
|
This specifies the whether to support automatic update of music database when
|
||||||
files are changed in music_directory. The default is to disable autoupdate
|
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
|
Limit the depth of the directories being watched, 0 means only watch
|
||||||
the music directory itself. There is no limit by default.
|
the music directory itself. There is no limit by default.
|
||||||
.TP
|
.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
|
.SH REQUIRED AUDIO OUTPUT PARAMETERS
|
||||||
.TP
|
.TP
|
||||||
.B type <type>
|
.B type <type>
|
||||||
|
@@ -115,7 +115,7 @@
|
|||||||
#
|
#
|
||||||
# This setting defines a list of tag types that will be extracted during the
|
# 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
|
# 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"
|
#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
|
# This setting enables automatic update of MPD's database when files in
|
||||||
@@ -231,7 +231,7 @@ input {
|
|||||||
#
|
#
|
||||||
#audio_output {
|
#audio_output {
|
||||||
# type "shout"
|
# type "shout"
|
||||||
# encoding "ogg" # optional
|
# encoder "vorbis" # optional
|
||||||
# name "My Shout Stream"
|
# name "My Shout Stream"
|
||||||
# host "localhost"
|
# host "localhost"
|
||||||
# port "8000"
|
# port "8000"
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
<?xml version='1.0' encoding="utf-8"?>
|
<?xml version='1.0' encoding="utf-8"?>
|
||||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
|
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||||
"docbook/dtd/xml/4.2/docbookx.dtd">
|
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||||
|
|
||||||
<book>
|
<book>
|
||||||
<title>The Music Player Daemon protocol</title>
|
<title>The Music Player Daemon protocol</title>
|
||||||
|
|
||||||
@@ -201,6 +202,25 @@
|
|||||||
omitted, then the maximum possible value is assumed.
|
omitted, then the maximum possible value is assumed.
|
||||||
</para>
|
</para>
|
||||||
</section>
|
</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>
|
||||||
|
|
||||||
<chapter id="recipes">
|
<chapter id="recipes">
|
||||||
@@ -1141,7 +1161,7 @@ OK
|
|||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Searches case-sensitively for partial matches in the
|
Searches case-insensitively for partial matches in the
|
||||||
current playlist.
|
current playlist.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
@@ -1533,6 +1553,7 @@ OK
|
|||||||
<command>count</command>
|
<command>count</command>
|
||||||
<arg choice="req"><replaceable>TAG</replaceable></arg>
|
<arg choice="req"><replaceable>TAG</replaceable></arg>
|
||||||
<arg choice="req"><replaceable>NEEDLE</replaceable></arg>
|
<arg choice="req"><replaceable>NEEDLE</replaceable></arg>
|
||||||
|
<arg choice="opt"><replaceable>...</replaceable></arg>
|
||||||
<arg choice="opt">group</arg>
|
<arg choice="opt">group</arg>
|
||||||
<arg choice="opt"><replaceable>GROUPTYPE</replaceable></arg>
|
<arg choice="opt"><replaceable>GROUPTYPE</replaceable></arg>
|
||||||
</cmdsynopsis>
|
</cmdsynopsis>
|
||||||
|
241
doc/user.xml
241
doc/user.xml
@@ -1,6 +1,7 @@
|
|||||||
<?xml version='1.0' encoding="utf-8"?>
|
<?xml version='1.0' encoding="utf-8"?>
|
||||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
|
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||||
"docbook/dtd/xml/4.2/docbookx.dtd">
|
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||||
|
|
||||||
<book>
|
<book>
|
||||||
<title>The Music Player Daemon - User's Manual</title>
|
<title>The Music Player Daemon - User's Manual</title>
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
<application>MPD</application> (Music Player Daemon) is, as the
|
<application>MPD</application> (Music Player Daemon) is, as the
|
||||||
name suggests, a server software allowing you to remotely play
|
name suggests, a server software allowing you to remotely play
|
||||||
your music, handle playlists, deliver music (HTTP streams with
|
your music, handle playlists, deliver music (HTTP streams with
|
||||||
various sub-protocols) and organizze playlists.
|
various sub-protocols) and organize playlists.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@@ -89,7 +90,7 @@ cd mpd-version</programlisting>
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<programlisting>
|
<programlisting>
|
||||||
apt-get install g++ automake autoconf \
|
apt-get install g++ \
|
||||||
libmad0-dev libmpg123-dev libid3tag0-dev \
|
libmad0-dev libmpg123-dev libid3tag0-dev \
|
||||||
libflac-dev libvorbis-dev libopus-dev \
|
libflac-dev libvorbis-dev libopus-dev \
|
||||||
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-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 \
|
libsidplay2-dev libsidutils-dev libresid-builder-dev \
|
||||||
libavcodec-dev libavformat-dev \
|
libavcodec-dev libavformat-dev \
|
||||||
libmp3lame-dev \
|
libmp3lame-dev \
|
||||||
libsamplerate0-dev \
|
libsamplerate0-dev libsoxr-dev \
|
||||||
libbz2-dev libcdio-paranoia-dev libiso9660-dev libmms-dev \
|
libbz2-dev libcdio-paranoia-dev libiso9660-dev libmms-dev \
|
||||||
libzzip-dev \
|
libzzip-dev \
|
||||||
libcurl4-gnutls-dev libyajl-dev \
|
libcurl4-gnutls-dev libyajl-dev libexpat-dev \
|
||||||
libasound2-dev libao-dev libjack-jackd2-dev libopenal-dev \
|
libasound2-dev libao-dev libjack-jackd2-dev libopenal-dev \
|
||||||
libpulse-dev libroar-dev libshout3-dev \
|
libpulse-dev libroar-dev libshout3-dev \
|
||||||
libmpdclient-dev \
|
libmpdclient-dev \
|
||||||
|
libnfs-dev libsmbclient-dev \
|
||||||
|
libupnp-dev \
|
||||||
libavahi-client-dev \
|
libavahi-client-dev \
|
||||||
libsqlite3-dev \
|
libsqlite3-dev \
|
||||||
libsystemd-daemon-dev libwrap0-dev \
|
libsystemd-daemon-dev libwrap0-dev \
|
||||||
libcppunit-dev xmlto \
|
libcppunit-dev xmlto \
|
||||||
libboost-dev \
|
libboost-dev \
|
||||||
libglib2.0-dev
|
libglib2.0-dev libicu-dev
|
||||||
</programlisting>
|
</programlisting>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@@ -313,9 +316,8 @@ systemctl start mpd.socket</programlisting>
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<programlisting>input {
|
<programlisting>input {
|
||||||
plugin "despotify"
|
plugin "curl"
|
||||||
user "foo"
|
proxy "proxy.local"
|
||||||
password "bar"
|
|
||||||
}
|
}
|
||||||
</programlisting>
|
</programlisting>
|
||||||
|
|
||||||
@@ -943,6 +945,33 @@ systemctl start mpd.socket</programlisting>
|
|||||||
<section id="config_other">
|
<section id="config_other">
|
||||||
<title>Other Settings</title>
|
<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>
|
<section>
|
||||||
<title>The State File</title>
|
<title>The State File</title>
|
||||||
|
|
||||||
@@ -1189,6 +1218,58 @@ database {
|
|||||||
plugin).
|
plugin).
|
||||||
</para>
|
</para>
|
||||||
</section>
|
</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>
|
||||||
|
|
||||||
<chapter id="use">
|
<chapter id="use">
|
||||||
@@ -1244,6 +1325,19 @@ database {
|
|||||||
</para>
|
</para>
|
||||||
</section>
|
</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">
|
<section id="queue">
|
||||||
<title>The queue</title>
|
<title>The queue</title>
|
||||||
|
|
||||||
@@ -1738,66 +1832,6 @@ buffer_size: 16384</programlisting>
|
|||||||
</informaltable>
|
</informaltable>
|
||||||
</section>
|
</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>
|
<section>
|
||||||
<title><varname>file</varname></title>
|
<title><varname>file</varname></title>
|
||||||
|
|
||||||
@@ -2654,7 +2688,8 @@ buffer_size: 16384</programlisting>
|
|||||||
/ <ulink
|
/ <ulink
|
||||||
url="http://icecast.org/"><application>IceCast</application></ulink>.
|
url="http://icecast.org/"><application>IceCast</application></ulink>.
|
||||||
HTTP streaming clients like
|
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>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@@ -3284,70 +3319,6 @@ buffer_size: 16384</programlisting>
|
|||||||
playlist files.
|
playlist files.
|
||||||
</para>
|
</para>
|
||||||
</section>
|
</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>
|
</section>
|
||||||
</chapter>
|
</chapter>
|
||||||
</book>
|
</book>
|
||||||
|
@@ -20,33 +20,45 @@
|
|||||||
#ifndef COMPILER_H
|
#ifndef COMPILER_H
|
||||||
#define COMPILER_H
|
#define COMPILER_H
|
||||||
|
|
||||||
#define GCC_CHECK_VERSION(major, minor) \
|
#define GCC_MAKE_VERSION(major, minor, patchlevel) ((major) * 10000 + (minor) * 100 + patchlevel)
|
||||||
(defined(__GNUC__) && \
|
|
||||||
(__GNUC__ > (major) || (__GNUC__ == (major) && __GNUC_MINOR__ >= (minor))))
|
|
||||||
|
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
#define GCC_VERSION (__GNUC__ * 10000 \
|
#define GCC_VERSION GCC_MAKE_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)
|
||||||
+ __GNUC_MINOR__ * 100 \
|
|
||||||
+ __GNUC_PATCHLEVEL__)
|
|
||||||
#else
|
#else
|
||||||
#define GCC_VERSION 0
|
#define GCC_VERSION 0
|
||||||
#endif
|
#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__
|
#ifdef __clang__
|
||||||
# define CLANG_VERSION (__clang_major__ * 10000 \
|
# define CLANG_VERSION GCC_MAKE_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__)
|
||||||
+ __clang_minor__ * 100 \
|
|
||||||
+ __clang_patchlevel__)
|
|
||||||
# if __clang_major__ < 3
|
# if __clang_major__ < 3
|
||||||
# error Sorry, your clang version is too old. You need at least version 3.1.
|
# error Sorry, your clang version is too old. You need at least version 3.1.
|
||||||
# endif
|
# endif
|
||||||
#elif defined(__GNUC__)
|
#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.
|
# error Sorry, your gcc version is too old. You need at least version 4.6.
|
||||||
# endif
|
# endif
|
||||||
#else
|
#else
|
||||||
# warning Untested compiler. Use at your own risk!
|
# warning Untested compiler. Use at your own risk!
|
||||||
#endif
|
#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)
|
#if GCC_CHECK_VERSION(4,0)
|
||||||
|
|
||||||
/* GCC 4.x */
|
/* GCC 4.x */
|
||||||
@@ -141,7 +153,7 @@
|
|||||||
#if defined(__cplusplus)
|
#if defined(__cplusplus)
|
||||||
|
|
||||||
/* support for C++11 "override" was added in gcc 4.7 */
|
/* 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 override
|
||||||
#define final
|
#define final
|
||||||
#endif
|
#endif
|
||||||
|
@@ -188,6 +188,14 @@ public:
|
|||||||
tag = std::move(other.tag);
|
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 {
|
time_t GetLastModified() const {
|
||||||
return mtime;
|
return mtime;
|
||||||
}
|
}
|
||||||
|
@@ -76,7 +76,10 @@ idle_get_names(void)
|
|||||||
unsigned
|
unsigned
|
||||||
idle_parse_name(const char *name)
|
idle_parse_name(const char *name)
|
||||||
{
|
{
|
||||||
|
#if !CLANG_CHECK_VERSION(3,6)
|
||||||
|
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||||
assert(name != nullptr);
|
assert(name != nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
for (unsigned i = 0; idle_names[i] != nullptr; ++i)
|
for (unsigned i = 0; idle_names[i] != nullptr; ++i)
|
||||||
if (StringEqualsCaseASCII(name, idle_names[i]))
|
if (StringEqualsCaseASCII(name, idle_names[i]))
|
||||||
|
@@ -194,6 +194,12 @@ FileLog(const Domain &domain, const char *message)
|
|||||||
domain.GetName(),
|
domain.GetName(),
|
||||||
chomp_length(message), message);
|
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
|
#ifdef HAVE_GLIB
|
||||||
g_free(converted);
|
g_free(converted);
|
||||||
#endif
|
#endif
|
||||||
|
@@ -111,6 +111,9 @@ log_early_init(bool verbose)
|
|||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
(void)verbose;
|
(void)verbose;
|
||||||
#else
|
#else
|
||||||
|
/* force stderr to be line-buffered */
|
||||||
|
setvbuf(stderr, nullptr, _IOLBF, 0);
|
||||||
|
|
||||||
if (verbose)
|
if (verbose)
|
||||||
SetLogThreshold(LogLevel::DEBUG);
|
SetLogThreshold(LogLevel::DEBUG);
|
||||||
#endif
|
#endif
|
||||||
|
@@ -54,7 +54,6 @@
|
|||||||
#include "system/FatalError.hxx"
|
#include "system/FatalError.hxx"
|
||||||
#include "util/UriUtil.hxx"
|
#include "util/UriUtil.hxx"
|
||||||
#include "util/Error.hxx"
|
#include "util/Error.hxx"
|
||||||
#include "util/Domain.hxx"
|
|
||||||
#include "thread/Id.hxx"
|
#include "thread/Id.hxx"
|
||||||
#include "thread/Slack.hxx"
|
#include "thread/Slack.hxx"
|
||||||
#include "lib/icu/Init.hxx"
|
#include "lib/icu/Init.hxx"
|
||||||
@@ -123,8 +122,6 @@
|
|||||||
static constexpr unsigned DEFAULT_BUFFER_SIZE = 4096;
|
static constexpr unsigned DEFAULT_BUFFER_SIZE = 4096;
|
||||||
static constexpr unsigned DEFAULT_BUFFER_BEFORE_PLAY = 10;
|
static constexpr unsigned DEFAULT_BUFFER_BEFORE_PLAY = 10;
|
||||||
|
|
||||||
static constexpr Domain main_domain("main");
|
|
||||||
|
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
Context *context;
|
Context *context;
|
||||||
#endif
|
#endif
|
||||||
@@ -633,7 +630,7 @@ static int mpd_main_after_fork(struct options options)
|
|||||||
config_get_unsigned(CONF_AUTO_UPDATE_DEPTH,
|
config_get_unsigned(CONF_AUTO_UPDATE_DEPTH,
|
||||||
INT_MAX));
|
INT_MAX));
|
||||||
#else
|
#else
|
||||||
FormatWarning(main_domain,
|
FormatWarning(config_domain,
|
||||||
"inotify: auto_update was disabled. enable during compilation phase");
|
"inotify: auto_update was disabled. enable during compilation phase");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@@ -25,6 +25,7 @@
|
|||||||
#include "MusicPipe.hxx"
|
#include "MusicPipe.hxx"
|
||||||
#include "MusicBuffer.hxx"
|
#include "MusicBuffer.hxx"
|
||||||
#include "MusicChunk.hxx"
|
#include "MusicChunk.hxx"
|
||||||
|
#include "pcm/Silence.hxx"
|
||||||
#include "DetachedSong.hxx"
|
#include "DetachedSong.hxx"
|
||||||
#include "system/FatalError.hxx"
|
#include "system/FatalError.hxx"
|
||||||
#include "CrossFade.hxx"
|
#include "CrossFade.hxx"
|
||||||
@@ -486,8 +487,12 @@ Player::SendSilence()
|
|||||||
|
|
||||||
MusicChunk *chunk = buffer.Allocate();
|
MusicChunk *chunk = buffer.Allocate();
|
||||||
if (chunk == nullptr) {
|
if (chunk == nullptr) {
|
||||||
LogError(player_domain, "Failed to allocate silence buffer");
|
/* this is non-fatal, because this means that the
|
||||||
return false;
|
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
|
#ifndef NDEBUG
|
||||||
@@ -501,7 +506,7 @@ Player::SendSilence()
|
|||||||
|
|
||||||
chunk->time = SignedSongTime::Negative(); /* undefined time stamp */
|
chunk->time = SignedSongTime::Negative(); /* undefined time stamp */
|
||||||
chunk->length = num_frames * frame_size;
|
chunk->length = num_frames * frame_size;
|
||||||
memset(chunk->data, 0, chunk->length);
|
PcmSilence({chunk->data, chunk->length}, play_audio_format.format);
|
||||||
|
|
||||||
Error error;
|
Error error;
|
||||||
if (!pc.outputs.Play(chunk, error)) {
|
if (!pc.outputs.Play(chunk, error)) {
|
||||||
@@ -518,6 +523,8 @@ Player::SeekDecoder()
|
|||||||
{
|
{
|
||||||
assert(pc.next_song != nullptr);
|
assert(pc.next_song != nullptr);
|
||||||
|
|
||||||
|
pc.outputs.Cancel();
|
||||||
|
|
||||||
const SongTime start_time = pc.next_song->GetStartTime();
|
const SongTime start_time = pc.next_song->GetStartTime();
|
||||||
|
|
||||||
if (!dc.LockIsCurrentSong(*pc.next_song)) {
|
if (!dc.LockIsCurrentSong(*pc.next_song)) {
|
||||||
@@ -583,8 +590,6 @@ Player::SeekDecoder()
|
|||||||
/* re-fill the buffer after seeking */
|
/* re-fill the buffer after seeking */
|
||||||
buffering = true;
|
buffering = true;
|
||||||
|
|
||||||
pc.outputs.Cancel();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -612,6 +617,12 @@ Player::ProcessCommand()
|
|||||||
|
|
||||||
queued = true;
|
queued = true;
|
||||||
pc.CommandFinished();
|
pc.CommandFinished();
|
||||||
|
|
||||||
|
pc.Unlock();
|
||||||
|
if (dc.LockIsIdle())
|
||||||
|
StartDecoder(*new MusicPipe());
|
||||||
|
pc.Lock();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PlayerCommand::PAUSE:
|
case PlayerCommand::PAUSE:
|
||||||
|
@@ -77,7 +77,10 @@ SongFilter::Item::Item(unsigned _tag, time_t _time)
|
|||||||
bool
|
bool
|
||||||
SongFilter::Item::StringMatch(const char *s) const
|
SongFilter::Item::StringMatch(const char *s) const
|
||||||
{
|
{
|
||||||
|
#if !CLANG_CHECK_VERSION(3,6)
|
||||||
|
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||||
assert(s != nullptr);
|
assert(s != nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (fold_case) {
|
if (fold_case) {
|
||||||
const std::string folded = IcuCaseFold(s);
|
const std::string folded = IcuCaseFold(s);
|
||||||
|
@@ -77,7 +77,10 @@ SongLoader::LoadFile(const char *path_utf8, Error &error) const
|
|||||||
DetachedSong *
|
DetachedSong *
|
||||||
SongLoader::LoadSong(const char *uri_utf8, Error &error) const
|
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);
|
assert(uri_utf8 != nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (memcmp(uri_utf8, "file:///", 8) == 0)
|
if (memcmp(uri_utf8, "file:///", 8) == 0)
|
||||||
/* absolute path */
|
/* absolute path */
|
||||||
|
@@ -53,7 +53,7 @@ public:
|
|||||||
|
|
||||||
Bzip2ArchiveFile(Path path, InputStream *_is)
|
Bzip2ArchiveFile(Path path, InputStream *_is)
|
||||||
:ArchiveFile(bz2_archive_plugin),
|
:ArchiveFile(bz2_archive_plugin),
|
||||||
name(PathTraitsFS::GetBase(path.c_str())),
|
name(path.GetBase().c_str()),
|
||||||
istream(_is) {
|
istream(_is) {
|
||||||
// remove .bz2 suffix
|
// remove .bz2 suffix
|
||||||
const size_t len = name.length();
|
const size_t len = name.length();
|
||||||
|
@@ -66,7 +66,11 @@ public:
|
|||||||
return iso9660_iso_seek_read(iso, ptr, start, i_size);
|
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 {
|
virtual void Close() override {
|
||||||
Unref();
|
Unref();
|
||||||
@@ -84,32 +88,36 @@ static constexpr Domain iso9660_domain("iso9660");
|
|||||||
/* archive open && listing routine */
|
/* archive open && listing routine */
|
||||||
|
|
||||||
inline void
|
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;
|
auto *entlist = iso9660_ifs_readdir(iso, path);
|
||||||
CdioListNode_t *entnode;
|
|
||||||
iso9660_stat_t *statbuf;
|
|
||||||
char pathname[4096];
|
|
||||||
|
|
||||||
entlist = iso9660_ifs_readdir (iso, psz_path);
|
|
||||||
if (!entlist) {
|
if (!entlist) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* Iterate over the list of nodes that iso9660_ifs_readdir gives */
|
/* Iterate over the list of nodes that iso9660_ifs_readdir gives */
|
||||||
|
CdioListNode_t *entnode;
|
||||||
_CDIO_LIST_FOREACH (entnode, entlist) {
|
_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);
|
size_t filename_length = strlen(filename);
|
||||||
strcat(pathname, statbuf->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 (iso9660_stat_s::_STAT_DIR == statbuf->type ) {
|
||||||
if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) {
|
memcpy(path + new_length, "/", 2);
|
||||||
strcat(pathname, "/");
|
Visit(path, new_length + 1, capacity, visitor);
|
||||||
Visit(pathname, visitor);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
//remove leading /
|
//remove leading /
|
||||||
visitor.VisitArchiveEntry(pathname + 1);
|
visitor.VisitArchiveEntry(path + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_cdio_list_free (entlist, true);
|
_cdio_list_free (entlist, true);
|
||||||
@@ -133,7 +141,8 @@ iso9660_archive_open(Path pathname, Error &error)
|
|||||||
void
|
void
|
||||||
Iso9660ArchiveFile::Visit(ArchiveVisitor &visitor)
|
Iso9660ArchiveFile::Visit(ArchiveVisitor &visitor)
|
||||||
{
|
{
|
||||||
Visit("/", visitor);
|
char path[4096] = "/";
|
||||||
|
Visit(path, 1, sizeof(path), visitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* single archive handling */
|
/* single archive handling */
|
||||||
|
@@ -168,12 +168,13 @@ bool
|
|||||||
ZzipInputStream::Seek(offset_type new_offset, Error &error)
|
ZzipInputStream::Seek(offset_type new_offset, Error &error)
|
||||||
{
|
{
|
||||||
zzip_off_t ofs = zzip_seek(file, new_offset, SEEK_SET);
|
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");
|
error.Set(zzip_domain, "zzip_seek() has failed");
|
||||||
offset = ofs;
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
offset = ofs;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* exported structures */
|
/* exported structures */
|
||||||
|
@@ -127,7 +127,7 @@ public:
|
|||||||
* a local (UNIX domain) socket?
|
* a local (UNIX domain) socket?
|
||||||
*/
|
*/
|
||||||
bool IsLocal() const {
|
bool IsLocal() const {
|
||||||
return uid > 0;
|
return uid >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned GetPermission() const {
|
unsigned GetPermission() const {
|
||||||
|
@@ -41,7 +41,7 @@ Client::AllowFile(Path path_fs, Error &error) const
|
|||||||
instance */
|
instance */
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (uid <= 0) {
|
if (uid < 0) {
|
||||||
/* unauthenticated client */
|
/* unauthenticated client */
|
||||||
error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied");
|
error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied");
|
||||||
return false;
|
return false;
|
||||||
|
@@ -41,7 +41,7 @@ client_process_command_list(Client &client, bool list_ok,
|
|||||||
|
|
||||||
FormatDebug(client_domain, "process command \"%s\"", cmd);
|
FormatDebug(client_domain, "process command \"%s\"", cmd);
|
||||||
ret = command_process(client, num++, 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())
|
if (ret != CommandResult::OK || client.IsExpired())
|
||||||
break;
|
break;
|
||||||
else if (list_ok)
|
else if (list_ok)
|
||||||
@@ -90,7 +90,7 @@ client_process_line(Client &client, char *line)
|
|||||||
std::move(cmd_list));
|
std::move(cmd_list));
|
||||||
FormatDebug(client_domain,
|
FormatDebug(client_domain,
|
||||||
"[%u] process command "
|
"[%u] process command "
|
||||||
"list returned %i", client.num, ret);
|
"list returned %i", client.num, int(ret));
|
||||||
|
|
||||||
if (ret == CommandResult::CLOSE ||
|
if (ret == CommandResult::CLOSE ||
|
||||||
client.IsExpired())
|
client.IsExpired())
|
||||||
@@ -126,7 +126,7 @@ client_process_line(Client &client, char *line)
|
|||||||
ret = command_process(client, 0, line);
|
ret = command_process(client, 0, line);
|
||||||
FormatDebug(client_domain,
|
FormatDebug(client_domain,
|
||||||
"[%u] command returned %i",
|
"[%u] command returned %i",
|
||||||
client.num, ret);
|
client.num, int(ret));
|
||||||
|
|
||||||
if (ret == CommandResult::CLOSE ||
|
if (ret == CommandResult::CLOSE ||
|
||||||
client.IsExpired())
|
client.IsExpired())
|
||||||
|
@@ -52,8 +52,8 @@ Client::OnSocketInput(void *data, size_t length)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case CommandResult::KILL:
|
case CommandResult::KILL:
|
||||||
Close();
|
|
||||||
partition.instance.event_loop->Break();
|
partition.instance.event_loop->Break();
|
||||||
|
Close();
|
||||||
return InputResult::CLOSED;
|
return InputResult::CLOSED;
|
||||||
|
|
||||||
case CommandResult::FINISH:
|
case CommandResult::FINISH:
|
||||||
|
@@ -61,7 +61,16 @@ translate_uri(Client &client, const char *uri)
|
|||||||
CommandResult
|
CommandResult
|
||||||
handle_add(Client &client, gcc_unused unsigned argc, char *argv[])
|
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)
|
if (uri == nullptr)
|
||||||
return CommandResult::ERROR;
|
return CommandResult::ERROR;
|
||||||
|
|
||||||
|
@@ -43,7 +43,7 @@ public:
|
|||||||
|
|
||||||
virtual const LightSong *GetSong(const char *uri_utf8,
|
virtual const LightSong *GetSong(const char *uri_utf8,
|
||||||
Error &error) const override;
|
Error &error) const override;
|
||||||
virtual void ReturnSong(const LightSong *song) const;
|
void ReturnSong(const LightSong *song) const override;
|
||||||
|
|
||||||
virtual bool Visit(const DatabaseSelection &selection,
|
virtual bool Visit(const DatabaseSelection &selection,
|
||||||
VisitDirectory visit_directory,
|
VisitDirectory visit_directory,
|
||||||
|
@@ -103,7 +103,7 @@ public:
|
|||||||
virtual void Close() override;
|
virtual void Close() override;
|
||||||
virtual const LightSong *GetSong(const char *uri_utf8,
|
virtual const LightSong *GetSong(const char *uri_utf8,
|
||||||
Error &error) const override;
|
Error &error) const override;
|
||||||
virtual void ReturnSong(const LightSong *song) const;
|
void ReturnSong(const LightSong *song) const override;
|
||||||
|
|
||||||
virtual bool Visit(const DatabaseSelection &selection,
|
virtual bool Visit(const DatabaseSelection &selection,
|
||||||
VisitDirectory visit_directory,
|
VisitDirectory visit_directory,
|
||||||
@@ -731,7 +731,7 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
|
|||||||
{
|
{
|
||||||
// TODO: eliminate the const_cast
|
// TODO: eliminate the const_cast
|
||||||
if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
|
if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
|
||||||
return nullptr;
|
return false;
|
||||||
|
|
||||||
if (!visit_directory && !visit_playlist && selection.recursive &&
|
if (!visit_directory && !visit_playlist && selection.recursive &&
|
||||||
(ServerSupportsSearchBase(connection)
|
(ServerSupportsSearchBase(connection)
|
||||||
@@ -757,7 +757,7 @@ ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
|
|||||||
{
|
{
|
||||||
// TODO: eliminate the const_cast
|
// TODO: eliminate the const_cast
|
||||||
if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
|
if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
|
||||||
return nullptr;
|
return false;
|
||||||
|
|
||||||
enum mpd_tag_type tag_type2 = Convert(tag_type);
|
enum mpd_tag_type tag_type2 = Convert(tag_type);
|
||||||
if (tag_type2 == MPD_TAG_COUNT) {
|
if (tag_type2 == MPD_TAG_COUNT) {
|
||||||
@@ -810,7 +810,7 @@ ProxyDatabase::GetStats(const DatabaseSelection &selection,
|
|||||||
|
|
||||||
// TODO: eliminate the const_cast
|
// TODO: eliminate the const_cast
|
||||||
if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
|
if (!const_cast<ProxyDatabase *>(this)->EnsureConnected(error))
|
||||||
return nullptr;
|
return false;
|
||||||
|
|
||||||
struct mpd_stats *stats2 =
|
struct mpd_stats *stats2 =
|
||||||
mpd_run_stats(connection);
|
mpd_run_stats(connection);
|
||||||
|
@@ -435,9 +435,12 @@ SimpleDatabase::Save(Error &error)
|
|||||||
bool
|
bool
|
||||||
SimpleDatabase::Mount(const char *uri, Database *db, Error &error)
|
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 != nullptr);
|
||||||
assert(*uri != 0);
|
|
||||||
assert(db != nullptr);
|
assert(db != nullptr);
|
||||||
|
#endif
|
||||||
|
assert(*uri != 0);
|
||||||
|
|
||||||
ScopeDatabaseLock protect;
|
ScopeDatabaseLock protect;
|
||||||
|
|
||||||
@@ -445,13 +448,13 @@ SimpleDatabase::Mount(const char *uri, Database *db, Error &error)
|
|||||||
if (r.uri == nullptr) {
|
if (r.uri == nullptr) {
|
||||||
error.Format(db_domain, DB_CONFLICT,
|
error.Format(db_domain, DB_CONFLICT,
|
||||||
"Already exists: %s", uri);
|
"Already exists: %s", uri);
|
||||||
return nullptr;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strchr(r.uri, '/') != nullptr) {
|
if (strchr(r.uri, '/') != nullptr) {
|
||||||
error.Format(db_domain, DB_NOT_FOUND,
|
error.Format(db_domain, DB_NOT_FOUND,
|
||||||
"Parent not found: %s", uri);
|
"Parent not found: %s", uri);
|
||||||
return nullptr;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Directory *mnt = r.directory->CreateChild(r.uri);
|
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()) {
|
if (cache_path.IsNull()) {
|
||||||
error.Format(db_domain, DB_NOT_FOUND,
|
error.Format(db_domain, DB_NOT_FOUND,
|
||||||
"No 'cache_directory' configured");
|
"No 'cache_directory' configured");
|
||||||
return nullptr;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string name(storage_uri);
|
std::string name(storage_uri);
|
||||||
|
@@ -110,9 +110,9 @@ public:
|
|||||||
virtual bool Open(Error &error) override;
|
virtual bool Open(Error &error) override;
|
||||||
virtual void Close() override;
|
virtual void Close() override;
|
||||||
|
|
||||||
virtual const LightSong *GetSong(const char *uri_utf8,
|
const LightSong *GetSong(const char *uri_utf8,
|
||||||
Error &error) const override;
|
Error &error) const override;
|
||||||
virtual void ReturnSong(const LightSong *song) const;
|
void ReturnSong(const LightSong *song) const override;
|
||||||
|
|
||||||
virtual bool Visit(const DatabaseSelection &selection,
|
virtual bool Visit(const DatabaseSelection &selection,
|
||||||
VisitDirectory visit_directory,
|
VisitDirectory visit_directory,
|
||||||
|
@@ -85,7 +85,7 @@ public:
|
|||||||
virtual void Close() override;
|
virtual void Close() override;
|
||||||
virtual const LightSong *GetSong(const char *uri_utf8,
|
virtual const LightSong *GetSong(const char *uri_utf8,
|
||||||
Error &error) const override;
|
Error &error) const override;
|
||||||
virtual void ReturnSong(const LightSong *song) const;
|
void ReturnSong(const LightSong *song) const override;
|
||||||
|
|
||||||
virtual bool Visit(const DatabaseSelection &selection,
|
virtual bool Visit(const DatabaseSelection &selection,
|
||||||
VisitDirectory visit_directory,
|
VisitDirectory visit_directory,
|
||||||
@@ -101,7 +101,9 @@ public:
|
|||||||
virtual bool GetStats(const DatabaseSelection &selection,
|
virtual bool GetStats(const DatabaseSelection &selection,
|
||||||
DatabaseStats &stats,
|
DatabaseStats &stats,
|
||||||
Error &error) const override;
|
Error &error) const override;
|
||||||
virtual time_t GetUpdateStamp() const {return 0;}
|
time_t GetUpdateStamp() const override {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool Configure(const config_param ¶m, Error &error);
|
bool Configure(const config_param ¶m, Error &error);
|
||||||
|
@@ -34,7 +34,9 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
|
#ifdef HAVE_GLIB
|
||||||
static constexpr Domain exclude_list_domain("exclude_list");
|
static constexpr Domain exclude_list_domain("exclude_list");
|
||||||
|
#endif
|
||||||
|
|
||||||
bool
|
bool
|
||||||
ExcludeList::LoadFile(Path path_fs)
|
ExcludeList::LoadFile(Path path_fs)
|
||||||
|
@@ -334,7 +334,7 @@ UpdateWalk::UpdateDirectory(Directory &directory, const FileInfo &info)
|
|||||||
directory_set_stat(directory, info);
|
directory_set_stat(directory, info);
|
||||||
|
|
||||||
Error error;
|
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) {
|
if (reader.get() == nullptr) {
|
||||||
LogError(error);
|
LogError(error);
|
||||||
return false;
|
return false;
|
||||||
|
@@ -301,7 +301,8 @@ decoder_check_cancel_read(const Decoder *decoder)
|
|||||||
/* ignore the SEEK command during initialization, the plugin
|
/* ignore the SEEK command during initialization, the plugin
|
||||||
should handle that after it has initialized successfully */
|
should handle that after it has initialized successfully */
|
||||||
if (dc.command == DecoderCommand::SEEK &&
|
if (dc.command == DecoderCommand::SEEK &&
|
||||||
(dc.state == DecoderState::START || decoder->seeking))
|
(dc.state == DecoderState::START || decoder->seeking ||
|
||||||
|
decoder->initial_seek_running))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -433,8 +434,11 @@ update_stream_tag(Decoder &decoder, InputStream *is)
|
|||||||
|
|
||||||
/* no stream tag present - submit the song tag
|
/* no stream tag present - submit the song tag
|
||||||
instead */
|
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;
|
delete decoder.stream_tag;
|
||||||
decoder.stream_tag = tag;
|
decoder.stream_tag = tag;
|
||||||
@@ -566,7 +570,7 @@ decoder_tag(Decoder &decoder, InputStream *is,
|
|||||||
/* save the tag */
|
/* save the tag */
|
||||||
|
|
||||||
delete decoder.decoder_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 */
|
/* check for a new stream tag */
|
||||||
|
|
||||||
|
@@ -25,6 +25,7 @@
|
|||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
struct Decoder;
|
struct Decoder;
|
||||||
class InputStream;
|
class InputStream;
|
||||||
|
@@ -37,7 +37,6 @@
|
|||||||
#include "plugins/MadDecoderPlugin.hxx"
|
#include "plugins/MadDecoderPlugin.hxx"
|
||||||
#include "plugins/SndfileDecoderPlugin.hxx"
|
#include "plugins/SndfileDecoderPlugin.hxx"
|
||||||
#include "plugins/Mpg123DecoderPlugin.hxx"
|
#include "plugins/Mpg123DecoderPlugin.hxx"
|
||||||
#include "plugins/Mp4v2DecoderPlugin.hxx"
|
|
||||||
#include "plugins/WildmidiDecoderPlugin.hxx"
|
#include "plugins/WildmidiDecoderPlugin.hxx"
|
||||||
#include "plugins/MikmodDecoderPlugin.hxx"
|
#include "plugins/MikmodDecoderPlugin.hxx"
|
||||||
#include "plugins/ModplugDecoderPlugin.hxx"
|
#include "plugins/ModplugDecoderPlugin.hxx"
|
||||||
@@ -55,9 +54,6 @@ const struct DecoderPlugin *const decoder_plugins[] = {
|
|||||||
#ifdef HAVE_MPG123
|
#ifdef HAVE_MPG123
|
||||||
&mpg123_decoder_plugin,
|
&mpg123_decoder_plugin,
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_MP4V2
|
|
||||||
&mp4v2_decoder_plugin,
|
|
||||||
#endif
|
|
||||||
#ifdef ENABLE_VORBIS_DECODER
|
#ifdef ENABLE_VORBIS_DECODER
|
||||||
&vorbis_decoder_plugin,
|
&vorbis_decoder_plugin,
|
||||||
#endif
|
#endif
|
||||||
|
@@ -26,7 +26,10 @@
|
|||||||
bool
|
bool
|
||||||
DecoderPlugin::SupportsSuffix(const char *suffix) const
|
DecoderPlugin::SupportsSuffix(const char *suffix) const
|
||||||
{
|
{
|
||||||
|
#if !CLANG_CHECK_VERSION(3,6)
|
||||||
|
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||||
assert(suffix != nullptr);
|
assert(suffix != nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
return suffixes != nullptr && string_array_contains(suffixes, suffix);
|
return suffixes != nullptr && string_array_contains(suffixes, suffix);
|
||||||
|
|
||||||
@@ -35,7 +38,10 @@ DecoderPlugin::SupportsSuffix(const char *suffix) const
|
|||||||
bool
|
bool
|
||||||
DecoderPlugin::SupportsMimeType(const char *mime_type) const
|
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);
|
assert(mime_type != nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
return mime_types != nullptr &&
|
return mime_types != nullptr &&
|
||||||
string_array_contains(mime_types, mime_type);
|
string_array_contains(mime_types, mime_type);
|
||||||
|
@@ -98,6 +98,7 @@ decoder_input_stream_open(DecoderControl &dc, const char *uri)
|
|||||||
|
|
||||||
if (!is->Check(error)) {
|
if (!is->Check(error)) {
|
||||||
dc.Unlock();
|
dc.Unlock();
|
||||||
|
delete is;
|
||||||
|
|
||||||
LogError(error);
|
LogError(error);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -255,7 +256,11 @@ decoder_run_stream_fallback(Decoder &decoder, InputStream &is)
|
|||||||
{
|
{
|
||||||
const struct DecoderPlugin *plugin;
|
const struct DecoderPlugin *plugin;
|
||||||
|
|
||||||
|
#ifdef HAVE_FFMPEG
|
||||||
|
plugin = decoder_plugin_from_name("ffmpeg");
|
||||||
|
#else
|
||||||
plugin = decoder_plugin_from_name("mad");
|
plugin = decoder_plugin_from_name("mad");
|
||||||
|
#endif
|
||||||
return plugin != nullptr && plugin->stream_decode != nullptr &&
|
return plugin != nullptr && plugin->stream_decode != nullptr &&
|
||||||
decoder_stream_decode(*plugin, decoder, is);
|
decoder_stream_decode(*plugin, decoder, is);
|
||||||
}
|
}
|
||||||
@@ -380,7 +385,11 @@ decoder_run_song(DecoderControl &dc,
|
|||||||
const DetachedSong &song, const char *uri, Path path_fs)
|
const DetachedSong &song, const char *uri, Path path_fs)
|
||||||
{
|
{
|
||||||
Decoder decoder(dc, dc.start_time.IsPositive(),
|
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;
|
int ret;
|
||||||
|
|
||||||
dc.state = DecoderState::START;
|
dc.state = DecoderState::START;
|
||||||
|
@@ -29,8 +29,10 @@
|
|||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
#include "tag/TagId3.hxx"
|
#include "tag/TagId3.hxx"
|
||||||
#include "util/Error.hxx"
|
#include "util/Error.hxx"
|
||||||
|
#include "util/Alloc.hxx"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
#ifdef HAVE_ID3TAG
|
#ifdef HAVE_ID3TAG
|
||||||
#include <id3tag.h>
|
#include <id3tag.h>
|
||||||
@@ -53,7 +55,7 @@ dsdlib_skip_to(Decoder *decoder, InputStream &is,
|
|||||||
offset_type offset)
|
offset_type offset)
|
||||||
{
|
{
|
||||||
if (is.IsSeekable())
|
if (is.IsSeekable())
|
||||||
return is.Seek(offset, IgnoreError());
|
return is.LockSeek(offset, IgnoreError());
|
||||||
|
|
||||||
if (is.GetOffset() > offset)
|
if (is.GetOffset() > offset)
|
||||||
return false;
|
return false;
|
||||||
@@ -72,7 +74,7 @@ dsdlib_skip(Decoder *decoder, InputStream &is,
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (is.IsSeekable())
|
if (is.IsSeekable())
|
||||||
return is.Seek(is.GetOffset() + delta, IgnoreError());
|
return is.LockSeek(is.GetOffset() + delta, IgnoreError());
|
||||||
|
|
||||||
if (delta > 1024 * 1024)
|
if (delta > 1024 * 1024)
|
||||||
/* don't skip more than one megabyte; it would be too
|
/* 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;
|
const id3_length_t count = size - offset;
|
||||||
|
|
||||||
/* Check and limit id3 tag size to prevent a stack overflow */
|
if (count < 10 || count > 1024 * 1024)
|
||||||
id3_byte_t dsdid3[4096];
|
|
||||||
if (count == 0 || count > sizeof(dsdid3))
|
|
||||||
return;
|
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;
|
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)
|
if (id3_tag == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
scan_id3_tag(id3_tag, handler, handler_ctx);
|
scan_id3_tag(id3_tag, handler, handler_ctx);
|
||||||
|
|
||||||
id3_tag_delete(id3_tag);
|
id3_tag_delete(id3_tag);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@@ -205,7 +205,7 @@ dsdiff_handle_native_tag(InputStream &is,
|
|||||||
if (length == 0 || length > 60)
|
if (length == 0 || length > 60)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
char string[length];
|
char string[length + 1];
|
||||||
char *label;
|
char *label;
|
||||||
label = string;
|
label = string;
|
||||||
|
|
||||||
|
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
|
static void
|
||||||
ffmpeg_copy_metadata(TagType type,
|
FfmpegScanTag(TagType type,
|
||||||
AVDictionary *m, const char *name,
|
AVDictionary *m, const char *name,
|
||||||
const struct tag_handler *handler, void *handler_ctx)
|
const struct tag_handler *handler, void *handler_ctx)
|
||||||
{
|
{
|
||||||
AVDictionaryEntry *mt = nullptr;
|
AVDictionaryEntry *mt = nullptr;
|
||||||
|
|
||||||
@@ -48,8 +48,8 @@ ffmpeg_copy_metadata(TagType type,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
ffmpeg_scan_pairs(AVDictionary *dict,
|
FfmpegScanPairs(AVDictionary *dict,
|
||||||
const struct tag_handler *handler, void *handler_ctx)
|
const struct tag_handler *handler, void *handler_ctx)
|
||||||
{
|
{
|
||||||
AVDictionaryEntry *i = nullptr;
|
AVDictionaryEntry *i = nullptr;
|
||||||
|
|
||||||
@@ -59,18 +59,20 @@ ffmpeg_scan_pairs(AVDictionary *dict,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ffmpeg_scan_dictionary(AVDictionary *dict,
|
FfmpegScanDictionary(AVDictionary *dict,
|
||||||
const struct tag_handler *handler, void *handler_ctx)
|
const struct tag_handler *handler, void *handler_ctx)
|
||||||
{
|
{
|
||||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
|
if (handler->tag != nullptr) {
|
||||||
ffmpeg_copy_metadata(TagType(i), dict, tag_item_names[i],
|
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
|
||||||
handler, handler_ctx);
|
FfmpegScanTag(TagType(i), dict, tag_item_names[i],
|
||||||
|
handler, handler_ctx);
|
||||||
|
|
||||||
for (const struct tag_table *i = ffmpeg_tags;
|
for (const struct tag_table *i = ffmpeg_tags;
|
||||||
i->name != nullptr; ++i)
|
i->name != nullptr; ++i)
|
||||||
ffmpeg_copy_metadata(i->type, dict, i->name,
|
FfmpegScanTag(i->type, dict, i->name,
|
||||||
handler, handler_ctx);
|
handler, handler_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
if (handler->pair != nullptr)
|
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;
|
struct tag_handler;
|
||||||
|
|
||||||
void
|
void
|
||||||
ffmpeg_scan_dictionary(AVDictionary *dict,
|
FfmpegScanDictionary(AVDictionary *dict,
|
||||||
const tag_handler *handler, void *handler_ctx);
|
const tag_handler *handler, void *handler_ctx);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -33,7 +33,7 @@ flac_data::flac_data(Decoder &_decoder,
|
|||||||
InputStream &_input_stream)
|
InputStream &_input_stream)
|
||||||
:FlacInput(_input_stream, &_decoder),
|
:FlacInput(_input_stream, &_decoder),
|
||||||
initialized(false), unsupported(false),
|
initialized(false), unsupported(false),
|
||||||
total_frames(0), first_frame(0), next_frame(0), position(0),
|
position(0),
|
||||||
decoder(_decoder), input_stream(_input_stream)
|
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
|
static void
|
||||||
flac_got_stream_info(struct flac_data *data,
|
flac_got_stream_info(struct flac_data *data,
|
||||||
const FLAC__StreamMetadata_StreamInfo *stream_info)
|
const FLAC__StreamMetadata_StreamInfo *stream_info)
|
||||||
@@ -66,22 +98,10 @@ flac_got_stream_info(struct flac_data *data,
|
|||||||
if (data->initialized || data->unsupported)
|
if (data->initialized || data->unsupported)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Error error;
|
data->Initialize(stream_info->sample_rate,
|
||||||
if (!audio_format_init_checked(data->audio_format,
|
stream_info->bits_per_sample,
|
||||||
stream_info->sample_rate,
|
stream_info->channels,
|
||||||
flac_sample_format(stream_info->bits_per_sample),
|
stream_info->total_samples);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
|
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)
|
if (data->unsupported)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Error error;
|
return data->Initialize(header->sample_rate,
|
||||||
if (!audio_format_init_checked(data->audio_format,
|
header->bits_per_sample,
|
||||||
header->sample_rate,
|
header->channels,
|
||||||
flac_sample_format(header->bits_per_sample),
|
/* unknown duration */
|
||||||
header->channels, error)) {
|
0);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FLAC__StreamDecoderWriteStatus
|
FLAC__StreamDecoderWriteStatus
|
||||||
@@ -155,7 +158,6 @@ flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
|
|||||||
FLAC__uint64 nbytes)
|
FLAC__uint64 nbytes)
|
||||||
{
|
{
|
||||||
void *buffer;
|
void *buffer;
|
||||||
unsigned bit_rate;
|
|
||||||
|
|
||||||
if (!data->initialized && !flac_got_first_frame(data, &frame->header))
|
if (!data->initialized && !flac_got_first_frame(data, &frame->header))
|
||||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
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,
|
data->audio_format.format, buf,
|
||||||
0, frame->header.blocksize);
|
0, frame->header.blocksize);
|
||||||
|
|
||||||
if (nbytes > 0)
|
unsigned bit_rate = nbytes * 8 * frame->header.sample_rate /
|
||||||
bit_rate = nbytes * 8 * frame->header.sample_rate /
|
(1000 * frame->header.blocksize);
|
||||||
(1000 * frame->header.blocksize);
|
|
||||||
else
|
|
||||||
bit_rate = 0;
|
|
||||||
|
|
||||||
auto cmd = decoder_data(data->decoder, data->input_stream,
|
auto cmd = decoder_data(data->decoder, data->input_stream,
|
||||||
buffer, buffer_size,
|
buffer, buffer_size,
|
||||||
bit_rate);
|
bit_rate);
|
||||||
data->next_frame += frame->header.blocksize;
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case DecoderCommand::NONE:
|
case DecoderCommand::NONE:
|
||||||
case DecoderCommand::START:
|
case DecoderCommand::START:
|
||||||
|
@@ -55,23 +55,9 @@ struct flac_data : public FlacInput {
|
|||||||
AudioFormat audio_format;
|
AudioFormat audio_format;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The total number of frames in this song. The decoder
|
* End of last frame's position within the stream. This is
|
||||||
* plugin may initialize this attribute to override the value
|
* used for bit rate calculations.
|
||||||
* provided by libFLAC (e.g. for sub songs from a CUE sheet).
|
|
||||||
*/
|
*/
|
||||||
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;
|
FLAC__uint64 position;
|
||||||
|
|
||||||
Decoder &decoder;
|
Decoder &decoder;
|
||||||
@@ -80,6 +66,12 @@ struct flac_data : public FlacInput {
|
|||||||
Tag tag;
|
Tag tag;
|
||||||
|
|
||||||
flac_data(Decoder &decoder, InputStream &input_stream);
|
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,
|
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
|
||||||
|
@@ -132,26 +132,16 @@ flac_decoder_new(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd,
|
flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd)
|
||||||
FLAC__uint64 duration)
|
|
||||||
{
|
{
|
||||||
data->total_frames = duration;
|
|
||||||
|
|
||||||
if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data->initialized) {
|
if (data->initialized) {
|
||||||
/* done */
|
/* 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,13 +157,10 @@ flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec,
|
flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec)
|
||||||
FLAC__uint64 t_start, FLAC__uint64 t_end)
|
|
||||||
{
|
{
|
||||||
Decoder &decoder = data->decoder;
|
Decoder &decoder = data->decoder;
|
||||||
|
|
||||||
data->first_frame = t_start;
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
DecoderCommand cmd;
|
DecoderCommand cmd;
|
||||||
if (!data->tag.IsEmpty()) {
|
if (!data->tag.IsEmpty()) {
|
||||||
@@ -184,24 +171,49 @@ flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec,
|
|||||||
cmd = decoder_get_command(decoder);
|
cmd = decoder_get_command(decoder);
|
||||||
|
|
||||||
if (cmd == DecoderCommand::SEEK) {
|
if (cmd == DecoderCommand::SEEK) {
|
||||||
FLAC__uint64 seek_sample = t_start +
|
FLAC__uint64 seek_sample =
|
||||||
decoder_seek_where_frame(decoder);
|
decoder_seek_where_frame(decoder);
|
||||||
if (seek_sample >= t_start &&
|
if (FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) {
|
||||||
(t_end == 0 || seek_sample <= t_end) &&
|
|
||||||
FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) {
|
|
||||||
data->next_frame = seek_sample;
|
|
||||||
data->position = 0;
|
data->position = 0;
|
||||||
decoder_command_finished(decoder);
|
decoder_command_finished(decoder);
|
||||||
} else
|
} else
|
||||||
decoder_seek_error(decoder);
|
decoder_seek_error(decoder);
|
||||||
} else if (cmd == DecoderCommand::STOP ||
|
} else if (cmd == DecoderCommand::STOP)
|
||||||
FLAC__stream_decoder_get_state(flac_dec) == FLAC__STREAM_DECODER_END_OF_STREAM)
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (t_end != 0 && data->next_frame >= t_end)
|
switch (FLAC__stream_decoder_get_state(flac_dec)) {
|
||||||
/* end of this sub track */
|
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;
|
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) &&
|
if (!FLAC__stream_decoder_process_single(flac_dec) &&
|
||||||
decoder_get_command(decoder) == DecoderCommand::NONE) {
|
decoder_get_command(decoder) == DecoderCommand::NONE) {
|
||||||
/* a failure that was not triggered by a
|
/* 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);
|
: 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
|
static void
|
||||||
flac_decode_internal(Decoder &decoder,
|
flac_decode_internal(Decoder &decoder,
|
||||||
InputStream &input_stream,
|
InputStream &input_stream,
|
||||||
@@ -263,24 +293,8 @@ flac_decode_internal(Decoder &decoder,
|
|||||||
|
|
||||||
struct flac_data data(decoder, input_stream);
|
struct flac_data data(decoder, input_stream);
|
||||||
|
|
||||||
FLAC__StreamDecoderInitStatus status =
|
FlacInitAndDecode(data, flac_dec, is_ogg);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
FLAC__stream_decoder_delete(flac_dec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,6 +20,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "FlacIOHandle.hxx"
|
#include "FlacIOHandle.hxx"
|
||||||
#include "util/Error.hxx"
|
#include "util/Error.hxx"
|
||||||
|
#include "Log.hxx"
|
||||||
#include "Compiler.h"
|
#include "Compiler.h"
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@@ -87,7 +88,13 @@ FlacIOSeek(FLAC__IOHandle handle, FLAC__int64 _offset, int whence)
|
|||||||
return -1;
|
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
|
static FLAC__int64
|
||||||
|
@@ -156,8 +156,11 @@ gme_file_decode(Decoder &decoder, Path path_fs)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SignedSongTime song_len = ti->length > 0
|
const int length = ti->play_length;
|
||||||
? SignedSongTime::FromMS(ti->length)
|
gme_free_info(ti);
|
||||||
|
|
||||||
|
const SignedSongTime song_len = length > 0
|
||||||
|
? SignedSongTime::FromMS(length)
|
||||||
: SignedSongTime::Negative();
|
: SignedSongTime::Negative();
|
||||||
|
|
||||||
/* initialize the MPD decoder */
|
/* initialize the MPD decoder */
|
||||||
@@ -168,7 +171,6 @@ gme_file_decode(Decoder &decoder, Path path_fs)
|
|||||||
SampleFormat::S16, GME_CHANNELS,
|
SampleFormat::S16, GME_CHANNELS,
|
||||||
error)) {
|
error)) {
|
||||||
LogError(error);
|
LogError(error);
|
||||||
gme_free_info(ti);
|
|
||||||
gme_delete(emu);
|
gme_delete(emu);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -179,8 +181,8 @@ gme_file_decode(Decoder &decoder, Path path_fs)
|
|||||||
if (gme_err != nullptr)
|
if (gme_err != nullptr)
|
||||||
LogWarning(gme_domain, gme_err);
|
LogWarning(gme_domain, gme_err);
|
||||||
|
|
||||||
if (ti->length > 0)
|
if (length > 0)
|
||||||
gme_set_fade(emu, ti->length);
|
gme_set_fade(emu, length);
|
||||||
|
|
||||||
/* play */
|
/* play */
|
||||||
DecoderCommand cmd;
|
DecoderCommand cmd;
|
||||||
@@ -196,16 +198,17 @@ gme_file_decode(Decoder &decoder, Path path_fs)
|
|||||||
if (cmd == DecoderCommand::SEEK) {
|
if (cmd == DecoderCommand::SEEK) {
|
||||||
unsigned where = decoder_seek_time(decoder).ToMS();
|
unsigned where = decoder_seek_time(decoder).ToMS();
|
||||||
gme_err = gme_seek(emu, where);
|
gme_err = gme_seek(emu, where);
|
||||||
if (gme_err != nullptr)
|
if (gme_err != nullptr) {
|
||||||
LogWarning(gme_domain, gme_err);
|
LogWarning(gme_domain, gme_err);
|
||||||
decoder_command_finished(decoder);
|
decoder_seek_error(decoder);
|
||||||
|
} else
|
||||||
|
decoder_command_finished(decoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gme_track_ended(emu))
|
if (gme_track_ended(emu))
|
||||||
break;
|
break;
|
||||||
} while (cmd != DecoderCommand::STOP);
|
} while (cmd != DecoderCommand::STOP);
|
||||||
|
|
||||||
gme_free_info(ti);
|
|
||||||
gme_delete(emu);
|
gme_delete(emu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,9 +239,9 @@ gme_scan_file(Path path_fs,
|
|||||||
|
|
||||||
assert(ti != nullptr);
|
assert(ti != nullptr);
|
||||||
|
|
||||||
if (ti->length > 0)
|
if (ti->play_length > 0)
|
||||||
tag_handler_invoke_duration(handler, handler_ctx,
|
tag_handler_invoke_duration(handler, handler_ctx,
|
||||||
SongTime::FromMS(ti->length));
|
SongTime::FromMS(ti->play_length));
|
||||||
|
|
||||||
if (ti->song != nullptr) {
|
if (ti->song != nullptr) {
|
||||||
if (gme_track_count(emu) > 1) {
|
if (gme_track_count(emu) > 1) {
|
||||||
|
@@ -1,330 +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)
|
|
||||||
{
|
|
||||||
unsigned long sample_rate;
|
|
||||||
|
|
||||||
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, &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 "../DecoderAPI.hxx"
|
||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
#include "CheckAudioFormat.hxx"
|
#include "CheckAudioFormat.hxx"
|
||||||
|
#include "pcm/Traits.hxx"
|
||||||
#include "tag/TagHandler.hxx"
|
#include "tag/TagHandler.hxx"
|
||||||
#include "util/Error.hxx"
|
#include "util/Error.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "util/Macros.hxx"
|
#include "util/Macros.hxx"
|
||||||
|
#include "util/Clamp.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
#include <mpc/mpcdec.h>
|
#include <mpc/mpcdec.h>
|
||||||
@@ -42,6 +44,9 @@ struct mpc_decoder_data {
|
|||||||
|
|
||||||
static constexpr Domain mpcdec_domain("mpcdec");
|
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
|
static mpc_int32_t
|
||||||
mpc_read_cb(mpc_reader *reader, void *ptr, mpc_int32_t size)
|
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 */
|
/* 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)
|
mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample)
|
||||||
{
|
{
|
||||||
/* only doing 16-bit audio for now */
|
/* only doing 16-bit audio for now */
|
||||||
int32_t val;
|
MpcdecSampleTraits::value_type val;
|
||||||
|
|
||||||
enum {
|
constexpr int bits = MpcdecSampleTraits::BITS;
|
||||||
bits = 24,
|
constexpr auto clip_min = MpcdecSampleTraits::MIN;
|
||||||
};
|
constexpr auto clip_max = MpcdecSampleTraits::MAX;
|
||||||
|
|
||||||
const int clip_min = -1 << (bits - 1);
|
|
||||||
const int clip_max = (1 << (bits - 1)) - 1;
|
|
||||||
|
|
||||||
#ifdef MPC_FIXED_POINT
|
#ifdef MPC_FIXED_POINT
|
||||||
const int shift = bits - MPC_FIXED_POINT_SCALE_SHIFT;
|
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;
|
val = sample * float_scale;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (val < clip_min)
|
return Clamp(val, clip_min, clip_max);
|
||||||
val = clip_min;
|
|
||||||
else if (val > clip_max)
|
|
||||||
val = clip_max;
|
|
||||||
|
|
||||||
return val;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
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)
|
unsigned num_samples)
|
||||||
{
|
{
|
||||||
while (num_samples-- > 0)
|
while (num_samples-- > 0)
|
||||||
@@ -162,7 +160,7 @@ mpcdec_decode(Decoder &mpd_decoder, InputStream &is)
|
|||||||
Error error;
|
Error error;
|
||||||
AudioFormat audio_format;
|
AudioFormat audio_format;
|
||||||
if (!audio_format_init_checked(audio_format, info.sample_freq,
|
if (!audio_format_init_checked(audio_format, info.sample_freq,
|
||||||
SampleFormat::S24_P32,
|
mpcdec_sample_format,
|
||||||
info.channels, error)) {
|
info.channels, error)) {
|
||||||
LogError(error);
|
LogError(error);
|
||||||
mpc_demux_exit(demux);
|
mpc_demux_exit(demux);
|
||||||
@@ -214,7 +212,7 @@ mpcdec_decode(Decoder &mpd_decoder, InputStream &is)
|
|||||||
mpc_uint32_t ret = frame.samples;
|
mpc_uint32_t ret = frame.samples;
|
||||||
ret *= info.channels;
|
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);
|
mpc_to_mpd_buffer(chunk, sample_buffer, ret);
|
||||||
|
|
||||||
long bit_rate = vbr_update_bits * audio_format.sample_rate
|
long bit_rate = vbr_update_bits * audio_format.sample_rate
|
||||||
|
@@ -214,7 +214,7 @@ LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno,
|
|||||||
ogg_stream_clear(&os);
|
ogg_stream_clear(&os);
|
||||||
|
|
||||||
/* restore the previous file position */
|
/* restore the previous file position */
|
||||||
is.Seek(old_offset, IgnoreError());
|
is.LockSeek(old_offset, IgnoreError());
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -441,13 +441,15 @@ mpd_opus_scan_stream(InputStream &is,
|
|||||||
if (!oy.ExpectFirstPage(os))
|
if (!oy.ExpectFirstPage(os))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
/* read at most two more pages */
|
/* read at most 64 more pages */
|
||||||
unsigned remaining_pages = 2;
|
unsigned remaining_pages = 64;
|
||||||
|
|
||||||
|
unsigned remaining_packets = 4;
|
||||||
|
|
||||||
bool result = false;
|
bool result = false;
|
||||||
|
|
||||||
ogg_packet packet;
|
ogg_packet packet;
|
||||||
while (true) {
|
while (remaining_packets > 0) {
|
||||||
int r = ogg_stream_packetout(&os, &packet);
|
int r = ogg_stream_packetout(&os, &packet);
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
result = false;
|
result = false;
|
||||||
@@ -466,6 +468,8 @@ mpd_opus_scan_stream(InputStream &is,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
--remaining_packets;
|
||||||
|
|
||||||
if (packet.b_o_s) {
|
if (packet.b_o_s) {
|
||||||
if (!IsOpusHead(packet))
|
if (!IsOpusHead(packet))
|
||||||
break;
|
break;
|
||||||
@@ -510,6 +514,13 @@ static const char *const opus_suffixes[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static const char *const opus_mime_types[] = {
|
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",
|
"audio/opus",
|
||||||
nullptr
|
nullptr
|
||||||
};
|
};
|
||||||
|
@@ -85,7 +85,7 @@ public:
|
|||||||
|
|
||||||
char *ReadString() {
|
char *ReadString() {
|
||||||
uint32_t length;
|
uint32_t length;
|
||||||
if (!ReadWord(length))
|
if (!ReadWord(length) || length >= 65536)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
const char *src = (const char *)Read(length);
|
const char *src = (const char *)Read(length);
|
||||||
|
@@ -22,67 +22,61 @@
|
|||||||
#include "../DecoderAPI.hxx"
|
#include "../DecoderAPI.hxx"
|
||||||
#include "tag/TagHandler.hxx"
|
#include "tag/TagHandler.hxx"
|
||||||
#include "fs/Path.hxx"
|
#include "fs/Path.hxx"
|
||||||
|
#include "fs/AllocatedPath.hxx"
|
||||||
|
#include "util/Macros.hxx"
|
||||||
#include "util/FormatString.hxx"
|
#include "util/FormatString.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
|
#include "util/Error.hxx"
|
||||||
#include "system/ByteOrder.hxx"
|
#include "system/ByteOrder.hxx"
|
||||||
|
#include "system/FatalError.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.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/sidplay2.h>
|
||||||
#include <sidplay/builders/resid.h>
|
#include <sidplay/builders/resid.h>
|
||||||
#include <sidplay/utils/SidTuneMod.h>
|
#include <sidplay/utils/SidTuneMod.h>
|
||||||
|
#include <sidplay/utils/SidDatabase.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#define SUBTUNE_PREFIX "tune_"
|
#define SUBTUNE_PREFIX "tune_"
|
||||||
|
|
||||||
static constexpr Domain sidplay_domain("sidplay");
|
static constexpr Domain sidplay_domain("sidplay");
|
||||||
|
|
||||||
static GPatternSpec *path_with_subtune;
|
static SidDatabase *songlength_database;
|
||||||
static const char *songlength_file;
|
|
||||||
static GKeyFile *songlength_database;
|
|
||||||
|
|
||||||
static bool all_files_are_containers;
|
static bool all_files_are_containers;
|
||||||
static unsigned default_songlength;
|
static unsigned default_songlength;
|
||||||
|
|
||||||
static bool filter_setting;
|
static bool filter_setting;
|
||||||
|
|
||||||
static GKeyFile *
|
static SidDatabase *
|
||||||
sidplay_load_songlength_db(const char *path)
|
sidplay_load_songlength_db(const Path path)
|
||||||
{
|
{
|
||||||
GError *error = nullptr;
|
SidDatabase *db = new SidDatabase();
|
||||||
gchar *data;
|
#ifdef HAVE_SIDPLAYFP
|
||||||
gsize size;
|
bool error = !db->open(path.c_str());
|
||||||
|
#else
|
||||||
if (!g_file_get_contents(path, &data, &size, &error)) {
|
bool error = db->open(path.c_str()) < 0;
|
||||||
|
#endif
|
||||||
|
if (error) {
|
||||||
FormatError(sidplay_domain,
|
FormatError(sidplay_domain,
|
||||||
"unable to read songlengths file %s: %s",
|
"unable to read songlengths file %s: %s",
|
||||||
path, error->message);
|
path.c_str(), db->error());
|
||||||
g_error_free(error);
|
delete db;
|
||||||
return nullptr;
|
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;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,18 +84,18 @@ static bool
|
|||||||
sidplay_init(const config_param ¶m)
|
sidplay_init(const config_param ¶m)
|
||||||
{
|
{
|
||||||
/* read the songlengths database file */
|
/* read the songlengths database file */
|
||||||
songlength_file = param.GetBlockValue("songlength_database");
|
Error error;
|
||||||
if (songlength_file != nullptr)
|
const auto database_path = param.GetBlockPath("songlength_database", error);
|
||||||
songlength_database = sidplay_load_songlength_db(songlength_file);
|
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);
|
default_songlength = param.GetBlockValue("default_songlength", 0u);
|
||||||
|
|
||||||
all_files_are_containers =
|
all_files_are_containers =
|
||||||
param.GetBlockValue("all_files_are_containers", true);
|
param.GetBlockValue("all_files_are_containers", true);
|
||||||
|
|
||||||
path_with_subtune=g_pattern_spec_new(
|
|
||||||
"*/" SUBTUNE_PREFIX "???.sid");
|
|
||||||
|
|
||||||
filter_setting = param.GetBlockValue("filter", true);
|
filter_setting = param.GetBlockValue("filter", true);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -110,98 +104,81 @@ sidplay_init(const config_param ¶m)
|
|||||||
static void
|
static void
|
||||||
sidplay_finish()
|
sidplay_finish()
|
||||||
{
|
{
|
||||||
g_pattern_spec_free(path_with_subtune);
|
delete songlength_database;
|
||||||
|
|
||||||
if(songlength_database)
|
|
||||||
g_key_file_free(songlength_database);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
struct SidplayContainerPath {
|
||||||
* returns the file path stripped of any /tune_xxx.sid subtune
|
AllocatedPath path;
|
||||||
* suffix
|
unsigned track;
|
||||||
*/
|
};
|
||||||
static char *
|
|
||||||
get_container_name(Path path_fs)
|
|
||||||
{
|
|
||||||
char *path_container = strdup(path_fs.c_str());
|
|
||||||
|
|
||||||
if(!g_pattern_match(path_with_subtune,
|
gcc_pure
|
||||||
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
|
|
||||||
*/
|
|
||||||
static unsigned
|
static unsigned
|
||||||
get_song_num(const char *path_fs)
|
ParseSubtuneName(const char *base)
|
||||||
{
|
{
|
||||||
if(g_pattern_match(path_with_subtune,
|
if (memcmp(base, SUBTUNE_PREFIX, sizeof(SUBTUNE_PREFIX) - 1) != 0)
|
||||||
strlen(path_fs), path_fs, nullptr)) {
|
return 0;
|
||||||
char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
|
|
||||||
if(!sub) return 1;
|
|
||||||
|
|
||||||
sub+=strlen("/" SUBTUNE_PREFIX);
|
base += sizeof(SUBTUNE_PREFIX) - 1;
|
||||||
int song_num=strtol(sub, nullptr, 10);
|
|
||||||
|
|
||||||
if (errno == EINVAL)
|
char *endptr;
|
||||||
return 1;
|
auto track = strtoul(base, &endptr, 10);
|
||||||
else
|
if (endptr == base || *endptr != '.')
|
||||||
return song_num;
|
return 0;
|
||||||
} else
|
|
||||||
return 1;
|
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
|
static SignedSongTime
|
||||||
get_song_length(Path path_fs)
|
get_song_length(SidTune &tune)
|
||||||
{
|
{
|
||||||
if (songlength_database == nullptr)
|
if (songlength_database == nullptr)
|
||||||
return SignedSongTime::Negative();
|
return SignedSongTime::Negative();
|
||||||
|
|
||||||
char *sid_file = get_container_name(path_fs);
|
const auto length = songlength_database->length(tune);
|
||||||
SidTuneMod tune(sid_file);
|
if (length < 0)
|
||||||
free(sid_file);
|
|
||||||
if(!tune) {
|
|
||||||
LogWarning(sidplay_domain,
|
|
||||||
"failed to load file for calculating md5 sum");
|
|
||||||
return SignedSongTime::Negative();
|
return SignedSongTime::Negative();
|
||||||
}
|
|
||||||
char md5sum[SIDTUNE_MD5_LENGTH+1];
|
|
||||||
tune.createMD5(md5sum);
|
|
||||||
|
|
||||||
const unsigned song_num = get_song_num(path_fs.c_str());
|
return SignedSongTime::FromS(length);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#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
|
static void
|
||||||
sidplay_file_decode(Decoder &decoder, Path path_fs)
|
sidplay_file_decode(Decoder &decoder, Path path_fs)
|
||||||
{
|
{
|
||||||
@@ -209,26 +186,43 @@ sidplay_file_decode(Decoder &decoder, Path path_fs)
|
|||||||
|
|
||||||
/* load the tune */
|
/* load the tune */
|
||||||
|
|
||||||
char *path_container=get_container_name(path_fs);
|
const auto container = ParseContainerPath(path_fs);
|
||||||
SidTune tune(path_container, nullptr, true);
|
#ifdef HAVE_SIDPLAYFP
|
||||||
free(path_container);
|
SidTune tune(container.path.c_str());
|
||||||
if (!tune) {
|
#else
|
||||||
LogWarning(sidplay_domain, "failed to load file");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int song_num = get_song_num(path_fs.c_str());
|
const int song_num = container.track;
|
||||||
tune.selectSong(song_num);
|
tune.selectSong(song_num);
|
||||||
|
|
||||||
auto duration = get_song_length(path_fs);
|
auto duration = get_song_length(tune);
|
||||||
if (duration.IsNegative() && default_songlength > 0)
|
if (duration.IsNegative() && default_songlength > 0)
|
||||||
duration = SongTime::FromS(default_songlength);
|
duration = SongTime::FromS(default_songlength);
|
||||||
|
|
||||||
/* initialize the player */
|
/* initialize the player */
|
||||||
|
|
||||||
|
#ifdef HAVE_SIDPLAYFP
|
||||||
|
sidplayfp player;
|
||||||
|
#else
|
||||||
sidplay2 player;
|
sidplay2 player;
|
||||||
int iret = player.load(&tune);
|
#endif
|
||||||
if (iret != 0) {
|
#ifdef HAVE_SIDPLAYFP
|
||||||
|
bool error = !player.load(&tune);
|
||||||
|
#else
|
||||||
|
bool error = player.load(&tune) < 0;
|
||||||
|
#endif
|
||||||
|
if (error) {
|
||||||
FormatWarning(sidplay_domain,
|
FormatWarning(sidplay_domain,
|
||||||
"sidplay2.load() failed: %s", player.error());
|
"sidplay2.load() failed: %s", player.error());
|
||||||
return;
|
return;
|
||||||
@@ -236,53 +230,104 @@ sidplay_file_decode(Decoder &decoder, Path path_fs)
|
|||||||
|
|
||||||
/* initialize the builder */
|
/* initialize the builder */
|
||||||
|
|
||||||
ReSIDBuilder builder("ReSID");
|
#ifdef HAVE_SIDPLAYFP
|
||||||
if (!builder) {
|
ReSIDfpBuilder builder("ReSID");
|
||||||
LogWarning(sidplay_domain,
|
if (!builder.getStatus()) {
|
||||||
"failed to initialize ReSIDBuilder");
|
FormatWarning(sidplay_domain,
|
||||||
|
"failed to initialize ReSIDfpBuilder: %s",
|
||||||
|
builder.error());
|
||||||
return;
|
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);
|
builder.create(player.info().maxsids);
|
||||||
if (!builder) {
|
if (!builder) {
|
||||||
LogWarning(sidplay_domain, "ReSIDBuilder.create() failed");
|
FormatWarning(sidplay_domain, "ReSIDBuilder.create() failed: %s",
|
||||||
|
builder.error());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
builder.filter(filter_setting);
|
builder.filter(filter_setting);
|
||||||
if (!builder) {
|
#ifdef HAVE_SIDPLAYFP
|
||||||
LogWarning(sidplay_domain, "ReSIDBuilder.filter() failed");
|
if (!builder.getStatus()) {
|
||||||
|
FormatWarning(sidplay_domain,
|
||||||
|
"ReSIDfpBuilder.filter() failed: %s",
|
||||||
|
builder.error());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
if (!builder) {
|
||||||
|
FormatWarning(sidplay_domain, "ReSIDBuilder.filter() failed: %s",
|
||||||
|
builder.error());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* configure the player */
|
/* configure the player */
|
||||||
|
|
||||||
sid2_config_t config = player.config();
|
auto config = player.config();
|
||||||
|
|
||||||
|
#ifndef HAVE_SIDPLAYFP
|
||||||
config.clockDefault = SID2_CLOCK_PAL;
|
config.clockDefault = SID2_CLOCK_PAL;
|
||||||
config.clockForced = true;
|
config.clockForced = true;
|
||||||
config.clockSpeed = SID2_CLOCK_CORRECT;
|
config.clockSpeed = SID2_CLOCK_CORRECT;
|
||||||
|
#endif
|
||||||
config.frequency = 48000;
|
config.frequency = 48000;
|
||||||
|
#ifndef HAVE_SIDPLAYFP
|
||||||
config.optimisation = SID2_DEFAULT_OPTIMISATION;
|
config.optimisation = SID2_DEFAULT_OPTIMISATION;
|
||||||
|
|
||||||
config.precision = 16;
|
config.precision = 16;
|
||||||
config.sidDefault = SID2_MOS6581;
|
config.sidDefault = SID2_MOS6581;
|
||||||
|
#endif
|
||||||
config.sidEmulation = &builder;
|
config.sidEmulation = &builder;
|
||||||
|
#ifdef HAVE_SIDPLAYFP
|
||||||
|
config.samplingMethod = SidConfig::INTERPOLATE;
|
||||||
|
config.fastSampling = false;
|
||||||
|
#else
|
||||||
config.sidModel = SID2_MODEL_CORRECT;
|
config.sidModel = SID2_MODEL_CORRECT;
|
||||||
config.sidSamples = true;
|
config.sidSamples = true;
|
||||||
config.sampleFormat = IsLittleEndian()
|
config.sampleFormat = IsLittleEndian()
|
||||||
? SID2_LITTLE_SIGNED
|
? SID2_LITTLE_SIGNED
|
||||||
: SID2_BIG_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;
|
config.playback = sid2_stereo;
|
||||||
|
#endif
|
||||||
channels = 2;
|
channels = 2;
|
||||||
} else {
|
} else {
|
||||||
|
#ifdef HAVE_SIDPLAYFP
|
||||||
|
config.playback = SidConfig::MONO;
|
||||||
|
#else
|
||||||
config.playback = sid2_mono;
|
config.playback = sid2_mono;
|
||||||
|
#endif
|
||||||
channels = 1;
|
channels = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
iret = player.config(config);
|
#ifdef HAVE_SIDPLAYFP
|
||||||
if (iret != 0) {
|
error = !player.config(config);
|
||||||
|
#else
|
||||||
|
error = player.config(config) < 0;
|
||||||
|
#endif
|
||||||
|
if (error) {
|
||||||
FormatWarning(sidplay_domain,
|
FormatWarning(sidplay_domain,
|
||||||
"sidplay2.config() failed: %s", player.error());
|
"sidplay2.config() failed: %s", player.error());
|
||||||
return;
|
return;
|
||||||
@@ -297,17 +342,21 @@ sidplay_file_decode(Decoder &decoder, Path path_fs)
|
|||||||
|
|
||||||
/* .. and play */
|
/* .. and play */
|
||||||
|
|
||||||
|
#ifdef HAVE_SIDPLAYFP
|
||||||
|
constexpr unsigned timebase = 1;
|
||||||
|
#else
|
||||||
const unsigned timebase = player.timebase();
|
const unsigned timebase = player.timebase();
|
||||||
|
#endif
|
||||||
const unsigned end = duration.IsNegative()
|
const unsigned end = duration.IsNegative()
|
||||||
? 0u
|
? 0u
|
||||||
: duration.ToScale<uint64_t>(timebase);
|
: duration.ToScale<uint64_t>(timebase);
|
||||||
|
|
||||||
DecoderCommand cmd;
|
DecoderCommand cmd;
|
||||||
do {
|
do {
|
||||||
char buffer[4096];
|
short buffer[4096];
|
||||||
size_t nbytes;
|
size_t nbytes;
|
||||||
|
|
||||||
nbytes = player.play(buffer, sizeof(buffer));
|
nbytes = player.play(buffer, ARRAY_SIZE(buffer));
|
||||||
if (nbytes == 0)
|
if (nbytes == 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -328,7 +377,7 @@ sidplay_file_decode(Decoder &decoder, Path path_fs)
|
|||||||
|
|
||||||
/* ignore data until target time is reached */
|
/* ignore data until target time is reached */
|
||||||
while(data_time<target_time) {
|
while(data_time<target_time) {
|
||||||
nbytes=player.play(buffer, sizeof(buffer));
|
nbytes=player.play(buffer, ARRAY_SIZE(buffer));
|
||||||
if(nbytes==0)
|
if(nbytes==0)
|
||||||
break;
|
break;
|
||||||
data_time = player.time();
|
data_time = player.time();
|
||||||
@@ -343,41 +392,72 @@ sidplay_file_decode(Decoder &decoder, Path path_fs)
|
|||||||
} while (cmd != DecoderCommand::STOP);
|
} 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
|
static bool
|
||||||
sidplay_scan_file(Path path_fs,
|
sidplay_scan_file(Path path_fs,
|
||||||
const struct tag_handler *handler, void *handler_ctx)
|
const struct tag_handler *handler, void *handler_ctx)
|
||||||
{
|
{
|
||||||
const int song_num = get_song_num(path_fs.c_str());
|
const auto container = ParseContainerPath(path_fs);
|
||||||
char *path_container=get_container_name(path_fs);
|
const unsigned song_num = container.track;
|
||||||
|
|
||||||
SidTune tune(path_container, nullptr, true);
|
#ifdef HAVE_SIDPLAYFP
|
||||||
free(path_container);
|
SidTune tune(container.path.c_str());
|
||||||
if (!tune)
|
#else
|
||||||
|
SidTuneMod tune(container.path.c_str());
|
||||||
|
#endif
|
||||||
|
if (!tune.getStatus())
|
||||||
return false;
|
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 SidTuneInfo &info = tune.getInfo();
|
||||||
|
const unsigned n_tracks = info.songs;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* title */
|
/* title */
|
||||||
const char *title;
|
const char *title = GetInfoString(info, 0);
|
||||||
if (info.numberOfInfoStrings > 0 && info.infoString[0] != nullptr)
|
if (title == nullptr)
|
||||||
title=info.infoString[0];
|
title = "";
|
||||||
else
|
|
||||||
title="";
|
|
||||||
|
|
||||||
if(info.songs>1) {
|
if (n_tracks > 1) {
|
||||||
char tag_title[1024];
|
char tag_title[1024];
|
||||||
snprintf(tag_title, sizeof(tag_title),
|
snprintf(tag_title, sizeof(tag_title),
|
||||||
"%s (%d/%d)",
|
"%s (%d/%u)",
|
||||||
title, song_num, info.songs);
|
title, song_num, n_tracks);
|
||||||
tag_handler_invoke_tag(handler, handler_ctx,
|
tag_handler_invoke_tag(handler, handler_ctx,
|
||||||
TAG_TITLE, tag_title);
|
TAG_TITLE, tag_title);
|
||||||
} else
|
} else
|
||||||
tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, title);
|
tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, title);
|
||||||
|
|
||||||
/* artist */
|
/* 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,
|
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 */
|
/* track */
|
||||||
char track[16];
|
char track[16];
|
||||||
@@ -385,7 +465,7 @@ sidplay_scan_file(Path path_fs,
|
|||||||
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track);
|
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track);
|
||||||
|
|
||||||
/* time */
|
/* time */
|
||||||
const auto duration = get_song_length(path_fs);
|
const auto duration = get_song_length(tune);
|
||||||
if (!duration.IsNegative())
|
if (!duration.IsNegative())
|
||||||
tag_handler_invoke_duration(handler, handler_ctx,
|
tag_handler_invoke_duration(handler, handler_ctx,
|
||||||
SongTime(duration));
|
SongTime(duration));
|
||||||
@@ -397,19 +477,25 @@ static char *
|
|||||||
sidplay_container_scan(Path path_fs, const unsigned int tnum)
|
sidplay_container_scan(Path path_fs, const unsigned int tnum)
|
||||||
{
|
{
|
||||||
SidTune tune(path_fs.c_str(), nullptr, true);
|
SidTune tune(path_fs.c_str(), nullptr, true);
|
||||||
if (!tune)
|
if (!tune.getStatus())
|
||||||
return nullptr;
|
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
|
/* Don't treat sids containing a single tune
|
||||||
as containers */
|
as containers */
|
||||||
if(!all_files_are_containers && info.songs<2)
|
if(!all_files_are_containers && n_tracks < 2)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
/* Construct container/tune path names, eg.
|
/* Construct container/tune path names, eg.
|
||||||
Delta.sid/tune_001.sid */
|
Delta.sid/tune_001.sid */
|
||||||
if(tnum<=info.songs) {
|
if (tnum <= n_tracks) {
|
||||||
return FormatNew(SUBTUNE_PREFIX "%03u.sid", tnum);
|
return FormatNew(SUBTUNE_PREFIX "%03u.sid", tnum);
|
||||||
} else
|
} else
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@@ -157,8 +157,6 @@ flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error)
|
|||||||
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
|
struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
|
||||||
unsigned bits_per_sample;
|
unsigned bits_per_sample;
|
||||||
|
|
||||||
encoder->audio_format = audio_format;
|
|
||||||
|
|
||||||
/* FIXME: flac should support 32bit as well */
|
/* FIXME: flac should support 32bit as well */
|
||||||
switch (audio_format.format) {
|
switch (audio_format.format) {
|
||||||
case SampleFormat::S8:
|
case SampleFormat::S8:
|
||||||
@@ -178,6 +176,8 @@ flac_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error)
|
|||||||
audio_format.format = SampleFormat::S24_P32;
|
audio_format.format = SampleFormat::S24_P32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
encoder->audio_format = audio_format;
|
||||||
|
|
||||||
/* allocate the encoder */
|
/* allocate the encoder */
|
||||||
encoder->fse = FLAC__stream_encoder_new();
|
encoder->fse = FLAC__stream_encoder_new();
|
||||||
if (encoder->fse == nullptr) {
|
if (encoder->fse == nullptr) {
|
||||||
|
@@ -66,7 +66,7 @@ struct opus_encoder {
|
|||||||
|
|
||||||
ogg_int64_t granulepos;
|
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");
|
static constexpr Domain opus_encoder_domain("opus_encoder");
|
||||||
|
@@ -26,7 +26,6 @@
|
|||||||
#include "util/NumberParser.hxx"
|
#include "util/NumberParser.hxx"
|
||||||
#include "util/DynamicFifoBuffer.hxx"
|
#include "util/DynamicFifoBuffer.hxx"
|
||||||
#include "util/Error.hxx"
|
#include "util/Error.hxx"
|
||||||
#include "util/Domain.hxx"
|
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
{
|
{
|
||||||
@@ -60,8 +59,6 @@ struct ShineEncoder {
|
|||||||
bool WriteChunk(bool flush);
|
bool WriteChunk(bool flush);
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr Domain shine_encoder_domain("shine_encoder");
|
|
||||||
|
|
||||||
inline bool
|
inline bool
|
||||||
ShineEncoder::Configure(const config_param ¶m,
|
ShineEncoder::Configure(const config_param ¶m,
|
||||||
gcc_unused Error &error)
|
gcc_unused Error &error)
|
||||||
|
@@ -27,6 +27,8 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
static constexpr uint16_t WAVE_FORMAT_PCM = 1;
|
||||||
|
|
||||||
struct WaveEncoder {
|
struct WaveEncoder {
|
||||||
Encoder encoder;
|
Encoder encoder;
|
||||||
unsigned bits;
|
unsigned bits;
|
||||||
@@ -64,15 +66,15 @@ fill_wave_header(struct wave_header *header, int channels, int bits,
|
|||||||
header->id_fmt = ToLE32(0x20746d66);
|
header->id_fmt = ToLE32(0x20746d66);
|
||||||
header->id_data = ToLE32(0x61746164);
|
header->id_data = ToLE32(0x61746164);
|
||||||
|
|
||||||
/* wave format */
|
/* wave format */
|
||||||
header->format = ToLE16(1); // PCM_FORMAT
|
header->format = ToLE16(WAVE_FORMAT_PCM);
|
||||||
header->channels = ToLE16(channels);
|
header->channels = ToLE16(channels);
|
||||||
header->bits = ToLE16(bits);
|
header->bits = ToLE16(bits);
|
||||||
header->freq = ToLE32(freq);
|
header->freq = ToLE32(freq);
|
||||||
header->blocksize = ToLE16(block_size);
|
header->blocksize = ToLE16(block_size);
|
||||||
header->byterate = ToLE32(freq * block_size);
|
header->byterate = ToLE32(freq * block_size);
|
||||||
|
|
||||||
/* chunk sizes (fake data length) */
|
/* chunk sizes (fake data length) */
|
||||||
header->fmt_size = ToLE32(16);
|
header->fmt_size = ToLE32(16);
|
||||||
header->data_size = ToLE32(data_size);
|
header->data_size = ToLE32(data_size);
|
||||||
header->riff_size = ToLE32(4 + (8 + 16) + (8 + data_size));
|
header->riff_size = ToLE32(4 + (8 + 16) + (8 + data_size));
|
||||||
|
@@ -118,9 +118,15 @@ BufferedSocket::OnSocketReady(unsigned flags)
|
|||||||
if (flags & READ) {
|
if (flags & READ) {
|
||||||
assert(!input.IsFull());
|
assert(!input.IsFull());
|
||||||
|
|
||||||
if (!ReadToBuffer() || !ResumeInput())
|
if (!ReadToBuffer())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (!ResumeInput())
|
||||||
|
/* we must return "true" here or
|
||||||
|
SocketMonitor::Dispatch() will call
|
||||||
|
Cancel() on a freed object */
|
||||||
|
return true;
|
||||||
|
|
||||||
if (!input.IsFull())
|
if (!input.IsFull())
|
||||||
ScheduleRead();
|
ScheduleRead();
|
||||||
}
|
}
|
||||||
|
@@ -21,9 +21,6 @@
|
|||||||
#define MPD_SOCKET_DEFERRED_MONITOR_HXX
|
#define MPD_SOCKET_DEFERRED_MONITOR_HXX
|
||||||
|
|
||||||
#include "check.h"
|
#include "check.h"
|
||||||
#include "Compiler.h"
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
class EventLoop;
|
class EventLoop;
|
||||||
|
|
||||||
|
@@ -179,9 +179,10 @@ EventLoop::Run()
|
|||||||
mutex.lock();
|
mutex.lock();
|
||||||
HandleDeferred();
|
HandleDeferred();
|
||||||
busy = false;
|
busy = false;
|
||||||
|
const bool _again = again;
|
||||||
mutex.unlock();
|
mutex.unlock();
|
||||||
|
|
||||||
if (again)
|
if (_again)
|
||||||
/* re-evaluate timers because one of the
|
/* re-evaluate timers because one of the
|
||||||
IdleMonitors may have added a new
|
IdleMonitors may have added a new
|
||||||
timeout */
|
timeout */
|
||||||
|
@@ -130,7 +130,7 @@ get_remote_uid(int fd)
|
|||||||
socklen_t len = sizeof (cred);
|
socklen_t len = sizeof (cred);
|
||||||
|
|
||||||
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0)
|
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0)
|
||||||
return 0;
|
return -1;
|
||||||
|
|
||||||
return cred.uid;
|
return cred.uid;
|
||||||
#else
|
#else
|
||||||
|
@@ -53,10 +53,11 @@ public:
|
|||||||
children.emplace_back(name, filter);
|
children.emplace_back(name, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual AudioFormat Open(AudioFormat &af, Error &error) override;
|
/* virtual methods from class Filter */
|
||||||
virtual void Close();
|
AudioFormat Open(AudioFormat &af, Error &error) override;
|
||||||
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
void Close() override;
|
||||||
Error &error);
|
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||||
|
Error &error) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
|
@@ -34,10 +34,11 @@ class NormalizeFilter final : public Filter {
|
|||||||
PcmBuffer buffer;
|
PcmBuffer buffer;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual AudioFormat Open(AudioFormat &af, Error &error) override;
|
/* virtual methods from class Filter */
|
||||||
virtual void Close();
|
AudioFormat Open(AudioFormat &af, Error &error) override;
|
||||||
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
void Close() override;
|
||||||
Error &error) override;
|
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||||
|
Error &error) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
static Filter *
|
static Filter *
|
||||||
|
@@ -112,10 +112,11 @@ public:
|
|||||||
*/
|
*/
|
||||||
void Update();
|
void Update();
|
||||||
|
|
||||||
virtual AudioFormat Open(AudioFormat &af, Error &error) override;
|
/* virtual methods from class Filter */
|
||||||
virtual void Close();
|
AudioFormat Open(AudioFormat &af, Error &error) override;
|
||||||
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
void Close() override;
|
||||||
Error &error) override;
|
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||||
|
Error &error) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -133,8 +134,6 @@ ReplayGainFilter::Update()
|
|||||||
volume = pcm_float_to_volume(scale);
|
volume = pcm_float_to_volume(scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
pv.SetVolume(volume);
|
|
||||||
|
|
||||||
if (mixer != nullptr) {
|
if (mixer != nullptr) {
|
||||||
/* update the hardware mixer volume */
|
/* update the hardware mixer volume */
|
||||||
|
|
||||||
@@ -145,7 +144,8 @@ ReplayGainFilter::Update()
|
|||||||
Error error;
|
Error error;
|
||||||
if (!mixer_set_volume(mixer, _volume, error))
|
if (!mixer_set_volume(mixer, _volume, error))
|
||||||
LogError(error, "Failed to update hardware mixer");
|
LogError(error, "Failed to update hardware mixer");
|
||||||
}
|
} else
|
||||||
|
pv.SetVolume(volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Filter *
|
static Filter *
|
||||||
@@ -173,7 +173,9 @@ ReplayGainFilter::Close()
|
|||||||
ConstBuffer<void>
|
ConstBuffer<void>
|
||||||
ReplayGainFilter::FilterPCM(ConstBuffer<void> src, gcc_unused Error &error)
|
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 = {
|
const struct filter_plugin replay_gain_filter_plugin = {
|
||||||
|
@@ -47,9 +47,11 @@
|
|||||||
#include "filter/FilterInternal.hxx"
|
#include "filter/FilterInternal.hxx"
|
||||||
#include "filter/FilterRegistry.hxx"
|
#include "filter/FilterRegistry.hxx"
|
||||||
#include "pcm/PcmBuffer.hxx"
|
#include "pcm/PcmBuffer.hxx"
|
||||||
|
#include "pcm/Silence.hxx"
|
||||||
#include "util/StringUtil.hxx"
|
#include "util/StringUtil.hxx"
|
||||||
#include "util/Error.hxx"
|
#include "util/Error.hxx"
|
||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
|
#include "util/WritableBuffer.hxx"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
@@ -120,10 +122,11 @@ public:
|
|||||||
*/
|
*/
|
||||||
bool Configure(const config_param ¶m, Error &error);
|
bool Configure(const config_param ¶m, Error &error);
|
||||||
|
|
||||||
virtual AudioFormat Open(AudioFormat &af, Error &error) override;
|
/* virtual methods from class Filter */
|
||||||
virtual void Close();
|
AudioFormat Open(AudioFormat &af, Error &error) override;
|
||||||
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
void Close() override;
|
||||||
Error &error) override;
|
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||||
|
Error &error) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@@ -265,9 +268,8 @@ RouteFilter::FilterPCM(ConstBuffer<void> src, gcc_unused Error &error)
|
|||||||
(unsigned)sources[c] >= input_format.channels) {
|
(unsigned)sources[c] >= input_format.channels) {
|
||||||
// No source for this destination output,
|
// No source for this destination output,
|
||||||
// give it zeroes as input
|
// give it zeroes as input
|
||||||
memset(chan_destination,
|
PcmSilence({chan_destination, bytes_per_frame_per_channel},
|
||||||
0x00,
|
input_format.format);
|
||||||
bytes_per_frame_per_channel);
|
|
||||||
} else {
|
} else {
|
||||||
// Get the data from channel sources[c]
|
// Get the data from channel sources[c]
|
||||||
// and copy it to the output
|
// and copy it to the output
|
||||||
|
@@ -26,7 +26,6 @@
|
|||||||
#include "AudioFormat.hxx"
|
#include "AudioFormat.hxx"
|
||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
#include "util/Error.hxx"
|
#include "util/Error.hxx"
|
||||||
#include "util/Domain.hxx"
|
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -43,14 +42,13 @@ public:
|
|||||||
pv.SetVolume(_volume);
|
pv.SetVolume(_volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual AudioFormat Open(AudioFormat &af, Error &error) override;
|
/* virtual methods from class Filter */
|
||||||
virtual void Close();
|
AudioFormat Open(AudioFormat &af, Error &error) override;
|
||||||
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
void Close() override;
|
||||||
Error &error) override;
|
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||||
|
Error &error) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr Domain volume_domain("pcm_volume");
|
|
||||||
|
|
||||||
static Filter *
|
static Filter *
|
||||||
volume_filter_init(gcc_unused const config_param ¶m,
|
volume_filter_init(gcc_unused const config_param ¶m,
|
||||||
gcc_unused Error &error)
|
gcc_unused Error &error)
|
||||||
|
@@ -46,7 +46,11 @@ AllocatedPath
|
|||||||
AllocatedPath::FromUTF8(const char *path_utf8)
|
AllocatedPath::FromUTF8(const char *path_utf8)
|
||||||
{
|
{
|
||||||
#ifdef HAVE_GLIB
|
#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
|
#else
|
||||||
return FromFS(path_utf8);
|
return FromFS(path_utf8);
|
||||||
#endif
|
#endif
|
||||||
|
@@ -252,7 +252,7 @@ public:
|
|||||||
void ChopSeparators();
|
void ChopSeparators();
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
bool IsAbsolute() {
|
bool IsAbsolute() const {
|
||||||
return PathTraitsFS::IsAbsolute(c_str());
|
return PathTraitsFS::IsAbsolute(c_str());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -103,7 +103,10 @@ static inline void FixSeparators(std::string &s)
|
|||||||
std::string
|
std::string
|
||||||
PathToUTF8(const char *path_fs)
|
PathToUTF8(const char *path_fs)
|
||||||
{
|
{
|
||||||
|
#if !CLANG_CHECK_VERSION(3,6)
|
||||||
|
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||||
assert(path_fs != nullptr);
|
assert(path_fs != nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_GLIB
|
#ifdef HAVE_GLIB
|
||||||
if (fs_charset.empty()) {
|
if (fs_charset.empty()) {
|
||||||
@@ -144,7 +147,10 @@ PathToUTF8(const char *path_fs)
|
|||||||
char *
|
char *
|
||||||
PathFromUTF8(const char *path_utf8)
|
PathFromUTF8(const char *path_utf8)
|
||||||
{
|
{
|
||||||
|
#if !CLANG_CHECK_VERSION(3,6)
|
||||||
|
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||||
assert(path_utf8 != nullptr);
|
assert(path_utf8 != nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (fs_charset.empty())
|
if (fs_charset.empty())
|
||||||
return g_strdup(path_utf8);
|
return g_strdup(path_utf8);
|
||||||
|
@@ -29,6 +29,8 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
class AllocatedPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A path name in the native file system character set.
|
* A path name in the native file system character set.
|
||||||
*
|
*
|
||||||
@@ -128,6 +130,22 @@ public:
|
|||||||
gcc_pure
|
gcc_pure
|
||||||
std::string ToUTF8() const;
|
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
|
* Determine the relative part of the given path to this
|
||||||
* object, not including the directory separator. Returns an
|
* object, not including the directory separator. Returns an
|
||||||
@@ -140,7 +158,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
bool IsAbsolute() {
|
bool IsAbsolute() const {
|
||||||
return PathTraitsFS::IsAbsolute(c_str());
|
return PathTraitsFS::IsAbsolute(c_str());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -17,9 +17,12 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_HXX
|
#include "config.h"
|
||||||
#define MPD_PLAYLIST_DESPOTIFY_PLAYLIST_PLUGIN_HXX
|
#include "Path.hxx"
|
||||||
|
#include "AllocatedPath.hxx"
|
||||||
|
|
||||||
extern const struct playlist_plugin despotify_playlist_plugin;
|
AllocatedPath
|
||||||
|
Path::GetDirectoryName() const
|
||||||
#endif
|
{
|
||||||
|
return AllocatedPath::FromFS(PathTraitsFS::GetParent(c_str()));
|
||||||
|
}
|
@@ -52,7 +52,10 @@ template<typename Traits>
|
|||||||
typename Traits::const_pointer
|
typename Traits::const_pointer
|
||||||
GetBasePathImpl(typename Traits::const_pointer p)
|
GetBasePathImpl(typename Traits::const_pointer p)
|
||||||
{
|
{
|
||||||
|
#if !CLANG_CHECK_VERSION(3,6)
|
||||||
|
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||||
assert(p != nullptr);
|
assert(p != nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
typename Traits::const_pointer sep = Traits::FindLastSeparator(p);
|
typename Traits::const_pointer sep = Traits::FindLastSeparator(p);
|
||||||
return sep != nullptr
|
return sep != nullptr
|
||||||
@@ -64,7 +67,10 @@ template<typename Traits>
|
|||||||
typename Traits::string
|
typename Traits::string
|
||||||
GetParentPathImpl(typename Traits::const_pointer p)
|
GetParentPathImpl(typename Traits::const_pointer p)
|
||||||
{
|
{
|
||||||
|
#if !CLANG_CHECK_VERSION(3,6)
|
||||||
|
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||||
assert(p != nullptr);
|
assert(p != nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
typename Traits::const_pointer sep = Traits::FindLastSeparator(p);
|
typename Traits::const_pointer sep = Traits::FindLastSeparator(p);
|
||||||
if (sep == nullptr)
|
if (sep == nullptr)
|
||||||
|
@@ -57,7 +57,11 @@ struct PathTraitsFS {
|
|||||||
|
|
||||||
gcc_pure gcc_nonnull_all
|
gcc_pure gcc_nonnull_all
|
||||||
static const_pointer FindLastSeparator(const_pointer p) {
|
static const_pointer FindLastSeparator(const_pointer p) {
|
||||||
|
#if !CLANG_CHECK_VERSION(3,6)
|
||||||
|
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||||
assert(p != nullptr);
|
assert(p != nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
const_pointer pos = p + GetLength(p);
|
const_pointer pos = p + GetLength(p);
|
||||||
while (p != pos && !IsSeparator(*pos))
|
while (p != pos && !IsSeparator(*pos))
|
||||||
@@ -77,7 +81,11 @@ struct PathTraitsFS {
|
|||||||
|
|
||||||
gcc_pure gcc_nonnull_all
|
gcc_pure gcc_nonnull_all
|
||||||
static bool IsAbsolute(const_pointer p) {
|
static bool IsAbsolute(const_pointer p) {
|
||||||
|
#if !CLANG_CHECK_VERSION(3,6)
|
||||||
|
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||||
assert(p != nullptr);
|
assert(p != nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
if (IsDrive(p) && IsSeparator(p[2]))
|
if (IsDrive(p) && IsSeparator(p[2]))
|
||||||
return true;
|
return true;
|
||||||
@@ -147,7 +155,11 @@ struct PathTraitsUTF8 {
|
|||||||
|
|
||||||
gcc_pure gcc_nonnull_all
|
gcc_pure gcc_nonnull_all
|
||||||
static const_pointer FindLastSeparator(const_pointer p) {
|
static const_pointer FindLastSeparator(const_pointer p) {
|
||||||
|
#if !CLANG_CHECK_VERSION(3,6)
|
||||||
|
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||||
assert(p != nullptr);
|
assert(p != nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
return strrchr(p, SEPARATOR);
|
return strrchr(p, SEPARATOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +172,11 @@ struct PathTraitsUTF8 {
|
|||||||
|
|
||||||
gcc_pure gcc_nonnull_all
|
gcc_pure gcc_nonnull_all
|
||||||
static bool IsAbsolute(const_pointer p) {
|
static bool IsAbsolute(const_pointer p) {
|
||||||
|
#if !CLANG_CHECK_VERSION(3,6)
|
||||||
|
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||||
assert(p != nullptr);
|
assert(p != nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
if (IsDrive(p) && IsSeparator(p[2]))
|
if (IsDrive(p) && IsSeparator(p[2]))
|
||||||
return true;
|
return true;
|
||||||
|
@@ -62,6 +62,7 @@ FileOutputStream::Commit(gcc_unused Error &error)
|
|||||||
assert(IsDefined());
|
assert(IsDefined());
|
||||||
|
|
||||||
CloseHandle(handle);
|
CloseHandle(handle);
|
||||||
|
handle = INVALID_HANDLE_VALUE;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,6 +72,7 @@ FileOutputStream::Cancel()
|
|||||||
assert(IsDefined());
|
assert(IsDefined());
|
||||||
|
|
||||||
CloseHandle(handle);
|
CloseHandle(handle);
|
||||||
|
handle = INVALID_HANDLE_VALUE;
|
||||||
RemoveFile(path);
|
RemoveFile(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -160,6 +160,11 @@ AsyncInputStream::SeekDone()
|
|||||||
assert(io_thread_inside());
|
assert(io_thread_inside());
|
||||||
assert(IsSeekPending());
|
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;
|
seek_state = SeekState::NONE;
|
||||||
cond.broadcast();
|
cond.broadcast();
|
||||||
}
|
}
|
||||||
|
@@ -67,7 +67,7 @@ input_stream_global_init(Error &error)
|
|||||||
case InputPlugin::InitResult::UNAVAILABLE:
|
case InputPlugin::InitResult::UNAVAILABLE:
|
||||||
if (error.IsDefined()) {
|
if (error.IsDefined()) {
|
||||||
FormatError(error,
|
FormatError(error,
|
||||||
"Input plugin '%s' is unavailable: ",
|
"Input plugin '%s' is unavailable",
|
||||||
plugin->name);
|
plugin->name);
|
||||||
error.Clear();
|
error.Clear();
|
||||||
}
|
}
|
||||||
|
@@ -122,7 +122,10 @@ InputStream::IsAvailable()
|
|||||||
size_t
|
size_t
|
||||||
InputStream::LockRead(void *ptr, size_t _size, Error &error)
|
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);
|
assert(ptr != nullptr);
|
||||||
|
#endif
|
||||||
assert(_size > 0);
|
assert(_size > 0);
|
||||||
|
|
||||||
const ScopeLock protect(mutex);
|
const ScopeLock protect(mutex);
|
||||||
|
@@ -54,10 +54,6 @@
|
|||||||
#include "plugins/CdioParanoiaInputPlugin.hxx"
|
#include "plugins/CdioParanoiaInputPlugin.hxx"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_DESPOTIFY
|
|
||||||
#include "plugins/DespotifyInputPlugin.hxx"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const InputPlugin *const input_plugins[] = {
|
const InputPlugin *const input_plugins[] = {
|
||||||
&input_plugin_file,
|
&input_plugin_file,
|
||||||
#ifdef HAVE_ALSA
|
#ifdef HAVE_ALSA
|
||||||
@@ -83,9 +79,6 @@ const InputPlugin *const input_plugins[] = {
|
|||||||
#endif
|
#endif
|
||||||
#ifdef ENABLE_CDIO_PARANOIA
|
#ifdef ENABLE_CDIO_PARANOIA
|
||||||
&input_plugin_cdio_paranoia,
|
&input_plugin_cdio_paranoia,
|
||||||
#endif
|
|
||||||
#ifdef ENABLE_DESPOTIFY
|
|
||||||
&input_plugin_despotify,
|
|
||||||
#endif
|
#endif
|
||||||
nullptr
|
nullptr
|
||||||
};
|
};
|
||||||
|
@@ -43,6 +43,8 @@
|
|||||||
|
|
||||||
#include <alsa/asoundlib.h>
|
#include <alsa/asoundlib.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
@@ -453,6 +453,8 @@ CurlInputStream::RequestDone(CURLcode result, long status)
|
|||||||
SeekDone();
|
SeekDone();
|
||||||
else if (!IsReady())
|
else if (!IsReady())
|
||||||
SetReady();
|
SetReady();
|
||||||
|
else
|
||||||
|
cond.broadcast();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@@ -1,227 +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"
|
|
||||||
#include "DespotifyInputPlugin.hxx"
|
|
||||||
#include "lib/despotify/DespotifyUtils.hxx"
|
|
||||||
#include "../InputStream.hxx"
|
|
||||||
#include "../InputPlugin.hxx"
|
|
||||||
#include "tag/Tag.hxx"
|
|
||||||
#include "util/StringUtil.hxx"
|
|
||||||
#include "Log.hxx"
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
#include <despotify.h>
|
|
||||||
}
|
|
||||||
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
class DespotifyInputStream final : public InputStream {
|
|
||||||
struct despotify_session *session;
|
|
||||||
struct ds_track *track;
|
|
||||||
Tag tag;
|
|
||||||
struct ds_pcm_data pcm;
|
|
||||||
size_t len_available;
|
|
||||||
bool eof;
|
|
||||||
|
|
||||||
DespotifyInputStream(const char *_uri,
|
|
||||||
Mutex &_mutex, Cond &_cond,
|
|
||||||
despotify_session *_session,
|
|
||||||
ds_track *_track)
|
|
||||||
:InputStream(_uri, _mutex, _cond),
|
|
||||||
session(_session), track(_track),
|
|
||||||
tag(mpd_despotify_tag_from_track(*track)),
|
|
||||||
len_available(0), eof(false) {
|
|
||||||
|
|
||||||
memset(&pcm, 0, sizeof(pcm));
|
|
||||||
|
|
||||||
/* Despotify outputs pcm data */
|
|
||||||
SetMimeType("audio/x-mpd-cdda-pcm");
|
|
||||||
SetReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
~DespotifyInputStream();
|
|
||||||
|
|
||||||
static InputStream *Open(const char *url, Mutex &mutex, Cond &cond,
|
|
||||||
Error &error);
|
|
||||||
|
|
||||||
void Callback(int sig);
|
|
||||||
|
|
||||||
/* virtual methods from InputStream */
|
|
||||||
|
|
||||||
bool IsEOF() override {
|
|
||||||
return eof;
|
|
||||||
}
|
|
||||||
|
|
||||||
Tag *ReadTag() override {
|
|
||||||
if (tag.IsEmpty())
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
Tag *result = new Tag(std::move(tag));
|
|
||||||
tag.Clear();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t Read(void *ptr, size_t size, Error &error) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void FillBuffer();
|
|
||||||
};
|
|
||||||
|
|
||||||
inline void
|
|
||||||
DespotifyInputStream::FillBuffer()
|
|
||||||
{
|
|
||||||
/* Wait until there is data */
|
|
||||||
while (1) {
|
|
||||||
int rc = despotify_get_pcm(session, &pcm);
|
|
||||||
|
|
||||||
if (rc == 0 && pcm.len) {
|
|
||||||
len_available = pcm.len;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eof == true)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (rc < 0) {
|
|
||||||
LogDebug(despotify_domain, "despotify_get_pcm error");
|
|
||||||
eof = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Wait a while until next iteration */
|
|
||||||
usleep(50 * 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void
|
|
||||||
DespotifyInputStream::Callback(int sig)
|
|
||||||
{
|
|
||||||
switch (sig) {
|
|
||||||
case DESPOTIFY_NEW_TRACK:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DESPOTIFY_TIME_TELL:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DESPOTIFY_TRACK_PLAY_ERROR:
|
|
||||||
LogWarning(despotify_domain, "Track play error");
|
|
||||||
eof = true;
|
|
||||||
len_available = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DESPOTIFY_END_OF_PLAYLIST:
|
|
||||||
eof = true;
|
|
||||||
LogDebug(despotify_domain, "End of playlist");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void callback(gcc_unused struct despotify_session* ds,
|
|
||||||
int sig, gcc_unused void* data, void* callback_data)
|
|
||||||
{
|
|
||||||
DespotifyInputStream *ctx = (DespotifyInputStream *)callback_data;
|
|
||||||
|
|
||||||
ctx->Callback(sig);
|
|
||||||
}
|
|
||||||
|
|
||||||
DespotifyInputStream::~DespotifyInputStream()
|
|
||||||
{
|
|
||||||
mpd_despotify_unregister_callback(callback);
|
|
||||||
despotify_free_track(track);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline InputStream *
|
|
||||||
DespotifyInputStream::Open(const char *url,
|
|
||||||
Mutex &mutex, Cond &cond,
|
|
||||||
gcc_unused Error &error)
|
|
||||||
{
|
|
||||||
if (!StringStartsWith(url, "spt://"))
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
despotify_session *session = mpd_despotify_get_session();
|
|
||||||
if (session == nullptr)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
ds_link *ds_link = despotify_link_from_uri(url + 6);
|
|
||||||
if (!ds_link) {
|
|
||||||
FormatDebug(despotify_domain, "Can't find %s", url);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
if (ds_link->type != LINK_TYPE_TRACK) {
|
|
||||||
despotify_free_link(ds_link);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
ds_track *track = despotify_link_get_track(session, ds_link);
|
|
||||||
despotify_free_link(ds_link);
|
|
||||||
if (!track)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
DespotifyInputStream *ctx =
|
|
||||||
new DespotifyInputStream(url, mutex, cond,
|
|
||||||
session, track);
|
|
||||||
|
|
||||||
if (!mpd_despotify_register_callback(callback, ctx)) {
|
|
||||||
delete ctx;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (despotify_play(ctx->session, ctx->track, false) == false) {
|
|
||||||
mpd_despotify_unregister_callback(callback);
|
|
||||||
delete ctx;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
static InputStream *
|
|
||||||
input_despotify_open(const char *url, Mutex &mutex, Cond &cond, Error &error)
|
|
||||||
{
|
|
||||||
return DespotifyInputStream::Open(url, mutex, cond, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
DespotifyInputStream::Read(void *ptr, size_t read_size,
|
|
||||||
gcc_unused Error &error)
|
|
||||||
{
|
|
||||||
if (len_available == 0)
|
|
||||||
FillBuffer();
|
|
||||||
|
|
||||||
size_t to_cpy = std::min(read_size, len_available);
|
|
||||||
memcpy(ptr, pcm.buf, to_cpy);
|
|
||||||
len_available -= to_cpy;
|
|
||||||
|
|
||||||
offset += to_cpy;
|
|
||||||
|
|
||||||
return to_cpy;
|
|
||||||
}
|
|
||||||
|
|
||||||
const InputPlugin input_plugin_despotify = {
|
|
||||||
"despotify",
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
input_despotify_open,
|
|
||||||
};
|
|
@@ -43,7 +43,7 @@ protected:
|
|||||||
virtual size_t ThreadRead(void *ptr, size_t size,
|
virtual size_t ThreadRead(void *ptr, size_t size,
|
||||||
Error &error) override;
|
Error &error) override;
|
||||||
|
|
||||||
virtual void Close() {
|
void Close() override {
|
||||||
mmsx_close(mms);
|
mmsx_close(mms);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -92,6 +92,13 @@ input_mms_open(const char *url,
|
|||||||
size_t
|
size_t
|
||||||
MmsInputStream::ThreadRead(void *ptr, size_t read_size, Error &error)
|
MmsInputStream::ThreadRead(void *ptr, size_t read_size, Error &error)
|
||||||
{
|
{
|
||||||
|
/* unfortunately, mmsx_read() blocks until the whole buffer
|
||||||
|
has been filled; to avoid big latencies, limit the size of
|
||||||
|
each chunk we read to a reasonable size */
|
||||||
|
constexpr size_t MAX_CHUNK = 16384;
|
||||||
|
if (read_size > MAX_CHUNK)
|
||||||
|
read_size = MAX_CHUNK;
|
||||||
|
|
||||||
int nbytes = mmsx_read(nullptr, mms, (char *)ptr, read_size);
|
int nbytes = mmsx_read(nullptr, mms, (char *)ptr, read_size);
|
||||||
if (nbytes <= 0) {
|
if (nbytes <= 0) {
|
||||||
if (nbytes < 0)
|
if (nbytes < 0)
|
||||||
|
@@ -132,6 +132,7 @@ SmbclientInputStream::Read(void *ptr, size_t read_size, Error &error)
|
|||||||
nbytes = 0;
|
nbytes = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
offset += nbytes;
|
||||||
return nbytes;
|
return nbytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -28,7 +28,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef JAVA_FILE_HXX
|
#ifndef JAVA_FILE_HXX
|
||||||
#define JAVA_FILE_HPP
|
#define JAVA_FILE_HXX
|
||||||
|
|
||||||
#include "Object.hxx"
|
#include "Object.hxx"
|
||||||
|
|
||||||
|
@@ -1,153 +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 "DespotifyUtils.hxx"
|
|
||||||
#include "tag/Tag.hxx"
|
|
||||||
#include "tag/TagBuilder.hxx"
|
|
||||||
#include "config/ConfigGlobal.hxx"
|
|
||||||
#include "config/ConfigOption.hxx"
|
|
||||||
#include "util/Domain.hxx"
|
|
||||||
#include "Log.hxx"
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
#include <despotify.h>
|
|
||||||
}
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
const Domain despotify_domain("despotify");
|
|
||||||
|
|
||||||
static struct despotify_session *g_session;
|
|
||||||
static void (*registered_callbacks[8])(struct despotify_session *,
|
|
||||||
int, void *, void *);
|
|
||||||
static void *registered_callback_data[8];
|
|
||||||
|
|
||||||
static void
|
|
||||||
callback(struct despotify_session* ds, int sig,
|
|
||||||
void *data, gcc_unused void *callback_data)
|
|
||||||
{
|
|
||||||
size_t i;
|
|
||||||
|
|
||||||
for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
|
|
||||||
void (*cb)(struct despotify_session *, int, void *, void *) = registered_callbacks[i];
|
|
||||||
void *cb_data = registered_callback_data[i];
|
|
||||||
|
|
||||||
if (cb)
|
|
||||||
cb(ds, sig, data, cb_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *),
|
|
||||||
void *cb_data)
|
|
||||||
{
|
|
||||||
size_t i;
|
|
||||||
|
|
||||||
for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
|
|
||||||
|
|
||||||
if (!registered_callbacks[i]) {
|
|
||||||
registered_callbacks[i] = cb;
|
|
||||||
registered_callback_data[i] = cb_data;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *))
|
|
||||||
{
|
|
||||||
size_t i;
|
|
||||||
|
|
||||||
for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) {
|
|
||||||
|
|
||||||
if (registered_callbacks[i] == cb) {
|
|
||||||
registered_callbacks[i] = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Tag
|
|
||||||
mpd_despotify_tag_from_track(const ds_track &track)
|
|
||||||
{
|
|
||||||
char tracknum[20];
|
|
||||||
char comment[80];
|
|
||||||
char date[20];
|
|
||||||
|
|
||||||
if (!track.has_meta_data)
|
|
||||||
return Tag();
|
|
||||||
|
|
||||||
TagBuilder tag;
|
|
||||||
snprintf(tracknum, sizeof(tracknum), "%d", track.tracknumber);
|
|
||||||
snprintf(date, sizeof(date), "%d", track.year);
|
|
||||||
snprintf(comment, sizeof(comment), "Bitrate %d Kbps, %sgeo restricted",
|
|
||||||
track.file_bitrate / 1000,
|
|
||||||
track.geo_restricted ? "" : "not ");
|
|
||||||
tag.AddItem(TAG_TITLE, track.title);
|
|
||||||
tag.AddItem(TAG_ARTIST, track.artist->name);
|
|
||||||
tag.AddItem(TAG_TRACK, tracknum);
|
|
||||||
tag.AddItem(TAG_ALBUM, track.album);
|
|
||||||
tag.AddItem(TAG_DATE, date);
|
|
||||||
tag.AddItem(TAG_COMMENT, comment);
|
|
||||||
tag.SetDuration(SignedSongTime::FromMS(track.length));
|
|
||||||
|
|
||||||
return tag.Commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
struct despotify_session *mpd_despotify_get_session(void)
|
|
||||||
{
|
|
||||||
const char *user;
|
|
||||||
const char *passwd;
|
|
||||||
bool high_bitrate;
|
|
||||||
|
|
||||||
if (g_session)
|
|
||||||
return g_session;
|
|
||||||
|
|
||||||
user = config_get_string(CONF_DESPOTIFY_USER, nullptr);
|
|
||||||
passwd = config_get_string(CONF_DESPOTIFY_PASSWORD, nullptr);
|
|
||||||
high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true);
|
|
||||||
|
|
||||||
if (user == nullptr || passwd == nullptr) {
|
|
||||||
LogDebug(despotify_domain,
|
|
||||||
"disabling despotify because account is not configured");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!despotify_init()) {
|
|
||||||
LogWarning(despotify_domain, "Can't initialize despotify");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_session = despotify_init_client(callback, nullptr,
|
|
||||||
high_bitrate, true);
|
|
||||||
if (!g_session) {
|
|
||||||
LogWarning(despotify_domain,
|
|
||||||
"Can't initialize despotify client");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!despotify_authenticate(g_session, user, passwd)) {
|
|
||||||
LogWarning(despotify_domain,
|
|
||||||
"Can't authenticate despotify session");
|
|
||||||
despotify_exit(g_session);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return g_session;
|
|
||||||
}
|
|
@@ -1,71 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef MPD_DESPOTIFY_H
|
|
||||||
#define MPD_DESPOTIFY_H
|
|
||||||
|
|
||||||
struct Tag;
|
|
||||||
struct despotify_session;
|
|
||||||
struct ds_track;
|
|
||||||
|
|
||||||
extern const class Domain despotify_domain;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the current despotify session.
|
|
||||||
*
|
|
||||||
* If the session isn't initialized, this function will initialize
|
|
||||||
* it and connect to Spotify.
|
|
||||||
*
|
|
||||||
* @return a pointer to the despotify session, or nullptr if it can't
|
|
||||||
* be initialized (e.g., if the configuration isn't supplied)
|
|
||||||
*/
|
|
||||||
struct despotify_session *mpd_despotify_get_session(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a MPD tags structure from a spotify track
|
|
||||||
*
|
|
||||||
* @param track the track to convert
|
|
||||||
*
|
|
||||||
* @return filled in #Tag structure
|
|
||||||
*/
|
|
||||||
Tag
|
|
||||||
mpd_despotify_tag_from_track(const ds_track &track);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a despotify callback.
|
|
||||||
*
|
|
||||||
* Despotify calls this e.g., when a track ends.
|
|
||||||
*
|
|
||||||
* @param cb the callback
|
|
||||||
* @param cb_data the data to pass to the callback
|
|
||||||
*
|
|
||||||
* @return true if the callback could be registered
|
|
||||||
*/
|
|
||||||
bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *),
|
|
||||||
void *cb_data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregister a despotify callback.
|
|
||||||
*
|
|
||||||
* @param cb the callback to unregister.
|
|
||||||
*/
|
|
||||||
void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *));
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
72
src/lib/ffmpeg/Buffer.hxx
Normal file
72
src/lib/ffmpeg/Buffer.hxx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_FFMPEG_BUFFER_HXX
|
||||||
|
#define MPD_FFMPEG_BUFFER_HXX
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <libavutil/mem.h>
|
||||||
|
|
||||||
|
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(52, 18, 0)
|
||||||
|
#define HAVE_AV_FAST_MALLOC
|
||||||
|
#else
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 25, 0)
|
||||||
|
#define HAVE_AV_FAST_MALLOC
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/* suppress the ffmpeg compatibility macro */
|
||||||
|
#ifdef SampleFormat
|
||||||
|
#undef SampleFormat
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class FfmpegBuffer {
|
||||||
|
void *data;
|
||||||
|
unsigned size;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FfmpegBuffer():data(nullptr), size(0) {}
|
||||||
|
|
||||||
|
~FfmpegBuffer() {
|
||||||
|
av_free(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *Get(size_t min_size) {
|
||||||
|
#ifdef HAVE_AV_FAST_MALLOC
|
||||||
|
av_fast_malloc(&data, &size, min_size);
|
||||||
|
#else
|
||||||
|
void *new_data = av_fast_realloc(data, &size, min_size);
|
||||||
|
if (new_data == nullptr)
|
||||||
|
return AVERROR(ENOMEM);
|
||||||
|
data = new_data;
|
||||||
|
#endif
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T *GetT(size_t n) {
|
||||||
|
return (T *)Get(n * sizeof(T));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
38
src/lib/ffmpeg/Init.cxx
Normal file
38
src/lib/ffmpeg/Init.cxx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* necessary because libavutil/common.h uses UINT64_C */
|
||||||
|
#define __STDC_CONSTANT_MACROS
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "Init.hxx"
|
||||||
|
#include "LogCallback.hxx"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
FfmpegInit()
|
||||||
|
{
|
||||||
|
av_log_set_callback(FfmpegLogCallback);
|
||||||
|
|
||||||
|
av_register_all();
|
||||||
|
}
|
||||||
|
|
@@ -17,9 +17,10 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef INPUT_DESPOTIFY_HXX
|
#ifndef MPD_FFMPEG_INIT_HXX
|
||||||
#define INPUT_DESPOTIFY_HXX
|
#define MPD_FFMPEG_INIT_HXX
|
||||||
|
|
||||||
extern const struct InputPlugin input_plugin_despotify;
|
void
|
||||||
|
FfmpegInit();
|
||||||
|
|
||||||
#endif
|
#endif
|
66
src/lib/ffmpeg/LogCallback.cxx
Normal file
66
src/lib/ffmpeg/LogCallback.cxx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* necessary because libavutil/common.h uses UINT64_C */
|
||||||
|
#define __STDC_CONSTANT_MACROS
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "LogCallback.hxx"
|
||||||
|
#include "Domain.hxx"
|
||||||
|
#include "LogV.hxx"
|
||||||
|
#include "util/Domain.hxx"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <libavutil/log.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
gcc_const
|
||||||
|
static LogLevel
|
||||||
|
FfmpegImportLogLevel(int level)
|
||||||
|
{
|
||||||
|
if (level <= AV_LOG_FATAL)
|
||||||
|
return LogLevel::ERROR;
|
||||||
|
|
||||||
|
if (level <= AV_LOG_WARNING)
|
||||||
|
return LogLevel::WARNING;
|
||||||
|
|
||||||
|
if (level <= AV_LOG_INFO)
|
||||||
|
return LogLevel::INFO;
|
||||||
|
|
||||||
|
return LogLevel::DEBUG;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
FfmpegLogCallback(gcc_unused void *ptr, int level, const char *fmt, va_list vl)
|
||||||
|
{
|
||||||
|
const AVClass * cls = nullptr;
|
||||||
|
|
||||||
|
if (ptr != nullptr)
|
||||||
|
cls = *(const AVClass *const*)ptr;
|
||||||
|
|
||||||
|
if (cls != nullptr) {
|
||||||
|
char domain[64];
|
||||||
|
snprintf(domain, sizeof(domain), "%s/%s",
|
||||||
|
ffmpeg_domain.GetName(), cls->item_name(ptr));
|
||||||
|
const Domain d(domain);
|
||||||
|
LogFormatV(d, FfmpegImportLogLevel(level), fmt, vl);
|
||||||
|
}
|
||||||
|
}
|
30
src/lib/ffmpeg/LogCallback.hxx
Normal file
30
src/lib/ffmpeg/LogCallback.hxx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_FFMPEG_LOG_CALLBACK_HXX
|
||||||
|
#define MPD_FFMPEG_LOG_CALLBACK_HXX
|
||||||
|
|
||||||
|
#include "check.h"
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
void
|
||||||
|
FfmpegLogCallback(void *ptr, int level, const char *fmt, va_list vl);
|
||||||
|
|
||||||
|
#endif
|
45
src/lib/ffmpeg/LogError.cxx
Normal file
45
src/lib/ffmpeg/LogError.cxx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* 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 "LogError.hxx"
|
||||||
|
#include "Domain.hxx"
|
||||||
|
#include "Log.hxx"
|
||||||
|
|
||||||
|
#include <cstdint> /* needed due to libavutil bug */
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <libavutil/error.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
LogFfmpegError(int errnum)
|
||||||
|
{
|
||||||
|
char msg[256];
|
||||||
|
av_strerror(errnum, msg, sizeof(msg));
|
||||||
|
LogError(ffmpeg_domain, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
LogFfmpegError(int errnum, const char *prefix)
|
||||||
|
{
|
||||||
|
char msg[256];
|
||||||
|
av_strerror(errnum, msg, sizeof(msg));
|
||||||
|
FormatError(ffmpeg_domain, "%s: %s", prefix, msg);
|
||||||
|
}
|
@@ -17,9 +17,13 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef MPD_DECODER_MP4V2_HXX
|
#ifndef MPD_FFMPEG_LOG_ERROR_HXX
|
||||||
#define MPD_DECODER_MP4V2_HXX
|
#define MPD_FFMPEG_LOG_ERROR_HXX
|
||||||
|
|
||||||
extern const struct DecoderPlugin mp4v2_decoder_plugin;
|
void
|
||||||
|
LogFfmpegError(int errnum);
|
||||||
|
|
||||||
|
void
|
||||||
|
LogFfmpegError(int errnum, const char *prefix);
|
||||||
|
|
||||||
#endif
|
#endif
|
104
src/lib/ffmpeg/Time.hxx
Normal file
104
src/lib/ffmpeg/Time.hxx
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_FFMPEG_TIME_HXX
|
||||||
|
#define MPD_FFMPEG_TIME_HXX
|
||||||
|
|
||||||
|
#include "Chrono.hxx"
|
||||||
|
#include "Compiler.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <libavutil/avutil.h>
|
||||||
|
#include <libavutil/mathematics.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* suppress the ffmpeg compatibility macro */
|
||||||
|
#ifdef SampleFormat
|
||||||
|
#undef SampleFormat
|
||||||
|
#endif
|
||||||
|
|
||||||
|
gcc_const
|
||||||
|
static inline double
|
||||||
|
FfmpegTimeToDouble(int64_t t, const AVRational time_base)
|
||||||
|
{
|
||||||
|
assert(t != (int64_t)AV_NOPTS_VALUE);
|
||||||
|
|
||||||
|
return (double)av_rescale_q(t, time_base, (AVRational){1, 1024})
|
||||||
|
/ (double)1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Ratio>
|
||||||
|
static inline constexpr AVRational
|
||||||
|
RatioToAVRational()
|
||||||
|
{
|
||||||
|
return { Ratio::num, Ratio::den };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a FFmpeg time stamp to a #SongTime.
|
||||||
|
*/
|
||||||
|
gcc_const
|
||||||
|
static inline SongTime
|
||||||
|
FromFfmpegTime(int64_t t, const AVRational time_base)
|
||||||
|
{
|
||||||
|
assert(t != (int64_t)AV_NOPTS_VALUE);
|
||||||
|
|
||||||
|
return SongTime::FromMS(av_rescale_q(t, time_base,
|
||||||
|
(AVRational){1, 1000}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a FFmpeg time stamp to a #SignedSongTime.
|
||||||
|
*/
|
||||||
|
gcc_const
|
||||||
|
static inline SignedSongTime
|
||||||
|
FromFfmpegTimeChecked(int64_t t, const AVRational time_base)
|
||||||
|
{
|
||||||
|
return t != (int64_t)AV_NOPTS_VALUE
|
||||||
|
? SignedSongTime(FromFfmpegTime(t, time_base))
|
||||||
|
: SignedSongTime::Negative();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a #SongTime to a FFmpeg time stamp with the given base.
|
||||||
|
*/
|
||||||
|
gcc_const
|
||||||
|
static inline int64_t
|
||||||
|
ToFfmpegTime(SongTime t, const AVRational time_base)
|
||||||
|
{
|
||||||
|
return av_rescale_q(t.count(),
|
||||||
|
RatioToAVRational<SongTime::period>(),
|
||||||
|
time_base);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace #AV_NOPTS_VALUE with the given fallback.
|
||||||
|
*/
|
||||||
|
static constexpr int64_t
|
||||||
|
FfmpegTimestampFallback(int64_t t, int64_t fallback)
|
||||||
|
{
|
||||||
|
return gcc_likely(t != int64_t(AV_NOPTS_VALUE))
|
||||||
|
? t
|
||||||
|
: fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user