Compare commits

...

38 Commits

Author SHA1 Message Date
Max Kellermann
0341ca1b6a release v0.23.7 2022-05-09 23:04:30 +02:00
Max Kellermann
7581ea55db python/build/libs.py: update CURL to 7.83.0 2022-05-09 23:03:14 +02:00
Max Kellermann
fc9cee38d8 python/build/libs.py: update OpenSSL to 3.0.3 2022-05-09 23:03:14 +02:00
Max Kellermann
b175e4128d encoder/meson.build: always generate encoder/Features.h
Fixes regression from commit 85f9863e0a
2022-05-09 22:52:59 +02:00
Max Kellermann
97b07798b0 doc/protocol.rst: clarify repeat/single/random side effects 2022-05-09 22:50:57 +02:00
Max Kellermann
112fcd206d Merge branch 'fix-hls-seeking' of https://github.com/burrocargado/MPD into v0.23.x 2022-05-09 22:44:53 +02:00
BurroCargado
11d1f56062 Fix seeking HLS on-demand streaming not working
This issue occurs when playing HLS streaming delivered
from a server that does not support partial requests.
The issue is reproduced as follows(using Ubuntu 20.04 PC):

1. Prepare HLS example content.

$ mkdir test
$ ffmpeg -i example.flac -vn -c:a aac -b:a 128000 -f hls -hls_list_size 0 test/output.m3u8
(ffmpeg 4.2.4 is used)

2. Prepare web server without partial requests support.
(Docker version 20.10.12 and NGINX official Docker image is used)

$ docker run --name tmp-nginx-container -d nginx
$ docker cp tmp-nginx-container:/etc/nginx/conf.d/default.conf .
$ docker rm -f tmp-nginx-container

Edit default.conf and add "max_ranges 0;" to "location / {...}".
This disables partial requests support,
removes 'Accept-Ranges: bytes' header from the server response.
Then, run the server:

$ docker run --name test-nginx -v $PWD/test:/usr/share/nginx/html:ro -v $PWD/default.conf:/etc/nginx/conf.d/default.conf -d -p 8080:80 nginx

3. Setup MPD to Play the next URL.

http://address-of-the-server:8080/output.m3u8

Seeking this stream results in "exception: Not seekable".
2022-05-07 12:18:56 +09:00
BurroCargado
bd840d4638 decoder/plugins/FFmpegDecoder: fix IsSeekable()
AVFMTCTX_UNSEEKABLE signals the stream is not seekable
according to FFmpeg source code description:
8e98dfc57f/libavformat/avformat.h (L1181)
2022-05-07 09:48:04 +09:00
Max Kellermann
c3d393f214 tag/Id3Picture: fix unaligned access 2022-04-26 21:03:48 +02:00
Max Kellermann
f88fc0ca1a util/ByteOrder: add class PackedBE32 2022-04-26 21:03:05 +02:00
Max Kellermann
fb8d8242ab tag/ApeLoader: fix unaligned access
Fixes part 4 of https://github.com/MusicPlayerDaemon/MPD/issues/1490
2022-04-26 21:00:41 +02:00
Max Kellermann
f2a3dfd700 decoder/ffmpeg: add missing nullptr checks
Fixes part 1 of https://github.com/MusicPlayerDaemon/MPD/issues/1490
2022-04-26 20:51:57 +02:00
Max Kellermann
85f9863e0a meson.build: always enable Wave encoder for Snapcast
Even if the "wave_encoder" option is disabled (and no other encoder
plugins are enabled), forcefully enable the Wave encoder (if Snapcast
is enabled).

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1500
2022-04-26 20:13:43 +02:00
Max Kellermann
83572701f4 python/build/libs.py: update Boost to 1.79.0 2022-04-26 18:27:51 +02:00
Max Kellermann
fa7d7e9187 python/build/libs.py: update OpenSSL to 3.0.2 2022-04-26 18:27:51 +02:00
Max Kellermann
f818cde32c python/build/libs.py: update FFmpeg to 5.0.1 2022-04-26 18:27:51 +02:00
Max Kellermann
9da93cd887 python/build/libs.py: update zlib to 1.2.12 2022-04-26 18:27:51 +02:00
Rosen Penev
026e7ea32a update all subprojecs
Done with meson wrap update

Signed-off-by: Rosen Penev <rosenp@gmail.com>
2022-04-26 18:01:53 +02:00
Max Kellermann
9659d19718 lib/upnp/Init: use if with initalizer 2022-04-26 17:58:33 +02:00
Rosen Penev
50d35c9677 upnp: use UpnpInit2 always
libupnp 1.14 removes the non 2 function. Fixes compilation there.

Signed-off-by: Rosen Penev <rosenp@gmail.com>

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1499
2022-04-26 17:57:48 +02:00
Thomas Guillem
4260e78861 android: add gdb.sh
This script setup a dummy android native app folder and call ndk-gdb from it.

It needs a modification in ANDROID_NDK since ndk-gdb may attach to the wrong
pid, cf. comments in the script.
2022-04-26 17:47:54 +02:00
Thomas Guillem
7342ae2e33 android: set application debuggable
This debuggable flag should not be set with release builds. Generally, graddle
is taking care of that.
2022-04-26 17:46:51 +02:00
Arsen Arsenović
35dbc1a90c mixer,output: prevent setting volume before outputs are really enabled
Previous versions of MPD would call SetVolume on enabled outputs before
they are ready, causing all of MPD to crash. Checking the really_enabled
flag prevents this, though it also prevents setting volume before the
player starts.

Before (with the PipeWire output):
  [i] ~$ mpc clear
  volume: 81%   repeat: off   random: off   single: off   consume: off
  [i] ~$ systemctl --user restart mpd.service
  [i] ~$ mpc volume 100
  MPD error: Connection closed by the server
  [i] ~ 1 $

After:
  [i] ~$ # mpd is freshly started w/o anything in the queue
  [i] ~$ mpc
  volume:100%   repeat: off   random: off   single: off   consume: off
  [i] ~$ mpc volume 80
  MPD error: problems setting volume
  [i] ~ 1 $ mpc
  volume:100%   repeat: off   random: off   single: off   consume: off
  [i] ~$
2022-04-26 17:45:29 +02:00
Arsen Arsenović
c7a4355153 outputs/pipewire: fix ParamChanged incorrectly setting volume
Previous versions of MPD would, on parameter change, set the PipeWire
volume before clearing the restore_volume flag, causing the call to
short circuit and do nothing. Instead, clear the flag before the call.
2022-04-26 17:44:19 +02:00
Max Kellermann
33a84a8ca2 output/shout: use shout_set_metadata_utf8() 2022-04-26 17:41:21 +02:00
Max Kellermann
1d04490ed3 output/shout: use shout_set_content_format() 2022-04-26 17:38:43 +02:00
Max Kellermann
4a30c2d79c output/shout: use shout_set_meta() 2022-04-26 17:24:49 +02:00
Max Kellermann
83072d6b9c output/shout: pass reference to Setup() 2022-04-26 16:49:18 +02:00
Max Kellermann
c779fc37eb output/shout: declare minimum version 2.4.0
This version was released 7 years ago, and it's reasonable to require
at least this version.
2022-04-26 16:46:36 +02:00
Max Kellermann
e08c13ad7e output/shout: add "noexcept" 2022-04-26 15:57:03 +02:00
Max Kellermann
2c82a6b2e0 output/shout: handle shout_metadata_add() errors
Fixes -Wunused-result
2022-04-26 15:56:55 +02:00
Max Kellermann
3929f17aef NEWS: mention the libiconv fix 2022-04-26 15:56:54 +02:00
Andreas Ziegler
ee39af3419 fix typo in comment 2022-04-24 04:14:17 +00:00
aeolio
3882a5a263 src/lib/icu: fix iconv() detection when libiconv is installed 2022-04-20 16:10:39 +02:00
Vitaly Ostrosablin
ac06088948 Make volume changes to apply to disabled software mixers.
Move audio output state check ahead of mixer check and force volume
applying even for disabled software mixed outputs.

This fixes incorrect software mixer volume that used to occur when
volume was changed while output being disabled.

This is easily reproduced with following sequence of commands on
multi-output software mixed MPD setup.

 mpc volume 38; mpc disable 3; mpc volume 88; mpc enable 3

On current MPD, following commands would result in output 3 playing at
volume 38, while all other enabled outputs would play at volume
88. Moreover, global volume would display average of outputs real
volumes. In my case, it's 75.

After applying this patch, following commands would produce expected
behavior. All outputs play at expected (88) volume. And volume is
correctly displayed as 88.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1423

Signed-off-by: Vitaly Ostrosablin tmp6154@yandex.ru


Signed-off-by: Vitaly Ostrosablin <tmp6154@yandex.ru>
2022-03-26 06:29:18 +01:00
Max Kellermann
a757eebfbb decoder/OggSyncState: allow skipping up to 64 kB after seek
This is more of what we did in commit 70bd35abe2 because it turns
out there are Ogg-Opus files with pages larger than 40 kB.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/1487
2022-03-16 16:54:50 +01:00
Max Kellermann
2be4f89555 test/DumpOgg: new debug program 2022-03-16 16:51:44 +01:00
Max Kellermann
4a5c7d8261 increment version number to 0.23.7 2022-03-14 18:55:55 +01:00
28 changed files with 385 additions and 116 deletions

13
NEWS
View File

@@ -1,3 +1,16 @@
ver 0.23.7 (2022/05/09)
* database
- upnp: support pupnp 1.14
* decoder
- ffmpeg: fix HLS seeking
- opus: fix missing song length on high-latency files
* output
- shout: require at least libshout 2.4.0
* mixer
- pipewire: fix volume restore
- software: update volume of disabled outputs
* support libiconv
ver 0.23.6 (2022/03/14) ver 0.23.6 (2022/03/14)
* protocol * protocol
- support filename "cover.webp" for "albumart" command - support filename "cover.webp" for "albumart" command

View File

@@ -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="65" android:versionCode="66"
android:versionName="0.23.5"> android:versionName="0.23.7">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
@@ -19,6 +19,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application android:allowBackup="true" <application android:allowBackup="true"
android:debuggable="true"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
android:icon="@drawable/icon" android:icon="@drawable/icon"
android:banner="@drawable/icon" android:banner="@drawable/icon"

54
android/gdb.sh Executable file
View File

@@ -0,0 +1,54 @@
#!/bin/sh
# This script need the following modification in ANDROID_NDK in order to attach
# to the good :main pid
#--- a/prebuilt/linux-x86_64/bin/ndk-gdb.py
#+++ b/prebuilt/linux-x86_64/bin/ndk-gdb.py
#@@ -669,7 +669,7 @@
# log("Sleeping for {} seconds.".format(args.delay))
# time.sleep(args.delay)
#
#- pids = gdbrunner.get_pids(device, pkg_name)
#+ pids = gdbrunner.get_pids(device, pkg_name + ":main")
# if len(pids) == 0:
# error("Failed to find running process '{}'".format(pkg_name))
# if len(pids) > 1:
SCRIPT_PATH=$(dirname $0)
BUILD_PATH="`pwd`"
TMP_PATH="$BUILD_PATH/gdb"
NDK_GDB_ARGS="--force"
ANDROID_NDK=$1
if [ ! -f $ANDROID_NDK/source.properties ];then
echo "usage: $0 ANDROID_NDK"
exit 1
fi
if [ ! -f $BUILD_PATH/libmpd.so ];then
echo "This script need to be executed from the android build directory"
exit 1
fi
rm -rf "$TMP_PATH"
mkdir -p "$TMP_PATH"
ANDROID_MANIFEST="$SCRIPT_PATH/AndroidManifest.xml"
ABI=`ls "$BUILD_PATH/android/apk/apk/lib" --sort=time | head -n 1`
if [ ! -f "$ANDROID_MANIFEST" -o "$ABI" = "" ]; then
echo "Invalid manifest/ABI, did you try building first ?"
exit 1
fi
mkdir -p "$TMP_PATH"/jni
touch "$TMP_PATH"/jni/Android.mk
echo "APP_ABI := $ABI" > "$TMP_PATH"/jni/Application.mk
DEST=obj/local/$ABI
mkdir -p "$TMP_PATH/$DEST"
cp "$BUILD_PATH/libmpd.so" "$TMP_PATH/$DEST"
cp "$ANDROID_MANIFEST" "$TMP_PATH"
(cd "$TMP_PATH" && bash $ANDROID_NDK/ndk-gdb $NDK_GDB_ARGS)

View File

@@ -38,7 +38,7 @@ author = 'Max Kellermann'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '0.23.6' version = '0.23.7'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
#release = version + '~git' #release = version + '~git'

View File

@@ -545,6 +545,13 @@ Playback options
Sets repeat state to ``STATE``, Sets repeat state to ``STATE``,
``STATE`` should be 0 or 1. ``STATE`` should be 0 or 1.
If enabled, MPD keeps repeating the whole queue (:ref:`single mode
<command_single>` disabled) or the current song (:ref:`single mode
<command_single>` enabled).
If :ref:`random mode <command_random>` is also enabled, the
playback order will be shuffled each time the queue gets repeated.
.. _command_setvol: .. _command_setvol:
:command:`setvol {VOL}` :command:`setvol {VOL}`

View File

@@ -1,7 +1,7 @@
project( project(
'mpd', 'mpd',
['c', 'cpp'], ['c', 'cpp'],
version: '0.23.6', version: '0.23.7',
meson_version: '>= 0.56.0', meson_version: '>= 0.56.0',
default_options: [ default_options: [
'c_std=c11', 'c_std=c11',

View File

@@ -55,8 +55,8 @@ flac = AutotoolsProject(
) )
zlib = ZlibProject( zlib = ZlibProject(
'http://zlib.net/zlib-1.2.11.tar.xz', 'http://zlib.net/zlib-1.2.12.tar.xz',
'4ff941449631ace0d4d203e3483be9dbc9da454084111f97ea0a2114e19bf066', '7db46b8d7726232a621befaab4a1c870f00a90805511c0e0090441dac57def18',
'lib/libz.a', 'lib/libz.a',
) )
@@ -151,8 +151,8 @@ gme = CmakeProject(
) )
ffmpeg = FfmpegProject( ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-5.0.tar.xz', 'http://ffmpeg.org/releases/ffmpeg-5.0.1.tar.xz',
'51e919f7d205062c0fd4fae6243a84850391115104ccf1efc451733bc0ac7298', 'ef2efae259ce80a240de48ec85ecb062cecca26e4352ffb3fda562c21a93007b',
'lib/libavcodec.a', 'lib/libavcodec.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
@@ -380,14 +380,14 @@ ffmpeg = FfmpegProject(
) )
openssl = OpenSSLProject( openssl = OpenSSLProject(
'https://www.openssl.org/source/openssl-3.0.1.tar.gz', 'https://www.openssl.org/source/openssl-3.0.3.tar.gz',
'c311ad853353bce796edad01a862c50a8a587f62e7e2100ef465ab53ec9b06d1', '9384a2b0570dd80358841464677115df785edb941c71211f75076d72fe6b438f',
'include/openssl/ossl_typ.h', 'include/openssl/ossl_typ.h',
) )
curl = CmakeProject( curl = CmakeProject(
'https://curl.se/download/curl-7.82.0.tar.xz', 'https://curl.se/download/curl-7.83.0.tar.xz',
'0aaa12d7bd04b0966254f2703ce80dd5c38dbbd76af0297d3d690cdce58a583c', 'bbff0e6b5047e773f3c3b084d80546cc1be4e354c09e419c2d0ef6116253511a',
'lib/libcurl.a', 'lib/libcurl.a',
[ [
'-DBUILD_CURL_EXE=OFF', '-DBUILD_CURL_EXE=OFF',
@@ -444,7 +444,7 @@ jack = JackProject(
) )
boost = BoostProject( boost = BoostProject(
'https://boostorg.jfrog.io/artifactory/main/release/1.78.0/source/boost_1_78_0.tar.bz2', 'https://boostorg.jfrog.io/artifactory/main/release/1.79.0/source/boost_1_79_0.tar.bz2',
'8681f175d4bdb26c52222665793eef08490d7758529330f98d3b29dd0735bccc', '475d589d51a7f8b3ba2ba4eda022b170e562ca3b760ee922c146b6c65856ef39',
'include/boost/version.hpp', 'include/boost/version.hpp',
) )

View File

@@ -384,7 +384,8 @@ static void
FfmpegParseMetaData(const AVStream &stream, FfmpegParseMetaData(const AVStream &stream,
ReplayGainInfo &rg, MixRampInfo &mr) ReplayGainInfo &rg, MixRampInfo &mr)
{ {
FfmpegParseMetaData(*stream.metadata, rg, mr); if (stream.metadata != nullptr)
FfmpegParseMetaData(*stream.metadata, rg, mr);
} }
static void static void
@@ -393,7 +394,9 @@ FfmpegParseMetaData(const AVFormatContext &format_context, int audio_stream,
{ {
assert(audio_stream >= 0); assert(audio_stream >= 0);
FfmpegParseMetaData(*format_context.metadata, rg, mr); if (format_context.metadata != nullptr)
FfmpegParseMetaData(*format_context.metadata, rg, mr);
FfmpegParseMetaData(*format_context.streams[audio_stream], FfmpegParseMetaData(*format_context.streams[audio_stream],
rg, mr); rg, mr);
} }
@@ -468,7 +471,7 @@ static bool
IsSeekable(const AVFormatContext &format_context) noexcept IsSeekable(const AVFormatContext &format_context) noexcept
{ {
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 6, 100) #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 6, 100)
return (format_context.ctx_flags & AVFMTCTX_UNSEEKABLE) != 0; return (format_context.ctx_flags & AVFMTCTX_UNSEEKABLE) == 0;
#else #else
(void)format_context; (void)format_context;
return false; return false;
@@ -530,9 +533,8 @@ FfmpegDecode(DecoderClient &client, InputStream *input,
: FromFfmpegTimeChecked(format_context.duration, AV_TIME_BASE_Q); : FromFfmpegTimeChecked(format_context.duration, AV_TIME_BASE_Q);
client.Ready(audio_format, client.Ready(audio_format,
input (input ? input->IsSeekable() : false)
? input->IsSeekable() || IsSeekable(format_context),
: IsSeekable(format_context),
total_time); total_time);
FfmpegParseMetaData(client, format_context, audio_stream); FfmpegParseMetaData(client, format_context, audio_stream);

View File

@@ -3,6 +3,23 @@ encoder_features = configuration_data()
encoder_features.set('ENABLE_ENCODER', need_encoder) encoder_features.set('ENABLE_ENCODER', need_encoder)
if not need_encoder if not need_encoder
if need_wave_encoder
# Special case for the Snapcast output plugin which only needs the
# PCM wave encoder encoder plugin
encoder_glue = static_library(
'encoder_glue',
'plugins/WaveEncoderPlugin.cxx',
include_directories: inc,
)
encoder_glue_dep = declare_dependency(
link_with: encoder_glue,
)
configure_file(output: 'Features.h', configuration: encoder_features)
subdir_done()
endif
encoder_glue_dep = dependency('', required: false) encoder_glue_dep = dependency('', required: false)
configure_file(output: 'Features.h', configuration: encoder_features) configure_file(output: 'Features.h', configuration: encoder_features)
subdir_done() subdir_done()

View File

@@ -35,7 +35,7 @@ if libshine_dep.found()
endif endif
encoder_features.set('ENABLE_WAVE_ENCODER', get_option('wave_encoder')) encoder_features.set('ENABLE_WAVE_ENCODER', get_option('wave_encoder'))
if get_option('wave_encoder') if get_option('wave_encoder') or need_wave_encoder
encoder_plugins_sources += 'WaveEncoderPlugin.cxx' encoder_plugins_sources += 'WaveEncoderPlugin.cxx'
endif endif

View File

@@ -12,17 +12,23 @@ if is_windows
icu_sources += 'Win32.cxx' icu_sources += 'Win32.cxx'
endif endif
iconv_dep = []
if icu_dep.found() if icu_dep.found()
icu_sources += [ icu_sources += [
'Util.cxx', 'Util.cxx',
'Init.cxx', 'Init.cxx',
] ]
elif not get_option('iconv').disabled() elif not get_option('iconv').disabled()
have_iconv = compiler.has_function('iconv', prefix : '#include <iconv.h>') # an installed iconv library will make the builtin iconv() unavailable,
conf.set('HAVE_ICONV', have_iconv) # so search for the library first and pass it as (possible) dependency
iconv_dep = compiler.find_library('libiconv', required: false)
have_iconv = compiler.has_function('iconv',
dependencies: iconv_dep,
prefix : '#include <iconv.h>')
if not have_iconv and get_option('iconv').enabled() if not have_iconv and get_option('iconv').enabled()
error('iconv() not available') error('iconv() not available')
endif endif
conf.set('HAVE_ICONV', have_iconv)
endif endif
icu = static_library( icu = static_library(
@@ -31,6 +37,7 @@ icu = static_library(
include_directories: inc, include_directories: inc,
dependencies: [ dependencies: [
icu_dep, icu_dep,
iconv_dep,
fmt_dep, fmt_dep,
], ],
) )

View File

@@ -35,13 +35,7 @@ static unsigned upnp_ref;
static void static void
DoInit(const char* iface) DoInit(const char* iface)
{ {
if (auto code = UpnpInit2(iface, 0); code != UPNP_E_SUCCESS)
#ifdef UPNP_ENABLE_IPV6
auto code = UpnpInit2(iface, 0);
#else
auto code = UpnpInit(iface, 0);
#endif
if (code != UPNP_E_SUCCESS)
throw FormatRuntimeError("UpnpInit() failed: %s", throw FormatRuntimeError("UpnpInit() failed: %s",
UpnpGetErrorMessage(code)); UpnpGetErrorMessage(code));

View File

@@ -67,7 +67,7 @@ OggSyncState::ExpectPageIn(ogg_stream_state &os)
bool bool
OggSyncState::ExpectPageSeek(ogg_page &page) OggSyncState::ExpectPageSeek(ogg_page &page)
{ {
size_t remaining_skipped = 32768; size_t remaining_skipped = 65536;
while (true) { while (true) {
int r = ogg_sync_pageseek(&oy, &page); int r = ogg_sync_pageseek(&oy, &page);

View File

@@ -34,13 +34,15 @@ gcc_pure
static int static int
output_mixer_get_volume(const AudioOutputControl &ao) noexcept output_mixer_get_volume(const AudioOutputControl &ao) noexcept
{ {
if (!ao.IsEnabled())
return -1;
auto *mixer = ao.GetMixer(); auto *mixer = ao.GetMixer();
if (mixer == nullptr) if (mixer == nullptr)
return -1; return -1;
/* software mixers are always considered, even if they are
disabled */
if (!ao.IsEnabled() && !mixer->IsPlugin(software_mixer_plugin))
return -1;
try { try {
return mixer_get_volume(mixer); return mixer_get_volume(mixer);
} catch (...) { } catch (...) {
@@ -76,13 +78,15 @@ output_mixer_set_volume(AudioOutputControl &ao, unsigned volume) noexcept
{ {
assert(volume <= 100); assert(volume <= 100);
if (!ao.IsEnabled())
return false;
auto *mixer = ao.GetMixer(); auto *mixer = ao.GetMixer();
if (mixer == nullptr) if (mixer == nullptr)
return false; return false;
/* software mixers are always updated, even if they are
disabled */
if (!ao.IsReallyEnabled() && !mixer->IsPlugin(software_mixer_plugin))
return false;
try { try {
mixer_set_volume(mixer, volume); mixer_set_volume(mixer, volume);
return true; return true;

View File

@@ -288,6 +288,13 @@ public:
return !output; return !output;
} }
/**
* Caller must lock the mutex.
*/
bool IsReallyEnabled() const noexcept {
return really_enabled;
}
/** /**
* Caller must lock the mutex. * Caller must lock the mutex.
*/ */

View File

@@ -638,8 +638,8 @@ PipeWireOutput::ParamChanged([[maybe_unused]] uint32_t id,
[[maybe_unused]] const struct spa_pod *param) noexcept [[maybe_unused]] const struct spa_pod *param) noexcept
{ {
if (restore_volume) { if (restore_volume) {
SetVolume(volume);
restore_volume = false; restore_volume = false;
SetVolume(volume);
} }
#if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE) #if defined(ENABLE_DSD) && defined(SPA_AUDIO_DSD_FLAG_NONE)

View File

@@ -59,7 +59,7 @@ class ShoutConfig {
public: public:
ShoutConfig(const ConfigBlock &block, const char *mime_type); ShoutConfig(const ConfigBlock &block, const char *mime_type);
void Setup(shout_t *connection) const; void Setup(shout_t &connection) const;
}; };
struct ShoutOutput final : AudioOutput { struct ShoutOutput final : AudioOutput {
@@ -229,41 +229,56 @@ ShoutOutput::Create(EventLoop &, const ConfigBlock &block)
return new ShoutOutput(block); return new ShoutOutput(block);
} }
inline void static void
ShoutConfig::Setup(shout_t *connection) const SetMeta(shout_t &connection, const char *name, const char *value)
{ {
if (shout_set_host(connection, host) != SHOUTERR_SUCCESS || if (shout_set_meta(&connection, name, value) != SHOUTERR_SUCCESS)
shout_set_port(connection, port) != SHOUTERR_SUCCESS || throw std::runtime_error(shout_get_error(&connection));
shout_set_password(connection, passwd) != SHOUTERR_SUCCESS || }
shout_set_mount(connection, mount) != SHOUTERR_SUCCESS ||
shout_set_name(connection, name) != SHOUTERR_SUCCESS || static void
shout_set_user(connection, user) != SHOUTERR_SUCCESS || SetOptionalMeta(shout_t &connection, const char *name, const char *value)
shout_set_public(connection, is_public) != SHOUTERR_SUCCESS || {
shout_set_format(connection, format) != SHOUTERR_SUCCESS || if (value != nullptr)
shout_set_protocol(connection, protocol) != SHOUTERR_SUCCESS || SetMeta(connection, name, value);
#ifdef SHOUT_TLS }
shout_set_tls(connection, tls) != SHOUTERR_SUCCESS ||
inline void
ShoutConfig::Setup(shout_t &connection) const
{
if (shout_set_host(&connection, host) != SHOUTERR_SUCCESS ||
shout_set_port(&connection, port) != SHOUTERR_SUCCESS ||
shout_set_password(&connection, passwd) != SHOUTERR_SUCCESS ||
shout_set_mount(&connection, mount) != SHOUTERR_SUCCESS ||
shout_set_user(&connection, user) != SHOUTERR_SUCCESS ||
shout_set_public(&connection, is_public) != SHOUTERR_SUCCESS ||
#ifdef SHOUT_USAGE_AUDIO
/* since libshout 2.4.3 */
shout_set_content_format(&connection, format, SHOUT_USAGE_AUDIO,
nullptr) != SHOUTERR_SUCCESS ||
#else
shout_set_format(&connection, format) != SHOUTERR_SUCCESS ||
#endif #endif
shout_set_agent(connection, "MPD") != SHOUTERR_SUCCESS) shout_set_protocol(&connection, protocol) != SHOUTERR_SUCCESS ||
throw std::runtime_error(shout_get_error(connection)); #ifdef SHOUT_TLS
shout_set_tls(&connection, tls) != SHOUTERR_SUCCESS ||
#endif
shout_set_agent(&connection, "MPD") != SHOUTERR_SUCCESS)
throw std::runtime_error(shout_get_error(&connection));
SetMeta(connection, SHOUT_META_NAME, name);
/* optional paramters */ /* optional paramters */
if (genre != nullptr && shout_set_genre(connection, genre)) SetOptionalMeta(connection, SHOUT_META_GENRE, genre);
throw std::runtime_error(shout_get_error(connection)); SetOptionalMeta(connection, SHOUT_META_DESCRIPTION, description);
SetOptionalMeta(connection, SHOUT_META_URL, url);
if (description != nullptr &&
shout_set_description(connection, description))
throw std::runtime_error(shout_get_error(connection));
if (url != nullptr && shout_set_url(connection, url))
throw std::runtime_error(shout_get_error(connection));
if (quality != nullptr) if (quality != nullptr)
shout_set_audio_info(connection, SHOUT_AI_QUALITY, quality); shout_set_audio_info(&connection, SHOUT_AI_QUALITY, quality);
if (bitrate != nullptr) if (bitrate != nullptr)
shout_set_audio_info(connection, SHOUT_AI_BITRATE, bitrate); shout_set_audio_info(&connection, SHOUT_AI_BITRATE, bitrate);
} }
void void
@@ -274,7 +289,7 @@ ShoutOutput::Enable()
throw std::bad_alloc{}; throw std::bad_alloc{};
try { try {
config.Setup(shout_conn); config.Setup(*shout_conn);
} catch (...) { } catch (...) {
shout_free(shout_conn); shout_free(shout_conn);
throw; throw;
@@ -418,7 +433,7 @@ ShoutOutput::Pause()
} }
static void static void
shout_tag_to_metadata(const Tag &tag, char *dest, size_t size) shout_tag_to_metadata(const Tag &tag, char *dest, size_t size) noexcept
{ {
const char *artist = tag.GetValue(TAG_ARTIST); const char *artist = tag.GetValue(TAG_ARTIST);
const char *title = tag.GetValue(TAG_TITLE); const char *title = tag.GetValue(TAG_TITLE);
@@ -446,9 +461,15 @@ ShoutOutput::SendTag(const Tag &tag)
char song[1024]; char song[1024];
shout_tag_to_metadata(tag, song, sizeof(song)); shout_tag_to_metadata(tag, song, sizeof(song));
shout_metadata_add(meta, "song", song); if (SHOUTERR_SUCCESS != shout_metadata_add(meta, "song", song) ||
shout_metadata_add(meta, "charset", "UTF-8"); #ifdef SHOUT_FORMAT_TEXT
if (SHOUTERR_SUCCESS != shout_set_metadata(shout_conn, meta)) { /* since libshout 2.4.6 */
SHOUTERR_SUCCESS != shout_set_metadata_utf8(shout_conn, meta)
#else
SHOUTERR_SUCCESS != shout_metadata_add(meta, "charset", "UTF-8") ||
SHOUTERR_SUCCESS != shout_set_metadata(shout_conn, meta)
#endif
) {
LogWarning(shout_output_domain, LogWarning(shout_output_domain,
"error setting shout metadata"); "error setting shout metadata");
} }

View File

@@ -10,6 +10,7 @@ output_plugins_deps = [
] ]
need_encoder = false need_encoder = false
need_wave_encoder = false
if alsa_dep.found() if alsa_dep.found()
output_plugins_sources += 'AlsaOutputPlugin.cxx' output_plugins_sources += 'AlsaOutputPlugin.cxx'
@@ -99,7 +100,7 @@ if get_option('recorder')
need_encoder = true need_encoder = true
endif endif
libshout_dep = dependency('shout', required: get_option('shout')) libshout_dep = dependency('shout', version: '>= 2.4.0', required: get_option('shout'))
output_features.set('HAVE_SHOUT', libshout_dep.found()) output_features.set('HAVE_SHOUT', libshout_dep.found())
if libshout_dep.found() if libshout_dep.found()
output_plugins_sources += 'ShoutOutputPlugin.cxx' output_plugins_sources += 'ShoutOutputPlugin.cxx'
@@ -127,9 +128,7 @@ if get_option('snapcast')
output_features.set('HAVE_YAJL', yajl_dep.found()) output_features.set('HAVE_YAJL', yajl_dep.found())
# TODO: the Snapcast plugin needs just the "wave" encoder, but this need_wave_encoder = true
# enables all available encoders
need_encoder = true
endif endif
enable_solaris_output = get_option('solaris_output') enable_solaris_output = get_option('solaris_output')

View File

@@ -73,10 +73,10 @@ try {
unsigned n = FromLE32(footer.count); unsigned n = FromLE32(footer.count);
const char *p = buffer.get(); const char *p = buffer.get();
while (n-- && remaining > 10) { while (n-- && remaining > 10) {
size_t size = FromLE32(*(const uint32_t *)p); size_t size = *(const PackedLE32 *)p;
p += 4; p += 4;
remaining -= 4; remaining -= 4;
unsigned long flags = FromLE32(*(const uint32_t *)p); unsigned long flags = *(const PackedLE32 *)p;
p += 4; p += 4;
remaining -= 4; remaining -= 4;

View File

@@ -32,7 +32,7 @@ ReadString(ConstBuffer<uint8_t> &src) noexcept
if (src.size < 4) if (src.size < 4)
return nullptr; return nullptr;
const size_t length = FromBE32(*(const uint32_t *)src.data); const size_t length = *(const PackedBE32 *)src.data;
src.skip_front(4); src.skip_front(4);
if (src.size < length) if (src.size < length)
@@ -65,7 +65,7 @@ ScanId3Apic(ConstBuffer<void> _buffer, TagHandler &handler) noexcept
buffer.skip_front(16); buffer.skip_front(16);
const size_t image_size = FromBE32(*(const uint32_t *)buffer.data); const size_t image_size = *(const PackedBE32 *)buffer.data;
buffer.skip_front(4); buffer.skip_front(4);
if (buffer.size < image_size) if (buffer.size < image_size)

View File

@@ -297,6 +297,60 @@ public:
static_assert(sizeof(PackedBE16) == sizeof(uint16_t), "Wrong size"); static_assert(sizeof(PackedBE16) == sizeof(uint16_t), "Wrong size");
static_assert(alignof(PackedBE16) == 1, "Wrong alignment"); static_assert(alignof(PackedBE16) == 1, "Wrong alignment");
/**
* A packed big-endian 32 bit integer.
*/
class PackedBE32 {
uint8_t a, b, c, d;
public:
PackedBE32() = default;
constexpr PackedBE32(uint32_t src) noexcept
:a(uint8_t(src >> 24)),
b(uint8_t(src >> 16)),
c(uint8_t(src >> 8)),
d(uint8_t(src)) {}
/**
* Construct an instance from an integer which is already
* big-endian.
*/
static constexpr auto FromBE(uint32_t src) noexcept {
union {
uint32_t in;
PackedBE32 out;
} u{src};
return u.out;
}
constexpr operator uint32_t() const noexcept {
return (uint32_t(a) << 24) | (uint32_t(b) << 16) |
(uint32_t(c) << 8) | uint32_t(d);
}
PackedBE32 &operator=(uint32_t new_value) noexcept {
d = uint8_t(new_value);
c = uint8_t(new_value >> 8);
b = uint8_t(new_value >> 16);
a = uint8_t(new_value >> 24);
return *this;
}
/**
* Reads the raw, big-endian value.
*/
constexpr uint32_t raw() const noexcept {
uint32_t x = *this;
if (IsLittleEndian())
x = ByteSwap32(x);
return x;
}
};
static_assert(sizeof(PackedBE32) == sizeof(uint32_t), "Wrong size");
static_assert(alignof(PackedBE32) == 1, "Wrong alignment");
/** /**
* A packed little-endian 16 bit integer. * A packed little-endian 16 bit integer.
*/ */

View File

@@ -1,9 +1,12 @@
[wrap-file] [wrap-file]
directory = expat-2.2.9 directory = expat-2.4.8
source_url = https://github.com/libexpat/libexpat/releases/download/R_2_2_9/expat-2.2.9.tar.xz source_url = https://github.com/libexpat/libexpat/releases/download/R_2_4_8/expat-2.4.8.tar.xz
source_filename = expat-2.2.9.tar.bz2 source_filename = expat-2.4.8.tar.bz2
source_hash = 1ea6965b15c2106b6bbe883397271c80dfa0331cdf821b2c319591b55eadc0a4 source_hash = f79b8f904b749e3e0d20afeadecf8249c55b2e32d4ebb089ae378df479dcaf25
patch_url = https://wrapdb.mesonbuild.com/v1/projects/expat/2.2.9/3/get_zip patch_filename = expat_2.4.8-1_patch.zip
patch_filename = expat-2.2.9-3-wrap.zip patch_url = https://wrapdb.mesonbuild.com/v2/expat_2.4.8-1/get_patch
patch_hash = e9aaace62e9a158b5e96f5c38c9f81f369179206acd87697653d777c0d3975d3 patch_hash = 9aec253a2c6d1c0feb852c5c6920298d14701eeec7acc6832bb402438b52112a
[provide]
expat = expat_dep

View File

@@ -1,11 +1,11 @@
[wrap-file] [wrap-file]
directory = fmt-7.1.3 directory = fmt-8.1.1
source_url = https://github.com/fmtlib/fmt/archive/7.1.3.tar.gz source_url = https://github.com/fmtlib/fmt/archive/8.1.1.tar.gz
source_filename = fmt-7.1.3.tar.gz source_filename = fmt-8.1.1.tar.gz
source_hash = 5cae7072042b3043e12d53d50ef404bbb76949dad1de368d7f993a15c8c05ecc source_hash = 3d794d3cf67633b34b2771eb9f073bde87e846e0d395d254df7b211ef1ec7346
patch_url = https://wrapdb.mesonbuild.com/v1/projects/fmt/7.1.3/1/get_zip patch_filename = fmt_8.1.1-1_patch.zip
patch_filename = fmt-7.1.3-1-wrap.zip patch_url = https://wrapdb.mesonbuild.com/v2/fmt_8.1.1-1/get_patch
patch_hash = 6eb951a51806fd6ffd596064825c39b844c1fe1799840ef507b61a53dba08213 patch_hash = 6035a67c7a8c90bed74c293c7265c769f47a69816125f7566bccb8e2543cee5e
[provide] [provide]
fmt = fmt_dep fmt = fmt_dep

View File

@@ -1,10 +1,15 @@
[wrap-file] [wrap-file]
directory = googletest-release-1.10.0 directory = googletest-release-1.11.0
source_url = https://github.com/google/googletest/archive/release-1.11.0.tar.gz
source_filename = gtest-1.11.0.tar.gz
source_hash = b4870bf121ff7795ba20d20bcdd8627b8e088f2d1dab299a031c1034eddc93d5
patch_filename = gtest_1.11.0-2_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/gtest_1.11.0-2/get_patch
patch_hash = 764530d812ac161c9eab02a8cfaec67c871fcfc5548e29fd3d488070913d4e94
source_url = https://github.com/google/googletest/archive/release-1.10.0.zip [provide]
source_filename = gtest-1.10.0.zip gtest = gtest_dep
source_hash = 94c634d499558a76fa649edb13721dce6e98fb1e7018dfaeba3cd7a083945e91 gtest_main = gtest_main_dep
gmock = gmock_dep
gmock_main = gmock_main_dep
patch_url = https://wrapdb.mesonbuild.com/v1/projects/gtest/1.10.0/1/get_zip
patch_filename = gtest-1.10.0-1-wrap.zip
patch_hash = 04ff14e8880e4e465f6260221e9dfd56fea6bc7cce4c4aff0dc528e4a2c8f514

View File

@@ -1,11 +1,11 @@
[wrap-file] [wrap-file]
directory = sqlite-amalgamation-3340100 directory = sqlite-amalgamation-3380000
source_url = https://www.sqlite.org/2021/sqlite-amalgamation-3340100.zip source_url = https://sqlite.org/2022/sqlite-amalgamation-3380000.zip
source_filename = sqlite-amalgamation-3340100.zip source_filename = sqlite-amalgamation-3380000.zip
source_hash = e0b1c0345fe4338b936e17da8e1bd88366cd210e576834546977f040c12a8f68 source_hash = e055f6054e97747a135c89e36520c0a423249e8a91c5fc445163f4a6adb20df6
patch_url = https://wrapdb.mesonbuild.com/v1/projects/sqlite3/3.34.1/1/get_zip patch_filename = sqlite3_3.38.0-1_patch.zip
patch_filename = sqlite3-3.34.1-1-wrap.zip patch_url = https://wrapdb.mesonbuild.com/v2/sqlite3_3.38.0-1/get_patch
patch_hash = cba9e47bdb4c02f88fadaae8deab357218d32562c6b86ce7ba0c72f107044360 patch_hash = 49e30bf010ff63ab772d5417885e6905379025ceac80382e292c6dbd3a9da744
[provide] [provide]
sqlite3 = sqlite3_dep sqlite3 = sqlite3_dep

View File

@@ -1,11 +1,11 @@
[wrap-file] [wrap-file]
directory = libvorbis-1.3.5 directory = libvorbis-1.3.7
source_url = http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.5.tar.xz source_url = https://downloads.xiph.org/releases/vorbis/libvorbis-1.3.7.tar.xz
source_filename = libvorbis-1.3.5.tar.xz source_filename = libvorbis-1.3.7.tar.xz
source_hash = 54f94a9527ff0a88477be0a71c0bab09a4c3febe0ed878b24824906cd4b0e1d1 source_hash = b33cc4934322bcbf6efcbacf49e3ca01aadbea4114ec9589d1b1e9d20f72954b
patch_url = https://wrapdb.mesonbuild.com/v1/projects/vorbis/1.3.5/7/get_zip patch_filename = vorbis_1.3.7-2_patch.zip
patch_filename = vorbis-1.3.5-7-wrap.zip patch_url = https://wrapdb.mesonbuild.com/v2/vorbis_1.3.7-2/get_patch
patch_hash = 7f4d3f9253925196461d52fd4553aad4468fd845560d1ff6c2eb6a012cf64fb0 patch_hash = fe302576cbf8408754b332b539ea1b83f0f96fa9aae50a5d1fea911713d5f21c
[provide] [provide]
vorbis = vorbis_dep vorbis = vorbis_dep

68
test/DumpOgg.cxx Normal file
View File

@@ -0,0 +1,68 @@
/*
* Copyright 2003-2022 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 "lib/xiph/OggSyncState.hxx"
#include "lib/xiph/OggStreamState.hxx"
#include "config/Data.hxx"
#include "input/Init.hxx"
#include "input/InputStream.hxx"
#include "input/Reader.hxx"
#include "event/Thread.hxx"
#include "util/PrintException.hxx"
#include <inttypes.h>
#include <stdio.h>
int
main(int argc, char **argv) noexcept
try {
if (argc != 2) {
fprintf(stderr, "Usage: DumpOgg FILE\n");
return EXIT_FAILURE;
}
const char *path = argv[1];
EventThread io_thread;
io_thread.Start();
const ScopeInputPluginsInit input_plugins_init(ConfigData(),
io_thread.GetEventLoop());
Mutex mutex;
auto is = InputStream::OpenReady(path, mutex);
InputStreamReader reader{*is};
OggSyncState sync{reader};
while (true) {
ogg_page page;
if (!sync.ExpectPage(page))
break;
printf("page offset=%" PRIu64 " serial=%d\n",
sync.GetStartOffset(), ogg_page_serialno(&page));
}
return EXIT_SUCCESS;
} catch (...) {
PrintException(std::current_exception());
return EXIT_FAILURE;
}

View File

@@ -490,6 +490,19 @@ if libid3tag_dep.found()
], ],
) )
endif endif
if ogg_dep.found()
executable(
'DumpOgg',
'DumpOgg.cxx',
include_directories: inc,
dependencies: [
ogg_dep,
input_glue_dep,
archive_glue_dep,
],
)
endif
# #
# Filter # Filter
@@ -559,7 +572,7 @@ executable(
# Encoder # Encoder
# #
if encoder_glue_dep.found() if need_encoder
executable( executable(
'run_encoder', 'run_encoder',
'run_encoder.cxx', 'run_encoder.cxx',