diff --git a/Makefile.in b/Makefile.in index e7f44c098c4efdcf32b71a169a9303c967c00fef..67b199bbc822739c7662ffceb4d4ec031d045856 100644 --- a/Makefile.in +++ b/Makefile.in @@ -38,7 +38,7 @@ gdb: $(MAKE) run DBG=gdb valgrind: - $(MAKE) run DBG="DISABLE_RTKIT=1 valgrind --trace-children=yes" + $(MAKE) run DBG="DISABLE_RTKIT=1 PIPEWIRE_DLCLOSE=false valgrind --trace-children=yes" test: all ninja -C $(BUILD_ROOT) test diff --git a/NEWS b/NEWS index f2c768108514ee639799e07b6ae68111d1b2720b..971e31271b666e8fd0ed13a161adabb81e0fbce1 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,121 @@ +# PipeWire 0.3.63 (2022-12-15) + +This is a quick bugfix release that is API and ABI compatible with previous +0.3.x releases. + +## Highlights + - Fix a critical bug that causes audio distortion in some cases when using + AVX2. + - Fix a crash in mpv caused by deinit of PipeWire. + - Resample the convolver IR to match the graph samplerate for better + results. + - Many more small bugfixes and improvements. + + +## PipeWire + - Fix a segfault in the PipeWire deinit code triggered by mpv in some + cases. (#2881) + - Fix docs about SPA_PLUGIN_DIR. + - Always dlclose by default (even under valgrind). Add an option with + PIPEWIRE_DLCLOSE to select alternative behaviour. + - Improve PIPEWIRE_DEBUG category handling. + +## modules + - Resample the IR for the convolver when the IR samplerate and graph rate + don't match. + +## SPA + - Handle spurious reads from timerfd gracefully. + - Fix potential stack-use-after-scope when starting Audacity. + - Fix distorted audio when using AVX2. (#2885) + - Remove fallback to default channel map in channelmix. + - Improve sorting of MIDI events, use the same order as Ardour. (#1816) + - Enable LFE downmixing by default. (#2425) + - Make IEC958/AC3 and IEC958/DTS work better by enforcing a fixed minimal + buffering for the encoder to avoid stuttering. (#2650) + +## Pulse-Server + - Add a new pulse.cmd config section to execute pulse commands, currently + only for loading modules. This removes the dependency on pactl. + - Improve debug of messages. + + +Older versions: + +# PipeWire 0.3.62 (2022-12-09) + +This is a bugfix release that is API and ABI compatible with previous +0.3.x releases. + +## Highlights + - A regression in screensharing was fixed. It was caused by a race when + activating links and driver nodes. + - Video transform metadata was added so that cameras and screen sharing + can report the video orientation and transformations. + - Support for the PulseAudio module-gsettings was added to make paprefs + work. + - Support for bluetooth offloading was added. This allows for the bluetooth + reception, decoding and playback to happen completely in hardware. + This also requires some support in WirePlumber. + - Many bugfixes and improvements. + + +## PipeWire + - More work on stopping nodes in a more controlled way. + - Fix a race in starting nodes and drivers. In some cases the driver + node would already be started while the link to the peer node was not + ready yet. This caused regressions in screen sharing. The driver is + now only started after all the followers and links completed. + - Fix a case where a slow capture stream would not recycle buffers + anymore and stall. (#2874) + - Fix a subtle bug in pw_loop_invoke that could cause callbacks to be + delayed and cause crashes in some cases. + - Fix a case where IPC was done from the data-thread and could cause + crashes. + +## Tools + - Silence some expected errors in the pw-top output. + +## modules + - The filter-chain has seen some optimizations in the copy plugin and + the convolver. + - The zeroconf plugin will now only unpublish services from the server + that was removed. + - Fix a potential crash when stopping pw-loopback. + - Some harmless errors were turned into info messages. + - Fix some cases where pw_stream methods were called from the data-thread + that could cause segfaults. (#2633) + +## SPA + - There is now a video transform metadata that indicates how a video + frame was transformed (rotated/flipped). libcamera and the GStreamer + elements now have support for this metadata. + - The SPA volume plugin is now disabled from the default build. + - Handle missing control info in libcamera. + - Handle errors from loop better, don't call the callbacks on errors. + - Somewhat improve performance in some audioconvert AVX2 code for format + conversion. + - Fix PortConfig and EnumPortConfig params in audioconvert and + audioadapter to reflect what is actually going on instead of using + hardcoded values. + - Pass ignore-dB property correctly in all cases. + - Probing is now done in 48KHz again. (#2857) + +## Pulse-server + - IPv4 addresses are now added first to the list and exposed first with + zeroconf discover. + - module-gsettings was added to make paprefs work. + - The pulse.idle.timeout option was disabled by default and only enabled + for selected apps (speech-dispatcher) because it caused some problems + for other apps. (#2880) + +## JACK + - Only process valid ports. Could fix some crashes. (#2863) + +## Bluetooth + - Support was added for offloading bluetooth handling. Some hardware can + receive, decode and play the bluetooth audio directly in hardware. + # PipeWire 0.3.61 (2022-11-24) This is a bugfix release that is API and ABI compatible with previous @@ -65,9 +183,6 @@ This is a bugfix release that is API and ABI compatible with previous - Add option to set node.passive on jack clients. Make some quirks for qsynth to make it suspend and fade out better. - -Older versions: - # PipeWire 0.3.60 (2022-11-10) This is a bugfix release that is API and ABI compatible with previous diff --git a/debian/changelog b/debian/changelog index 758f8ddcda3ac31cababbedfe9bacf2f2666913c..e6d1fdaec3a56b83eb54c4b2a8cbf684355daa7e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,36 @@ +pipewire (0.3.63-1~bpo11+1) bullseye-backports; urgency=medium + + * Rebuild for bullseye-backports. + * Disable XFixes, minimum required version not available in Bullseye + * Disable libcamera, not available in Bullseye + * Disable liblc3, not available in Bullseye + * Do not mark pipewire-alsa in conflict with pulseaudio + This will break dependencies in Bullseye. + + -- Dylan Aïssi <daissi@debian.org> Tue, 03 Jan 2023 11:31:01 +0100 + +pipewire (0.3.63-1) unstable; urgency=medium + + * New upstream release + - pw-mon: recognize -N and -C as valid options (Closes: #1025900) + * Update symbols file + * Re-enable libcamera plugin for x32 + + -- Dylan Aïssi <daissi@debian.org> Thu, 15 Dec 2022 12:07:57 +0100 + +pipewire (0.3.62-1) unstable; urgency=medium + + * New upstream release + * Don't build the legacy volume SPA plugin, as per upstream: this + volume plugin was an experiment that's not really used anywhere. + * Disable libcamera plugin for m68k and x32, + because the libcamera minimum version is not available. + * Install upstream NEWS file in /usr/share/doc/pipewire instead of + /usr/share/doc/libpipewire-0.3-0/ to make it easier to find. + (Closes: #1024815) + + -- Dylan Aïssi <daissi@debian.org> Fri, 09 Dec 2022 14:03:00 +0100 + pipewire (0.3.61-1~bpo11+1+apertis2) apertis; urgency=medium * apparmor: fix rule for /proc/*/task/*/comm diff --git a/debian/control b/debian/control index 8f2b45111bc1a906ee009cb3d30964d98ca6e540..6aa24bcb275fd2838298fe7d7bb5a60b83b8b3ae 100644 --- a/debian/control +++ b/debian/control @@ -11,7 +11,7 @@ Build-Depends: debhelper-compat (= 13), libasound2-dev, libavahi-client-dev, libbluetooth-dev, -# libcamera-dev (>= 0.0.1), +# libcamera-dev (>= 0.0.1) [!m68k], libdbus-1-dev, # libfreeaptx-dev, libglib2.0-dev, @@ -309,7 +309,7 @@ Description: PipeWire V4L2 plugin #Package: pipewire-libcamera #Section: video -#Architecture: linux-any +#Architecture: amd64 arm64 armel armhf i386 mips64el mipsel ppc64el s390x alpha hppa ia64 powerpc ppc64 riscv64 sh4 sparc64 x32 #Multi-Arch: same #Depends: pipewire (= ${binary:Version}), # ${misc:Depends}, diff --git a/debian/libpipewire-0.3-0.symbols b/debian/libpipewire-0.3-0.symbols index 84324498643447c537e623198e0e4c397eefdc4a..15998a6de7b0ec0d3266f6ab32df7220b0ed69db 100644 --- a/debian/libpipewire-0.3-0.symbols +++ b/debian/libpipewire-0.3-0.symbols @@ -376,6 +376,7 @@ libpipewire-0.3.so.0 libpipewire-0.3-0 #MINVER# pw_resource_set_bound_id@Base 0.3.1 pw_resource_unref@Base 0.3.52 pw_set_domain@Base 0.3.26 + pw_split_ip@Base 0.3.63 pw_split_strv@Base 0.3.1 pw_split_walk@Base 0.3.1 pw_stream_add_listener@Base 0.3.1 diff --git a/debian/libspa-0.2-modules.install b/debian/libspa-0.2-modules.install index 7accb5b3c0cfc6ef6ba4db449553f847ac6c3855..a4063f08a07cc2f42bf976eae6b966254cf4d515 100644 --- a/debian/libspa-0.2-modules.install +++ b/debian/libspa-0.2-modules.install @@ -10,4 +10,3 @@ usr/lib/*/spa-0.2/test usr/lib/*/spa-0.2/v4l2 usr/lib/*/spa-0.2/videoconvert usr/lib/*/spa-0.2/videotestsrc -usr/lib/*/spa-0.2/volume diff --git a/debian/docs b/debian/pipewire.docs similarity index 100% rename from debian/docs rename to debian/pipewire.docs diff --git a/debian/rules b/debian/rules index 643481a8b3e45e4f7e28d560c00ed5afbba04f0e..34b1b970653170e6b240f1f2e5c2d11ffdc83844 100755 --- a/debian/rules +++ b/debian/rules @@ -18,6 +18,12 @@ else BLUEZ5_CODEC_LDAC=enabled endif +ifneq (,$(filter m68k,$(DEB_HOST_ARCH))) +LIBCAMERA=disabled +else +LIBCAMERA=enabled +endif + # lilv and some of its dependencies are in universe ifeq (yes,$(shell dpkg-vendor --derives-from Ubuntu && echo yes)) LV2=disabled @@ -46,7 +52,6 @@ override_dh_auto_configure: -Dsession-managers= \ -Dtest=enabled \ -Dvideotestsrc=enabled \ - -Dvolume=enabled \ -Dvulkan=disabled \ -Dsdl2=disabled \ -Djack=disabled \ diff --git a/doc/spa-plugins.dox b/doc/spa-plugins.dox index 4ab50000ccfddbc133596a3a81b4e222fc4b161d..af14d5eb19115fd155ff1a5af3ad01ee0ac6ef4f 100644 --- a/doc/spa-plugins.dox +++ b/doc/spa-plugins.dox @@ -19,7 +19,7 @@ To use a plugin, the following steps are required: In pseudo-code, loading a logger interface looks like this: \code{.py} -handle = dlopen("$SPA_PLUGIN_PATH/support/libspa-support.so") +handle = dlopen("$SPA_PLUGIN_DIR/support/libspa-support.so") factory_enumeration_func = dlsym(handle, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME) spa_log *logger = NULL @@ -49,8 +49,8 @@ are versioned and many versions can live on the same system. The `spa-inspect` tool provides a CLI interface to inspect SPA plugins: \verbatim -$ export SPA_PLUGIN_PATH=$(pkg-config --variable plugindir libspa-0.2) -$ spa-inspect ${SPA_PLUGIN_PATH}/support/libspa-support.so +$ export SPA_PLUGIN_DIR=$(pkg-config --variable plugindir libspa-0.2) +$ spa-inspect ${SPA_PLUGIN_DIR}/support/libspa-support.so ... factory version: 1 factory name: 'support.cpu' @@ -87,11 +87,11 @@ later, instead of hardcoding the plugin name. To `dlopen` a plugin we then need to prefix the plugin path like this: \code{.c} -#define SPA_PLUGIN_PATH /usr/lib64/spa-0.2/" -void *hnd = dlopen(SPA_PLUGIN_PATH"/support/libspa-support.so", RTLD_NOW); +#define SPA_PLUGIN_DIR /usr/lib64/spa-0.2/" +void *hnd = dlopen(SPA_PLUGIN_DIR"/support/libspa-support.so", RTLD_NOW); \endcode -The environment variable `SPA_PLUGIN_PATH` and `pkg-config` variable +The environment variable `SPA_PLUGIN_DIR` and `pkg-config` variable `plugindir` are usually used to find the location of the plugins. You will have to do some more work to construct the shared object path. diff --git a/meson.build b/meson.build index 5420c10edf1d7f5d4c523d09c4f3999ec1a201af..448725e59bd6bfabe996df5d955fe7016de4fc6e 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('pipewire', ['c' ], - version : '0.3.61', + version : '0.3.63', license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ], meson_version : '>= 0.59.0', default_options : [ 'warning_level=3', @@ -301,6 +301,9 @@ summary({'GLib-2.0 (Flatpak support)': glib2_dep.found()}, bool_yn: true, sectio flatpak_support = glib2_dep.found() cdata.set('HAVE_GLIB2', flatpak_support) +gio_dep = dependency('gio-2.0', version : '>= 2.26.0', required : get_option('gsettings')) +summary({'GIO (GSettings)': gio_dep.found()}, bool_yn: true, section: 'Misc dependencies') + gst_option = get_option('gstreamer') gst_deps_def = { 'glib-2.0': {'version': '>=2.32.0'}, diff --git a/meson_options.txt b/meson_options.txt index f306ecbf92449ae98b273e963467cb9cc4699330..fd5c9dab21efa5028653f7da946d2fb01475286e 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -177,9 +177,9 @@ option('videotestsrc', type: 'feature', value: 'enabled') option('volume', - description: 'Enable volume spa plugin integration', + description: 'Build the legacy volume spa plugin', type: 'feature', - value: 'enabled') + value: 'disabled') option('vulkan', description: 'Enable vulkan spa plugin integration', type: 'feature', @@ -269,3 +269,7 @@ option('readline', description: 'Enable code that depends on libreadline', type: 'feature', value: 'auto') +option('gsettings', + description: 'Enable code that depends on gsettings', + type: 'feature', + value: 'auto') diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index 98eede7de469ad72c999a1f40ea5977f661f2a49..ab837f60336549fa8ae050df42fc33ad850a325b 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -1002,6 +1002,38 @@ static inline void fix_midi_event(uint8_t *data, size_t size) } } +static inline int event_sort(struct spa_pod_control *a, struct spa_pod_control *b) +{ + if (a->offset < b->offset) + return -1; + if (a->offset > b->offset) + return 1; + if (a->type != b->type) + return 0; + switch(a->type) { + case SPA_CONTROL_Midi: + { + /* 11 (controller) > 12 (program change) > + * 8 (note off) > 9 (note on) > 10 (aftertouch) > + * 13 (channel pressure) > 14 (pitch bend) */ + static int priotab[] = { 5,4,3,7,6,2,1,0 }; + uint8_t *da, *db; + + if (SPA_POD_BODY_SIZE(&a->value) < 1 || + SPA_POD_BODY_SIZE(&b->value) < 1) + return 0; + + da = SPA_POD_BODY(&a->value); + db = SPA_POD_BODY(&b->value); + if ((da[0] & 0xf) != (db[0] & 0xf)) + return 0; + return priotab[(db[0]>>4) & 7] - priotab[(da[0]>>4) & 7]; + } + default: + return 0; + } +} + static void convert_to_midi(struct spa_pod_sequence **seq, uint32_t n_seq, void *midi, bool fix) { struct spa_pod_control *c[n_seq]; @@ -1014,15 +1046,13 @@ static void convert_to_midi(struct spa_pod_sequence **seq, uint32_t n_seq, void while (true) { struct spa_pod_control *next = NULL; uint32_t next_index = 0; - uint8_t *data; - size_t size; for (i = 0; i < n_seq; i++) { if (!spa_pod_control_is_inside(&seq[i]->body, SPA_POD_BODY_SIZE(seq[i]), c[i])) continue; - if (next == NULL || c[i]->offset < next->offset) { + if (next == NULL || event_sort(c[i], next) <= 0) { next = c[i]; next_index = i; } @@ -1030,11 +1060,12 @@ static void convert_to_midi(struct spa_pod_sequence **seq, uint32_t n_seq, void if (SPA_UNLIKELY(next == NULL)) break; - data = SPA_POD_BODY(&next->value); - size = SPA_POD_BODY_SIZE(&next->value); - switch(next->type) { case SPA_CONTROL_Midi: + { + uint8_t *data = SPA_POD_BODY(&next->value); + size_t size = SPA_POD_BODY_SIZE(&next->value); + if (fix) fix_midi_event(data, size); @@ -1043,6 +1074,7 @@ static void convert_to_midi(struct spa_pod_sequence **seq, uint32_t n_seq, void spa_strerror(res)); break; } + } c[next_index] = spa_pod_control_next(c[next_index]); } } @@ -1144,6 +1176,8 @@ static void complete_process(struct client *c, uint32_t frames) if (pw_map_item_is_free(item)) continue; p = item->data; + if (!p->valid) + continue; spa_list_for_each(mix, &p->mix, port_link) { if (SPA_LIKELY(mix->io != NULL)) mix->io->status = SPA_STATUS_NEED_DATA; @@ -1153,6 +1187,8 @@ static void complete_process(struct client *c, uint32_t frames) if (pw_map_item_is_free(item)) continue; p = item->data; + if (!p->valid) + continue; prepare_output(p, frames); p->io.status = SPA_STATUS_NEED_DATA; } diff --git a/spa/include/spa/buffer/meta.h b/spa/include/spa/buffer/meta.h index e270c56cacbaf13d9d9a93b5f1cc2ae127b067f4..ec09f18bd2b6bfff2a427f41c35f68da1b94364a 100644 --- a/spa/include/spa/buffer/meta.h +++ b/spa/include/spa/buffer/meta.h @@ -39,16 +39,17 @@ extern "C" { enum spa_meta_type { SPA_META_Invalid, - SPA_META_Header, /**< struct spa_meta_header */ - SPA_META_VideoCrop, /**< struct spa_meta_region with cropping data */ - SPA_META_VideoDamage, /**< array of struct spa_meta_region with damage, where an invalid entry or end-of-array marks the end. */ - SPA_META_Bitmap, /**< struct spa_meta_bitmap */ - SPA_META_Cursor, /**< struct spa_meta_cursor */ - SPA_META_Control, /**< metadata contains a spa_meta_control - * associated with the data */ - SPA_META_Busy, /**< don't write to buffer when count > 0 */ - - _SPA_META_LAST, /**< not part of ABI/API */ + SPA_META_Header, /**< struct spa_meta_header */ + SPA_META_VideoCrop, /**< struct spa_meta_region with cropping data */ + SPA_META_VideoDamage, /**< array of struct spa_meta_region with damage, where an invalid entry or end-of-array marks the end. */ + SPA_META_Bitmap, /**< struct spa_meta_bitmap */ + SPA_META_Cursor, /**< struct spa_meta_cursor */ + SPA_META_Control, /**< metadata contains a spa_meta_control + * associated with the data */ + SPA_META_Busy, /**< don't write to buffer when count > 0 */ + SPA_META_VideoTransform, /**< struct spa_meta_transform */ + + _SPA_META_LAST, /**< not part of ABI/API */ }; /** @@ -161,6 +162,24 @@ struct spa_meta_busy { uint32_t count; /**< number of users busy with the buffer */ }; +enum spa_meta_videotransform_value { + SPA_META_TRANSFORMATION_None = 0, /**< no transform */ + SPA_META_TRANSFORMATION_90, /**< 90 degree counter-clockwise */ + SPA_META_TRANSFORMATION_180, /**< 180 degree counter-clockwise */ + SPA_META_TRANSFORMATION_270, /**< 270 degree counter-clockwise */ + SPA_META_TRANSFORMATION_Flipped, /**< 180 degree flipped around the vertical axis. Equivalent + * to a reflexion through the vertical line splitting the + * bufffer in two equal sized parts */ + SPA_META_TRANSFORMATION_Flipped90, /**< flip then rotate around 90 degree counter-clockwise */ + SPA_META_TRANSFORMATION_Flipped180, /**< flip then rotate around 180 degree counter-clockwise */ + SPA_META_TRANSFORMATION_Flipped270, /**< flip then rotate around 270 degree counter-clockwise */ +}; + +/** a transformation of the buffer */ +struct spa_meta_videotransform { + uint32_t transform; /**< orientation transformation that was applied to the buffer */ +}; + /** * \} */ diff --git a/spa/include/spa/param/props.h b/spa/include/spa/param/props.h index 3f57bdeb6a0441396ee8e5f7c13eba945522b6a6..900dffaeb904d4f62ac65588a71f35971799c47d 100644 --- a/spa/include/spa/param/props.h +++ b/spa/include/spa/param/props.h @@ -74,6 +74,7 @@ enum spa_prop { SPA_PROP_rate, SPA_PROP_quality, SPA_PROP_bluetoothAudioCodec, + SPA_PROP_bluetoothOffloadActive, SPA_PROP_START_Audio = 0x10000, /**< audio related properties */ SPA_PROP_waveType, diff --git a/spa/include/spa/param/type-info.h b/spa/include/spa/param/type-info.h index 55a03124e2d2be814183f6574e990eb41527253d..ecde2371ba02ccdd7360612c74af08405376ff62 100644 --- a/spa/include/spa/param/type-info.h +++ b/spa/include/spa/param/type-info.h @@ -117,6 +117,7 @@ static const struct spa_type_info spa_type_props[] = { { SPA_PROP_rate, SPA_TYPE_Double, SPA_TYPE_INFO_PROPS_BASE "rate", NULL }, { SPA_PROP_quality, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "quality", NULL }, { SPA_PROP_bluetoothAudioCodec, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "bluetoothAudioCodec", spa_type_bluetooth_audio_codec }, + { SPA_PROP_bluetoothOffloadActive, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "bluetoothOffloadActive", NULL }, { SPA_PROP_waveType, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "waveType", NULL }, { SPA_PROP_frequency, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "frequency", NULL }, diff --git a/spa/plugins/aec/aec-webrtc.cpp b/spa/plugins/aec/aec-webrtc.cpp index 5c129c534b5c2ee5fa17c9bc24a2c601fdb2a2e3..19a506e84c5b573caa0573d72a9c278e8551125f 100644 --- a/spa/plugins/aec/aec-webrtc.cpp +++ b/spa/plugins/aec/aec-webrtc.cpp @@ -100,7 +100,8 @@ static int webrtc_init(void *object, const struct spa_dict *args, const struct s } apm->high_pass_filter()->Enable(high_pass_filter); - // Always disable drift compensation since it requires drift sampling + // Always disable drift compensation since PipeWire will already do + // drift compensation on all sinks and sources linked to this echo-canceler apm->echo_cancellation()->enable_drift_compensation(false); apm->echo_cancellation()->Enable(true); // TODO: wire up supression levels to args diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index 4bbe5ee927e0eb29e34768beb381dae555ac133a..b969c3d89aab40d2d753d8f38144b43ccb832c65 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -34,7 +34,7 @@ void *_acp_log_data; struct spa_i18n *acp_i18n; -#define DEFAULT_RATE 44100 +#define DEFAULT_RATE 48000 #define VOLUME_ACCURACY (PA_VOLUME_NORM/100) /* don't require volume adjustments to be perfectly correct. don't necessarily extend granularity in software unless the differences get greater than this level */ @@ -1348,7 +1348,6 @@ static int device_disable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_devic static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device *dev) { const char *mod_name; - bool ignore_dB = false; uint32_t i, port_index; int res; @@ -1365,7 +1364,7 @@ static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device dev->device.flags |= ACP_DEVICE_ACTIVE; - find_mixer(impl, dev, NULL, ignore_dB); + find_mixer(impl, dev, NULL, impl->ignore_dB); /* Synchronize priority values, as it may have changed when setting the profile */ for (i = 0; i < impl->card.n_ports; i++) { @@ -1386,7 +1385,7 @@ static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device if (dev->active_port) dev->active_port->port.flags |= ACP_PORT_ACTIVE; - if ((res = setup_mixer(impl, dev, ignore_dB)) < 0) + if ((res = setup_mixer(impl, dev, impl->ignore_dB)) < 0) return res; if (dev->read_volume) @@ -1533,7 +1532,6 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props) struct acp_card *card; const char *s, *profile_set = NULL, *profile = NULL; char device_id[16]; - bool ignore_dB = false; uint32_t profile_index; int res; @@ -1554,6 +1552,7 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props) impl->use_ucm = true; impl->auto_profile = true; impl->auto_port = true; + impl->ignore_dB = false; if (props) { if ((s = acp_dict_lookup(props, "api.alsa.use-ucm")) != NULL) @@ -1561,7 +1560,7 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props) if ((s = acp_dict_lookup(props, "api.alsa.soft-mixer")) != NULL) impl->soft_mixer = spa_atob(s); if ((s = acp_dict_lookup(props, "api.alsa.ignore-dB")) != NULL) - ignore_dB = spa_atob(s); + impl->ignore_dB = spa_atob(s); if ((s = acp_dict_lookup(props, "device.profile-set")) != NULL) profile_set = s; if ((s = acp_dict_lookup(props, "device.profile")) != NULL) @@ -1609,7 +1608,7 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props) goto error; } - impl->profile_set->ignore_dB = ignore_dB; + impl->profile_set->ignore_dB = impl->ignore_dB; pa_alsa_profile_set_probe(impl->profile_set, impl->ucm.mixers, device_id, diff --git a/spa/plugins/alsa/acp/card.h b/spa/plugins/alsa/acp/card.h index 139e2a6ae2e5c7c144fe11330965dd2c4dd94211..9f58343325faea01ea0d07d7320ccd0450235aab 100644 --- a/spa/plugins/alsa/acp/card.h +++ b/spa/plugins/alsa/acp/card.h @@ -46,6 +46,7 @@ struct pa_card { bool soft_mixer; bool auto_profile; bool auto_port; + bool ignore_dB; pa_alsa_ucm_config ucm; pa_alsa_profile_set *profile_set; diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 5e0a60b375e8658f8d4c682543393c6e6c9c0292..76fe433b8354816ea0ed35c0843744024de59280 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -1583,11 +1583,18 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ if (is_batch) state->headroom += period_size; + if (spa_strstartswith(state->props.device, "a52") || + spa_strstartswith(state->props.device, "dca")) + state->min_delay = SPA_MIN(2048u, state->buffer_frames); + else + state->min_delay = 0; + state->headroom = SPA_MIN(state->headroom, state->buffer_frames); state->start_delay = state->default_start_delay; - state->latency[state->port_direction].min_rate = state->headroom; - state->latency[state->port_direction].max_rate = state->headroom; + state->latency[state->port_direction].min_rate = + state->latency[state->port_direction].max_rate = + SPA_MAX(state->min_delay, state->headroom); spa_log_info(state->log, "%s (%s): format:%s access:%s-%s rate:%d channels:%d " "buffer frames %lu, period frames %lu, periods %u, frame_size %zd " @@ -1859,7 +1866,7 @@ static int get_status(struct state *state, uint64_t current_time, *delay = avail; *target = SPA_MAX(*target, state->read_size); } - *target = SPA_MIN(*target, state->buffer_frames); + *target = SPA_CLAMP(*target, state->min_delay, state->buffer_frames); return 0; } @@ -2428,9 +2435,20 @@ static void alsa_on_timeout_event(struct spa_source *source) struct state *state = source->data; snd_pcm_uframes_t delay, target; uint64_t expire, current_time; + int res; - if (SPA_UNLIKELY(state->started && spa_system_timerfd_read(state->data_system, state->timerfd, &expire) < 0)) - spa_log_warn(state->log, "%p: error reading timerfd: %m", state); + if (SPA_LIKELY(state->started)) { + if (SPA_UNLIKELY((res = spa_system_timerfd_read(state->data_system, + state->timerfd, &expire)) < 0)) { + /* we can get here when the timer is changed since the last + * timerfd wakeup, for example by do_reassign_follower() executed + * in the same epoll wakeup cycle */ + if (res != -EAGAIN) + spa_log_warn(state->log, "%p: error reading timerfd: %s", + state, spa_strerror(res)); + return; + } + } check_position_config(state); @@ -2610,6 +2628,8 @@ int spa_alsa_reassign_follower(struct state *state) else snd_pcm_pause(state->hndl, 0); } + + state->alsa_sync_warning = false; return 0; } diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index c630de3acc349b5fc919f72c602cabefa4997a82..9c4a86862563e03ecfce690cf707e529d361cef6 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -197,6 +197,7 @@ struct state { uint32_t last_threshold; uint32_t headroom; uint32_t start_delay; + uint32_t min_delay; uint32_t duration; unsigned int alsa_started:1; diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index 407b88f84cb08a85f464a5e6856583cd5f9f04b8..9cec44d2d4463b614b5a4234ab9383ac9f3aa550 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -800,8 +800,14 @@ static void alsa_on_timeout_event(struct spa_source *source) uint64_t expire; int res; - if (state->started && spa_system_timerfd_read(state->data_system, state->timerfd, &expire) < 0) - spa_log_warn(state->log, "error reading timerfd: %m"); + if (state->started) { + if ((res = spa_system_timerfd_read(state->data_system, state->timerfd, &expire)) < 0) { + if (res != -EAGAIN) + spa_log_warn(state->log, "%p: error reading timerfd: %s", + state, spa_strerror(res)); + return; + } + } state->current_time = state->next_time; diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 14bf3e6d970f926d34f36cee4b39018697939e2f..6875264465308aa331bddfd681e080219047d5d5 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -138,6 +138,27 @@ static int follower_enum_params(struct impl *this, return 0; } +static int convert_enum_port_config(struct impl *this, + int seq, uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter, struct spa_pod_builder *builder) +{ + struct spa_pod *f1, *f2 = NULL; + int res; + + f1 = spa_pod_builder_add_object(builder, + SPA_TYPE_OBJECT_ParamPortConfig, id, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(this->direction)); + + if (filter) { + if ((res = spa_pod_filter(builder, &f2, f1, filter)) < 0) + return res; + } + else { + f2 = f1; + } + return spa_node_enum_params(this->convert, seq, id, start, num, f2); +} + static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) @@ -163,9 +184,25 @@ next: switch (id) { case SPA_PARAM_EnumPortConfig: + return convert_enum_port_config(this, seq, id, start, num, filter, &b.b); case SPA_PARAM_PortConfig: - res = spa_node_enum_params(this->convert, seq, id, start, num, filter); - return res; + if (this->passthrough) { + switch (result.index) { + case 0: + result.param = spa_pod_builder_add_object(&b.b, + SPA_TYPE_OBJECT_ParamPortConfig, id, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(this->direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id( + SPA_PARAM_PORT_CONFIG_MODE_passthrough)); + result.next++; + break; + default: + return 0; + } + } else { + return convert_enum_port_config(this, seq, id, start, num, filter, &b.b); + } + break; case SPA_PARAM_PropInfo: res = follower_enum_params(this, id, IDX_PropInfo, &result, filter, &b.b); @@ -436,6 +473,7 @@ static int negotiate_buffers(struct impl *this) static int configure_format(struct impl *this, uint32_t flags, const struct spa_pod *format) { + uint8_t buffer[4096]; int res; if (format == NULL && !this->have_format) @@ -450,14 +488,13 @@ static int configure_format(struct impl *this, uint32_t flags, const struct spa_ SPA_PARAM_Format, flags, format)) < 0) return res; + if (res > 0) { - uint8_t buffer[4096]; - struct spa_pod_builder b = { 0 }; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); uint32_t state = 0; struct spa_pod *fmt; /* format was changed to nearest compatible format */ - spa_pod_builder_init(&b, buffer, sizeof(buffer)); if ((res = spa_node_port_enum_params_sync(this->follower, this->direction, 0, @@ -813,9 +850,11 @@ static int impl_node_send_command(void *object, const struct spa_command *comman this->started = true; break; case SPA_NODE_COMMAND_Suspend: + this->started = false; spa_log_debug(this->log, "%p: suspending", this); break; case SPA_NODE_COMMAND_Pause: + this->started = false; spa_log_debug(this->log, "%p: pausing", this); break; case SPA_NODE_COMMAND_Flush: @@ -847,10 +886,10 @@ static int impl_node_send_command(void *object, const struct spa_command *comman break; case SPA_NODE_COMMAND_Suspend: configure_format(this, 0, NULL); - SPA_FALLTHROUGH + spa_log_debug(this->log, "%p: suspended", this); + break; case SPA_NODE_COMMAND_Pause: - this->started = false; - spa_log_debug(this->log, "%p: stopped", this); + spa_log_debug(this->log, "%p: paused", this); break; case SPA_NODE_COMMAND_Flush: spa_log_debug(this->log, "%p: flushed", this); @@ -1164,7 +1203,7 @@ static int follower_ready(void *data, int status) spa_log_trace_fp(this->log, "%p: ready %d", this, status); if (!this->started) { - spa_log_warn(this->log, "%p: ready stopped node", this); + spa_log_info(this->log, "%p: ready stopped node", this); return -EIO; } diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 8e5237bdbf29bb2a0b7e87a8b2fd4ac91fcc478e..ccf4e30c5fb898a6afc77670a8c473520bde9824 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -161,6 +161,7 @@ struct dir { struct port *ports[MAX_PORTS]; uint32_t n_ports; + enum spa_direction direction; enum spa_param_port_config_mode mode; struct spa_audio_info format; @@ -378,55 +379,61 @@ static int impl_node_enum_params(void *object, int seq, switch (id) { case SPA_PARAM_EnumPortConfig: + { + struct dir *dir; switch (result.index) { case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamPortConfig, id, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_INPUT), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp)); + dir = &this->dir[SPA_DIRECTION_INPUT];; break; case 1: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamPortConfig, id, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_OUTPUT), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp)); - break; - case 2: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamPortConfig, id, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_INPUT), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_convert)); - break; - case 3: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamPortConfig, id, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_OUTPUT), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_convert)); + dir = &this->dir[SPA_DIRECTION_OUTPUT];; break; default: return 0; } + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, id, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_CHOICE_ENUM_Id(4, + SPA_PARAM_PORT_CONFIG_MODE_none, + SPA_PARAM_PORT_CONFIG_MODE_none, + SPA_PARAM_PORT_CONFIG_MODE_dsp, + SPA_PARAM_PORT_CONFIG_MODE_convert), + SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_CHOICE_Bool(false), + SPA_PARAM_PORT_CONFIG_control, SPA_POD_CHOICE_Bool(false)); break; - + } case SPA_PARAM_PortConfig: + { + struct dir *dir; + struct spa_pod_frame f[1]; + switch (result.index) { case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamPortConfig, id, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_INPUT), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(this->dir[SPA_DIRECTION_INPUT].mode)); + dir = &this->dir[SPA_DIRECTION_INPUT];; break; case 1: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamPortConfig, id, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_OUTPUT), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(this->dir[SPA_DIRECTION_OUTPUT].mode)); + dir = &this->dir[SPA_DIRECTION_OUTPUT];; break; default: return 0; } + spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_ParamPortConfig, id); + spa_pod_builder_add(&b, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(dir->mode), + SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(this->monitor), + SPA_PARAM_PORT_CONFIG_control, SPA_POD_Bool(dir->control), + 0); + + if (dir->have_format) { + spa_pod_builder_prop(&b, SPA_PARAM_PORT_CONFIG_format, 0); + spa_format_audio_raw_build(&b, SPA_PARAM_PORT_CONFIG_format, + &dir->format.info.raw); + } + param = spa_pod_builder_pop(&b, &f[0]); break; - + } case SPA_PARAM_PropInfo: { struct props *p = &this->props; @@ -1015,6 +1022,8 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m init_port(this, direction, 0, 0, false, false, false); break; } + case SPA_PARAM_PORT_CONFIG_MODE_none: + break; default: return -ENOTSUP; } @@ -1169,45 +1178,6 @@ static int setup_in_convert(struct impl *this) return 0; } -#define _MASK(ch) (1ULL << SPA_AUDIO_CHANNEL_ ## ch) -#define STEREO (_MASK(FL)|_MASK(FR)) - -static uint64_t default_mask(uint32_t channels) -{ - uint64_t mask = 0; - switch (channels) { - case 7: - case 8: - mask |= _MASK(RL); - mask |= _MASK(RR); - SPA_FALLTHROUGH - case 5: - case 6: - mask |= _MASK(SL); - mask |= _MASK(SR); - if ((channels & 1) == 0) - mask |= _MASK(LFE); - SPA_FALLTHROUGH - case 3: - mask |= _MASK(FC); - SPA_FALLTHROUGH - case 2: - mask |= _MASK(FL); - mask |= _MASK(FR); - break; - case 1: - mask |= _MASK(MONO); - break; - case 4: - mask |= _MASK(FL); - mask |= _MASK(FR); - mask |= _MASK(RL); - mask |= _MASK(RR); - break; - } - return mask; -} - static void fix_volumes(struct impl *this, struct volumes *vols, uint32_t channels) { float s; @@ -1328,11 +1298,6 @@ static int setup_channelmix(struct impl *this) spa_log_info(this->log, "out %s (%016"PRIx64")", format_position(str, sizeof(str), dst_chan, out->format.info.raw.position), dst_mask); - if (src_mask & 1) - src_mask = default_mask(src_chan); - if (dst_mask & 1) - dst_mask = default_mask(dst_chan); - spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d %08"PRIx64":%08"PRIx64, this, spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), src_chan, @@ -2870,7 +2835,7 @@ impl_init(const struct spa_handle_factory *factory, props_reset(&this->props); - this->mix.options = CHANNELMIX_OPTION_UPMIX; + this->mix.options = CHANNELMIX_OPTION_UPMIX | CHANNELMIX_OPTION_MIX_LFE; this->mix.upmix = CHANNELMIX_UPMIX_PSD; this->mix.log = this->log; this->mix.lfe_cutoff = 150.0f; @@ -2906,7 +2871,9 @@ impl_init(const struct spa_handle_factory *factory, this->props.soft.n_volumes = this->props.n_channels; this->props.monitor.n_volumes = this->props.n_channels; + this->dir[SPA_DIRECTION_INPUT].direction = SPA_DIRECTION_INPUT; this->dir[SPA_DIRECTION_INPUT].latency = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); + this->dir[SPA_DIRECTION_OUTPUT].direction = SPA_DIRECTION_OUTPUT; this->dir[SPA_DIRECTION_OUTPUT].latency = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); this->node.iface = SPA_INTERFACE_INIT( diff --git a/spa/plugins/audioconvert/channelmix-ops.c b/spa/plugins/audioconvert/channelmix-ops.c index cfc449f0b0d739d0292ef6d68229c68e98817aa4..44d3967761e3270f9261b60b3b8bce87c61033ca 100644 --- a/spa/plugins/audioconvert/channelmix-ops.c +++ b/spa/plugins/audioconvert/channelmix-ops.c @@ -522,6 +522,7 @@ done: if (i == 0) idx2 += snprintf(str2 + idx2, sizeof(str2) - idx2, "%-4.4s ", + src_mask == ~0LU ? "MONO" : spa_debug_type_find_short_name(spa_type_audio_channel, j + 3)); mix->matrix_orig[ic][jc++] = matrix[i][j]; @@ -536,6 +537,7 @@ done: if (i == 0) spa_log_info(mix->log, " %s", str2); spa_log_info(mix->log, "%-4.4s %s %f", + dst_mask == ~0LU ? "MONO" : spa_debug_type_find_short_name(spa_type_audio_channel, i + 3), str, sum); } diff --git a/spa/plugins/audioconvert/fmt-ops-avx2.c b/spa/plugins/audioconvert/fmt-ops-avx2.c index 723aea369bc67f5959c7e5b99fe30707d902464e..087f0275ec4d631609160bda336e9850ee37f22c 100644 --- a/spa/plugins/audioconvert/fmt-ops-avx2.c +++ b/spa/plugins/audioconvert/fmt-ops-avx2.c @@ -156,11 +156,12 @@ void conv_s24_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { - const int24_t *s = src; + const int8_t *s = src; float *d0 = dst[0]; uint32_t n, unrolled; __m128i in; __m128 out, factor = _mm_set1_ps(1.0f / S24_SCALE); + __m128i mask1 = _mm_setr_epi32(0*n_channels, 3*n_channels, 6*n_channels, 9*n_channels); if (SPA_IS_ALIGNED(d0, 16) && n_samples > 0) { unrolled = n_samples & ~3; @@ -171,23 +172,19 @@ conv_s24_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA unrolled = 0; for(n = 0; n < unrolled; n += 4) { - in = _mm_setr_epi32( - *((uint32_t*)&s[0 * n_channels]), - *((uint32_t*)&s[1 * n_channels]), - *((uint32_t*)&s[2 * n_channels]), - *((uint32_t*)&s[3 * n_channels])); + in = _mm_i32gather_epi32((int*)s, mask1, 1); in = _mm_slli_epi32(in, 8); in = _mm_srai_epi32(in, 8); out = _mm_cvtepi32_ps(in); out = _mm_mul_ps(out, factor); _mm_store_ps(&d0[n], out); - s += 4 * n_channels; + s += 12 * n_channels; } for(; n < n_samples; n++) { - out = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); + out = _mm_cvtsi32_ss(factor, s24_to_s32(*(int24_t*)s)); out = _mm_mul_ss(out, factor); _mm_store_ss(&d0[n], out); - s += n_channels; + s += 3 * n_channels; } } @@ -195,11 +192,12 @@ static void conv_s24_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { - const int24_t *s = src; + const int8_t *s = src; float *d0 = dst[0], *d1 = dst[1]; uint32_t n, unrolled; __m128i in[2]; __m128 out[2], factor = _mm_set1_ps(1.0f / S24_SCALE); + __m128i mask1 = _mm_setr_epi32(0*n_channels, 3*n_channels, 6*n_channels, 9*n_channels); if (SPA_IS_ALIGNED(d0, 16) && SPA_IS_ALIGNED(d1, 16) && @@ -212,16 +210,8 @@ conv_s24_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA unrolled = 0; for(n = 0; n < unrolled; n += 4) { - in[0] = _mm_setr_epi32( - *((uint32_t*)&s[0 + 0*n_channels]), - *((uint32_t*)&s[0 + 1*n_channels]), - *((uint32_t*)&s[0 + 2*n_channels]), - *((uint32_t*)&s[0 + 3*n_channels])); - in[1] = _mm_setr_epi32( - *((uint32_t*)&s[1 + 0*n_channels]), - *((uint32_t*)&s[1 + 1*n_channels]), - *((uint32_t*)&s[1 + 2*n_channels]), - *((uint32_t*)&s[1 + 3*n_channels])); + in[0] = _mm_i32gather_epi32((int*)&s[0], mask1, 1); + in[1] = _mm_i32gather_epi32((int*)&s[3], mask1, 1); in[0] = _mm_slli_epi32(in[0], 8); in[1] = _mm_slli_epi32(in[1], 8); @@ -238,27 +228,28 @@ conv_s24_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA _mm_store_ps(&d0[n], out[0]); _mm_store_ps(&d1[n], out[1]); - s += 4 * n_channels; + s += 12 * n_channels; } for(; n < n_samples; n++) { - out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); - out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+1))); + out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+0))); + out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+1))); out[0] = _mm_mul_ss(out[0], factor); out[1] = _mm_mul_ss(out[1], factor); _mm_store_ss(&d0[n], out[0]); _mm_store_ss(&d1[n], out[1]); - s += n_channels; + s += 3 * n_channels; } } static void conv_s24_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { - const int24_t *s = src; + const int8_t *s = src; float *d0 = dst[0], *d1 = dst[1], *d2 = dst[2], *d3 = dst[3]; uint32_t n, unrolled; __m128i in[4]; __m128 out[4], factor = _mm_set1_ps(1.0f / S24_SCALE); + __m128i mask1 = _mm_setr_epi32(0*n_channels, 3*n_channels, 6*n_channels, 9*n_channels); if (SPA_IS_ALIGNED(d0, 16) && SPA_IS_ALIGNED(d1, 16) && @@ -273,26 +264,10 @@ conv_s24_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA unrolled = 0; for(n = 0; n < unrolled; n += 4) { - in[0] = _mm_setr_epi32( - *((uint32_t*)&s[0 + 0*n_channels]), - *((uint32_t*)&s[0 + 1*n_channels]), - *((uint32_t*)&s[0 + 2*n_channels]), - *((uint32_t*)&s[0 + 3*n_channels])); - in[1] = _mm_setr_epi32( - *((uint32_t*)&s[1 + 0*n_channels]), - *((uint32_t*)&s[1 + 1*n_channels]), - *((uint32_t*)&s[1 + 2*n_channels]), - *((uint32_t*)&s[1 + 3*n_channels])); - in[2] = _mm_setr_epi32( - *((uint32_t*)&s[2 + 0*n_channels]), - *((uint32_t*)&s[2 + 1*n_channels]), - *((uint32_t*)&s[2 + 2*n_channels]), - *((uint32_t*)&s[2 + 3*n_channels])); - in[3] = _mm_setr_epi32( - *((uint32_t*)&s[3 + 0*n_channels]), - *((uint32_t*)&s[3 + 1*n_channels]), - *((uint32_t*)&s[3 + 2*n_channels]), - *((uint32_t*)&s[3 + 3*n_channels])); + in[0] = _mm_i32gather_epi32((int*)&s[0], mask1, 1); + in[1] = _mm_i32gather_epi32((int*)&s[3], mask1, 1); + in[2] = _mm_i32gather_epi32((int*)&s[6], mask1, 1); + in[3] = _mm_i32gather_epi32((int*)&s[9], mask1, 1); in[0] = _mm_slli_epi32(in[0], 8); in[1] = _mm_slli_epi32(in[1], 8); @@ -319,13 +294,13 @@ conv_s24_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA _mm_store_ps(&d2[n], out[2]); _mm_store_ps(&d3[n], out[3]); - s += 4 * n_channels; + s += 12 * n_channels; } for(; n < n_samples; n++) { - out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); - out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+1))); - out[2] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+2))); - out[3] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+3))); + out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+0))); + out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+1))); + out[2] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+2))); + out[3] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+3))); out[0] = _mm_mul_ss(out[0], factor); out[1] = _mm_mul_ss(out[1], factor); out[2] = _mm_mul_ss(out[2], factor); @@ -334,7 +309,7 @@ conv_s24_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA _mm_store_ss(&d1[n], out[1]); _mm_store_ss(&d2[n], out[2]); _mm_store_ss(&d3[n], out[3]); - s += n_channels; + s += 3 * n_channels; } } @@ -361,12 +336,10 @@ conv_s32_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA const int32_t *s = src; float *d0 = dst[0], *d1 = dst[1], *d2 = dst[2], *d3 = dst[3]; uint32_t n, unrolled; - __m256i in[4], t[4]; + __m256i in[4]; __m256 out[4], factor = _mm256_set1_ps(1.0f / S24_SCALE); - __m256i mask1 = _mm256_setr_epi64x(0*n_channels, 0*n_channels+2, 4*n_channels, 4*n_channels+2); - __m256i mask2 = _mm256_setr_epi64x(1*n_channels, 1*n_channels+2, 5*n_channels, 5*n_channels+2); - __m256i mask3 = _mm256_setr_epi64x(2*n_channels, 2*n_channels+2, 6*n_channels, 6*n_channels+2); - __m256i mask4 = _mm256_setr_epi64x(3*n_channels, 3*n_channels+2, 7*n_channels, 7*n_channels+2); + __m256i mask1 = _mm256_setr_epi32(0*n_channels, 1*n_channels, 2*n_channels, 3*n_channels, + 4*n_channels, 5*n_channels, 6*n_channels, 7*n_channels); if (SPA_IS_ALIGNED(d0, 32) && SPA_IS_ALIGNED(d1, 32) && @@ -377,19 +350,10 @@ conv_s32_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA unrolled = 0; for(n = 0; n < unrolled; n += 8) { - in[0] = _mm256_i64gather_epi64((long long int *)&s[0*n_channels], mask1, 4); - in[1] = _mm256_i64gather_epi64((long long int *)&s[0*n_channels], mask2, 4); - in[2] = _mm256_i64gather_epi64((long long int *)&s[0*n_channels], mask3, 4); - in[3] = _mm256_i64gather_epi64((long long int *)&s[0*n_channels], mask4, 4); - - t[0] = _mm256_unpacklo_epi32(in[0], in[1]); /* a0 a1 b0 b1 a4 a5 b4 b5 */ - t[1] = _mm256_unpackhi_epi32(in[0], in[1]); /* c0 c1 d0 d1 c4 c5 d4 d5 */ - t[2] = _mm256_unpacklo_epi32(in[2], in[3]); /* a2 a3 b2 b3 a6 a7 b6 b7 */ - t[3] = _mm256_unpackhi_epi32(in[2], in[3]); /* c2 c3 d2 d3 c6 c7 d6 d7 */ - in[0] = _mm256_unpacklo_epi64(t[0], t[2]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ - in[1] = _mm256_unpackhi_epi64(t[0], t[2]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ - in[2] = _mm256_unpacklo_epi64(t[1], t[3]); /* c0 c1 c2 c3 c4 c5 c6 c7 */ - in[3] = _mm256_unpackhi_epi64(t[1], t[3]); /* d0 d1 d2 d3 d4 d5 d6 d7 */ + in[0] = _mm256_i32gather_epi32((int*)&s[0], mask1, 4); + in[1] = _mm256_i32gather_epi32((int*)&s[1], mask1, 4); + in[2] = _mm256_i32gather_epi32((int*)&s[2], mask1, 4); + in[3] = _mm256_i32gather_epi32((int*)&s[3], mask1, 4); in[0] = _mm256_srai_epi32(in[0], 8); in[1] = _mm256_srai_epi32(in[1], 8); @@ -438,11 +402,10 @@ conv_s32_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA const int32_t *s = src; float *d0 = dst[0], *d1 = dst[1]; uint32_t n, unrolled; - __m256i in[4], t[4]; + __m256i in[4]; __m256 out[4], factor = _mm256_set1_ps(1.0f / S24_SCALE); - __m256i perm = _mm256_setr_epi32(0, 2, 4, 6, 1, 3, 5, 7); - __m256i mask1 = _mm256_setr_epi64x(0*n_channels, 1*n_channels, 2*n_channels, 3*n_channels); - __m256i mask2 = _mm256_setr_epi64x(4*n_channels, 5*n_channels, 6*n_channels, 7*n_channels); + __m256i mask1 = _mm256_setr_epi32(0*n_channels, 1*n_channels, 2*n_channels, 3*n_channels, + 4*n_channels, 5*n_channels, 6*n_channels, 7*n_channels); if (SPA_IS_ALIGNED(d0, 32) && SPA_IS_ALIGNED(d1, 32)) @@ -451,14 +414,8 @@ conv_s32_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA unrolled = 0; for(n = 0; n < unrolled; n += 8) { - in[0] = _mm256_i64gather_epi64((long long int *)s, mask1, 4); - in[1] = _mm256_i64gather_epi64((long long int *)s, mask2, 4); - - t[0] = _mm256_permutevar8x32_epi32(in[0], perm); - t[1] = _mm256_permutevar8x32_epi32(in[1], perm); - - in[0] = _mm256_permute2x128_si256(t[0], t[1], 0 | (2 << 4)); - in[1] = _mm256_permute2x128_si256(t[0], t[1], 1 | (3 << 4)); + in[0] = _mm256_i32gather_epi32((int*)&s[0], mask1, 4); + in[1] = _mm256_i32gather_epi32((int*)&s[1], mask1, 4); in[0] = _mm256_srai_epi32(in[0], 8); in[1] = _mm256_srai_epi32(in[1], 8); @@ -495,8 +452,8 @@ conv_s32_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA uint32_t n, unrolled; __m256i in[2]; __m256 out[2], factor = _mm256_set1_ps(1.0f / S24_SCALE); - __m256i mask1 = _mm256_setr_epi64x(0*n_channels, 1*n_channels, 2*n_channels, 3*n_channels); - __m256i mask2 = _mm256_setr_epi64x(4*n_channels, 5*n_channels, 6*n_channels, 7*n_channels); + __m256i mask1 = _mm256_setr_epi32(0*n_channels, 1*n_channels, 2*n_channels, 3*n_channels, + 4*n_channels, 5*n_channels, 6*n_channels, 7*n_channels); if (SPA_IS_ALIGNED(d0, 32)) unrolled = n_samples & ~15; @@ -504,12 +461,8 @@ conv_s32_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA unrolled = 0; for(n = 0; n < unrolled; n += 16) { - in[0] = _mm256_setr_m128i( - _mm256_i64gather_epi32(&s[ 0*n_channels], mask1, 4), - _mm256_i64gather_epi32(&s[ 0*n_channels], mask2, 4)); - in[1] = _mm256_setr_m128i( - _mm256_i64gather_epi32(&s[ 8*n_channels], mask1, 4), - _mm256_i64gather_epi32(&s[ 8*n_channels], mask2, 4)); + in[0] = _mm256_i32gather_epi32(&s[0*n_channels], mask1, 4); + in[1] = _mm256_i32gather_epi32(&s[8*n_channels], mask1, 4); in[0] = _mm256_srai_epi32(in[0], 8); in[1] = _mm256_srai_epi32(in[1], 8); diff --git a/spa/plugins/audiotestsrc/audiotestsrc.c b/spa/plugins/audiotestsrc/audiotestsrc.c index f009b9a29b5a40c78c32d5c7852c3317fb7cabd6..c0c9c7a68dbc9daf3f042f8f9236e1d572a9a3f9 100644 --- a/spa/plugins/audiotestsrc/audiotestsrc.c +++ b/spa/plugins/audiotestsrc/audiotestsrc.c @@ -34,6 +34,7 @@ #include <spa/support/loop.h> #include <spa/utils/list.h> #include <spa/utils/keys.h> +#include <spa/utils/result.h> #include <spa/utils/string.h> #include <spa/node/node.h> #include <spa/node/utils.h> @@ -348,14 +349,20 @@ static void set_timer(struct impl *this, bool enabled) } } -static void read_timer(struct impl *this) +static int read_timer(struct impl *this) { uint64_t expirations; + int res = 0; if (this->async || this->props.live) { - if (spa_system_timerfd_read(this->data_system, this->timer_source.fd, &expirations) < 0) - perror("read timerfd"); + if ((res = spa_system_timerfd_read(this->data_system, + this->timer_source.fd, &expirations)) < 0) { + if (res != -EAGAIN) + spa_log_error(this->log, NAME " %p: timerfd error: %s", + this, spa_strerror(res)); + } } + return 0; } static int make_buffer(struct impl *this) @@ -369,7 +376,8 @@ static int make_buffer(struct impl *this) uint32_t filled, avail; uint32_t index, offset, l0, l1; - read_timer(this); + if (read_timer(this) < 0) + return 0; if (spa_list_is_empty(&port->empty)) { set_timer(this, false); diff --git a/spa/plugins/avb/avb-pcm.c b/spa/plugins/avb/avb-pcm.c index 8fe9503a379be7f684d7907a6cc29a9c47b95698..484adc6605ee42ad6c20bf2a5273d3d3be675a2a 100644 --- a/spa/plugins/avb/avb-pcm.c +++ b/spa/plugins/avb/avb-pcm.c @@ -36,6 +36,7 @@ #include <arpa/inet.h> #include <spa/pod/filter.h> +#include <spa/utils/result.h> #include <spa/utils/string.h> #include <spa/support/system.h> #include <spa/utils/keys.h> @@ -1048,14 +1049,15 @@ static void avb_on_timeout_event(struct spa_source *source) struct state *state = source->data; uint64_t expirations, current_time, duration; uint32_t rate; + int res; spa_log_trace(state->log, "timeout"); - if (spa_system_timerfd_read(state->data_system, - state->timer_source.fd, &expirations) < 0) { - if (errno == EAGAIN) - return; - spa_log_error(state->log, "read timerfd: %m"); + if ((res = spa_system_timerfd_read(state->data_system, + state->timer_source.fd, &expirations)) < 0) { + if (res != -EAGAIN) + spa_log_error(state->log, "read timerfd: %s", spa_strerror(res)); + return; } current_time = state->next_time; diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index f3c2d721f3b05f031f3d6284d1785047b52de317..558cff2bee8918e15f03054621169144e210d167 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -79,11 +79,13 @@ enum { struct props { enum spa_bluetooth_audio_codec codec; + bool offload_active; }; static void reset_props(struct props *props) { props->codec = 0; + props->offload_active = false; } struct impl; @@ -97,6 +99,7 @@ struct node { unsigned int mute:1; unsigned int save:1; unsigned int a2dp_duplex:1; + unsigned int offload_acquired:1; uint32_t n_channels; int64_t latency_offset; uint32_t channels[SPA_AUDIO_MAX_CHANNELS]; @@ -407,6 +410,24 @@ static const struct spa_bt_transport_events transport_events = { .volume_changed = volume_changed, }; +static int node_offload_set_active(struct node *node, bool active) +{ + int res = 0; + + if (node->transport == NULL || !node->active) + return -ENOTSUP; + + if (active && !node->offload_acquired) + res = spa_bt_transport_acquire(node->transport, false); + else if (!active && node->offload_acquired) + res = spa_bt_transport_release(node->transport); + + if (res >= 0) + node->offload_acquired = active; + + return res; +} + static void get_channels(struct spa_bt_transport *t, bool a2dp_duplex, uint32_t *n_channels, uint32_t *channels) { const struct media_codec *codec; @@ -480,6 +501,7 @@ static void emit_node(struct impl *this, struct spa_bt_transport *t, this->nodes[id].impl = this; this->nodes[id].active = true; + this->nodes[id].offload_acquired = false; this->nodes[id].a2dp_duplex = a2dp_duplex; get_channels(t, a2dp_duplex, &this->nodes[id].n_channels, this->nodes[id].channels); if (this->nodes[id].transport) @@ -804,6 +826,7 @@ static void emit_remove_nodes(struct impl *this) for (uint32_t i = 0; i < 2; i++) { struct node * node = &this->nodes[i]; + node_offload_set_active(node, false); if (node->transport) { spa_hook_remove(&node->transport_listener); node->transport = NULL; @@ -813,6 +836,8 @@ static void emit_remove_nodes(struct impl *this) node->active = false; } } + + this->props.offload_active = false; } static bool validate_profile(struct impl *this, uint32_t profile, @@ -1674,7 +1699,7 @@ next: return true; } -static struct spa_pod *build_prop_info(struct impl *this, struct spa_pod_builder *b, uint32_t id) +static struct spa_pod *build_prop_info_codec(struct impl *this, struct spa_pod_builder *b, uint32_t id) { struct spa_pod_frame f[2]; struct spa_pod_choice *choice; @@ -1748,7 +1773,8 @@ static struct spa_pod *build_props(struct impl *this, struct spa_pod_builder *b, return spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Props, id, - SPA_PROP_bluetoothAudioCodec, SPA_POD_Id(p->codec)); + SPA_PROP_bluetoothAudioCodec, SPA_POD_Id(p->codec), + SPA_PROP_bluetoothOffloadActive, SPA_POD_Bool(p->offload_active)); } static int impl_enum_params(void *object, int seq, @@ -1841,7 +1867,14 @@ static int impl_enum_params(void *object, int seq, { switch (result.index) { case 0: - param = build_prop_info(this, &b, id); + param = build_prop_info_codec(this, &b, id); + break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_bluetoothOffloadActive), + SPA_PROP_INFO_description, SPA_POD_String("Bluetooth audio offload active"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(false)); break; default: return 0; @@ -2030,6 +2063,25 @@ static int apply_device_props(struct impl *this, struct node *node, struct spa_p return changed; } +static void apply_prop_offload_active(struct impl *this, bool active) +{ + bool old_value = this->props.offload_active; + + this->props.offload_active = active; + + for (int i = 0; i < 2; i++) { + node_offload_set_active(&this->nodes[i], active); + if (!this->nodes[i].offload_acquired) + this->props.offload_active = false; + } + + if (this->props.offload_active != old_value) { + this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; + this->params[IDX_Props].flags ^= SPA_PARAM_INFO_SERIAL; + emit_info(this, false); + } +} + static int impl_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) @@ -2104,19 +2156,23 @@ static int impl_set_param(void *object, case SPA_PARAM_Props: { uint32_t codec_id = SPA_ID_INVALID; + bool offload_active = this->props.offload_active; if (param == NULL) return 0; if ((res = spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, - SPA_PROP_bluetoothAudioCodec, SPA_POD_OPT_Id(&codec_id))) < 0) { + SPA_PROP_bluetoothAudioCodec, SPA_POD_OPT_Id(&codec_id), + SPA_PROP_bluetoothOffloadActive, SPA_POD_OPT_Bool(&offload_active))) < 0) { spa_log_warn(this->log, "can't parse props"); spa_debug_pod(0, NULL, param); return res; } - spa_log_debug(this->log, "setting props codec:%d", codec_id); + spa_log_debug(this->log, "setting props codec:%d offload:%d", (int)codec_id, (int)offload_active); + + apply_prop_offload_active(this, offload_active); if (codec_id == SPA_ID_INVALID) return 0; diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index a5d72fcfca7b0ff4ab67b538fccdf84b2f75d86a..ee6cef4ce8d2c8da2c0a56162d0233e0c293f689 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -839,11 +839,15 @@ static void media_on_flush_timeout(struct spa_source *source) { struct impl *this = source->data; uint64_t exp; + int res; spa_log_trace(this->log, "%p: flush on timeout", this); - if (spa_system_timerfd_read(this->data_system, this->flush_timerfd, &exp) < 0) - spa_log_warn(this->log, "error reading timerfd: %s", strerror(errno)); + if ((res = spa_system_timerfd_read(this->data_system, this->flush_timerfd, &exp)) < 0) { + if (res != -EAGAIN) + spa_log_warn(this->log, "error reading timerfd: %s", spa_strerror(res)); + return; + } if (this->transport == NULL) { enable_flush_timer(this, false); @@ -864,12 +868,19 @@ static void media_on_timeout(struct spa_source *source) uint32_t rate; struct spa_io_buffers *io = port->io; uint64_t prev_time, now_time; + int res; if (this->transport == NULL) return; - if (this->started && spa_system_timerfd_read(this->data_system, this->timerfd, &exp) < 0) - spa_log_warn(this->log, "error reading timerfd: %s", strerror(errno)); + if (this->started) { + if ((res = spa_system_timerfd_read(this->data_system, this->timerfd, &exp)) < 0) { + if (res != -EAGAIN) + spa_log_warn(this->log, "error reading timerfd: %s", + spa_strerror(res)); + return; + } + } prev_time = this->current_time; now_time = this->current_time = this->next_time; diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index 58ff14a52955a73d16bc019a14d391f417cd4d5b..d260335ce0251dbb96ea4ceaf1fc327bf0729222 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -538,9 +538,13 @@ static void media_on_duplex_timeout(struct spa_source *source) { struct impl *this = source->data; uint64_t exp; + int res; - if (spa_system_timerfd_read(this->data_system, this->duplex_timerfd, &exp) < 0) - spa_log_warn(this->log, "error reading timerfd: %s", strerror(errno)); + if ((res = spa_system_timerfd_read(this->data_system, this->duplex_timerfd, &exp)) < 0) { + if (res != -EAGAIN) + spa_log_warn(this->log, "error reading timerfd: %s", spa_strerror(res)); + return; + } set_duplex_timeout(this, this->duplex_timeout); @@ -577,12 +581,18 @@ static void media_on_timeout(struct spa_source *source) uint64_t exp, duration; uint32_t rate; uint64_t prev_time, now_time; + int res; if (this->transport == NULL) return; - if (this->started && spa_system_timerfd_read(this->data_system, this->timerfd, &exp) < 0) - spa_log_warn(this->log, "error reading timerfd: %s", strerror(errno)); + if (this->started) { + if ((res = spa_system_timerfd_read(this->data_system, this->timerfd, &exp)) < 0) { + if (res != -EAGAIN) + spa_log_warn(this->log, "error reading timerfd: %s", spa_strerror(res)); + return; + } + } prev_time = this->current_time; now_time = this->current_time = this->next_time; diff --git a/spa/plugins/bluez5/sco-sink.c b/spa/plugins/bluez5/sco-sink.c index f8db7eaf8fcf031610288dd0aab9d253dede3671..7025c78a9155c0746473e4222bf7077f8c9a5489 100644 --- a/spa/plugins/bluez5/sco-sink.c +++ b/spa/plugins/bluez5/sco-sink.c @@ -95,7 +95,6 @@ struct port { struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; - struct spa_list free; struct spa_list ready; struct buffer *current_buffer; @@ -568,16 +567,19 @@ stop: enable_flush_timer(this, false); } - static void sco_on_flush_timeout(struct spa_source *source) { struct impl *this = source->data; uint64_t exp; + int res; spa_log_trace(this->log, "%p: flush on timeout", this); - if (spa_system_timerfd_read(this->data_system, this->flush_timerfd, &exp) < 0) - spa_log_warn(this->log, "error reading timerfd: %s", strerror(errno)); + if ((res = spa_system_timerfd_read(this->data_system, this->flush_timerfd, &exp)) < 0) { + if (res != -EAGAIN) + spa_log_warn(this->log, "error reading timerfd: %s", spa_strerror(res)); + return; + } if (this->transport == NULL) { enable_flush_timer(this, false); @@ -598,12 +600,18 @@ static void sco_on_timeout(struct spa_source *source) uint32_t rate; struct spa_io_buffers *io = port->io; uint64_t prev_time, now_time; + int res; if (this->transport == NULL) return; - if (this->started && spa_system_timerfd_read(this->data_system, this->timerfd, &exp) < 0) - spa_log_warn(this->log, "error reading timerfd: %s", strerror(errno)); + if (this->started) { + if ((res = spa_system_timerfd_read(this->data_system, this->timerfd, &exp)) < 0) { + if (res != -EAGAIN) + spa_log_warn(this->log, "error reading timerfd: %s", spa_strerror(res)); + return; + } + } prev_time = this->current_time; now_time = this->current_time = this->next_time; diff --git a/spa/plugins/bluez5/sco-source.c b/spa/plugins/bluez5/sco-source.c index 17855065f0f4634ecfebc6a58a49f672ec3038b8..b4c40983a769bcd60334ae4848da350933ddba54 100644 --- a/spa/plugins/bluez5/sco-source.c +++ b/spa/plugins/bluez5/sco-source.c @@ -34,6 +34,7 @@ #include <spa/support/loop.h> #include <spa/support/log.h> #include <spa/support/system.h> +#include <spa/utils/result.h> #include <spa/utils/list.h> #include <spa/utils/keys.h> #include <spa/utils/names.h> @@ -600,12 +601,19 @@ static void sco_on_timeout(struct spa_source *source) uint64_t exp, duration; uint32_t rate; uint64_t prev_time, now_time; + int res; if (this->transport == NULL) return; - if (this->started && spa_system_timerfd_read(this->data_system, this->timerfd, &exp) < 0) - spa_log_warn(this->log, "error reading timerfd: %s", strerror(errno)); + if (this->started) { + if ((res = spa_system_timerfd_read(this->data_system, this->timerfd, &exp)) < 0) { + if (res != -EAGAIN) + spa_log_warn(this->log, "error reading timerfd: %s", + spa_strerror(res)); + return; + } + } prev_time = this->current_time; now_time = this->current_time = this->next_time; diff --git a/spa/plugins/control/mixer.c b/spa/plugins/control/mixer.c index 81a3bd5191cab114c76fe5b8fb0c8ddac84aabc9..1ef212b46ebc24b07745e1718de645a4cd8378f7 100644 --- a/spa/plugins/control/mixer.c +++ b/spa/plugins/control/mixer.c @@ -37,6 +37,7 @@ #include <spa/node/io.h> #include <spa/param/audio/format-utils.h> #include <spa/param/param.h> +#include <spa/control/control.h> #include <spa/pod/filter.h> #define NAME "control-mixer" @@ -571,6 +572,38 @@ static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t return queue_buffer(this, port, &port->buffers[buffer_id]); } +static inline int event_sort(struct spa_pod_control *a, struct spa_pod_control *b) +{ + if (a->offset < b->offset) + return -1; + if (a->offset > b->offset) + return 1; + if (a->type != b->type) + return 0; + switch(a->type) { + case SPA_CONTROL_Midi: + { + /* 11 (controller) > 12 (program change) > + * 8 (note off) > 9 (note on) > 10 (aftertouch) > + * 13 (channel pressure) > 14 (pitch bend) */ + static int priotab[] = { 5,4,3,7,6,2,1,0 }; + uint8_t *da, *db; + + if (SPA_POD_BODY_SIZE(&a->value) < 1 || + SPA_POD_BODY_SIZE(&b->value) < 1) + return 0; + + da = SPA_POD_BODY(&a->value); + db = SPA_POD_BODY(&b->value); + if ((da[0] & 0xf) != (db[0] & 0xf)) + return 0; + return priotab[(db[0]>>4) & 7] - priotab[(da[0]>>4) & 7]; + } + default: + return 0; + } +} + static int impl_node_process(void *object) { struct impl *this = object; @@ -664,7 +697,7 @@ static int impl_node_process(void *object) SPA_POD_BODY_SIZE(seq[i]), ctrl[i])) continue; - if (next == NULL || ctrl[i]->offset < next->offset) { + if (next == NULL || event_sort(ctrl[i], next) <= 0) { next = ctrl[i]; next_index = i; } diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 565325369cd4b499b896a635660d20c9506e58f4..50954e5e3ddaa37a13284702b82395f02f8da54b 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -78,6 +78,7 @@ struct buffer { struct spa_list link; struct spa_buffer *outbuf; struct spa_meta_header *h; + struct spa_meta_videotransform *videotransform; void *ptr; }; @@ -589,6 +590,12 @@ next: SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; + case 1: + param = (struct spa_pod*)spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamMeta, id, + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoTransform), + SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_videotransform))); + break; default: return 0; } diff --git a/spa/plugins/libcamera/libcamera-utils.cpp b/spa/plugins/libcamera/libcamera-utils.cpp index 446220bd10500cb38035cd9bddc1e3f120f39010..af07484f2011998da6812c1b2bf1dbe6c66f1b41 100644 --- a/spa/plugins/libcamera/libcamera-utils.cpp +++ b/spa/plugins/libcamera/libcamera-utils.cpp @@ -74,7 +74,7 @@ static void spa_libcamera_get_config(struct impl *impl) return; StreamRoles roles; - roles.push_back(VideoRecording); + roles.push_back(StreamRole::VideoRecording); impl->config = impl->camera->generateConfiguration(roles); } @@ -500,28 +500,48 @@ next: 0); switch (ctrl_id->type()) { - case ControlTypeBool: + case ControlTypeBool: { + bool def; + if (ctrl_info.def().isNone()) + def = ctrl_info.min().get<bool>(); + else + def = ctrl_info.def().get<bool>(); + spa_pod_builder_add(&b, - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( - (bool)ctrl_info.def().get<bool>()), - 0); - break; - case ControlTypeFloat: + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( + def), + 0); + } break; + case ControlTypeFloat: { + float min = ctrl_info.min().get<float>(); + float max = ctrl_info.max().get<float>(); + float def; + + if (ctrl_info.def().isNone()) + def = (min + max) / 2; + else + def = ctrl_info.def().get<float>(); + spa_pod_builder_add(&b, SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( - (float)ctrl_info.def().get<float>(), - (float)ctrl_info.min().get<float>(), - (float)ctrl_info.max().get<float>()), + def, min, max), 0); - break; - case ControlTypeInteger32: + } break; + case ControlTypeInteger32: { + int32_t min = ctrl_info.min().get<int32_t>(); + int32_t max = ctrl_info.max().get<int32_t>(); + int32_t def; + + if (ctrl_info.def().isNone()) + def = (min + max) / 2; + else + def = ctrl_info.def().get<int32_t>(); + spa_pod_builder_add(&b, SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int( - (int32_t)ctrl_info.def().get<int32_t>(), - (int32_t)ctrl_info.min().get<int32_t>(), - (int32_t)ctrl_info.max().get<int32_t>()), + def, min, max), 0); - break; + } break; default: goto next; } @@ -678,6 +698,31 @@ static int spa_libcamera_use_buffers(struct impl *impl, struct port *port, return -ENOTSUP; } +static const struct { + Transform libcamera_transform; + uint32_t spa_transform_value; +} transform_map[] = { + { Transform::Identity, SPA_META_TRANSFORMATION_None }, + { Transform::Rot0, SPA_META_TRANSFORMATION_None }, + { Transform::HFlip, SPA_META_TRANSFORMATION_Flipped }, + { Transform::VFlip, SPA_META_TRANSFORMATION_Flipped180 }, + { Transform::HVFlip, SPA_META_TRANSFORMATION_180 }, + { Transform::Rot180, SPA_META_TRANSFORMATION_180 }, + { Transform::Transpose, SPA_META_TRANSFORMATION_Flipped90 }, + { Transform::Rot90, SPA_META_TRANSFORMATION_90 }, + { Transform::Rot270, SPA_META_TRANSFORMATION_270 }, + { Transform::Rot180Transpose, SPA_META_TRANSFORMATION_Flipped270 }, +}; + +static uint32_t libcamera_transform_to_spa_transform_value(Transform transform) +{ + for (const auto& t : transform_map) { + if (t.libcamera_transform == transform) + return t.spa_transform_value; + } + return SPA_META_TRANSFORMATION_None; +} + static int mmap_init(struct impl *impl, struct port *port, struct spa_buffer **buffers, uint32_t n_buffers) @@ -722,6 +767,16 @@ mmap_init(struct impl *impl, struct port *port, b->flags = BUFFER_FLAG_OUTSTANDING; b->h = (struct spa_meta_header*)spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); + b->videotransform = (struct spa_meta_videotransform*)spa_buffer_find_meta_data( + buffers[i], SPA_META_VideoTransform, sizeof(*b->videotransform)); + if (b->videotransform) { + b->videotransform->transform = + libcamera_transform_to_spa_transform_value(impl->config->transform); + spa_log_debug(impl->log, "Setting videotransform for buffer %d to %u (from %s)", + i, b->videotransform->transform, transformToString(impl->config->transform)); + + } + d = buffers[i]->datas; for(j = 0; j < buffers[i]->n_datas; ++j) { d[j].type = port->memtype; diff --git a/spa/plugins/support/loop.c b/spa/plugins/support/loop.c index dd308cfda99a3445c0eeba1c9f358fbf1595a34e..3c3e020eefa68f1aa38fa80fe8384bbe048bf4d6 100644 --- a/spa/plugins/support/loop.c +++ b/spa/plugins/support/loop.c @@ -89,7 +89,7 @@ struct impl { uint8_t *buffer_data; uint8_t buffer_mem[DATAS_SIZE + MAX_ALIGN]; - unsigned int flushing:1; + uint32_t flush_count; unsigned int polling:1; }; @@ -166,23 +166,39 @@ static int loop_remove_source(void *object, struct spa_source *source) static void flush_items(struct impl *impl) { - uint32_t index; + uint32_t index, flush_count; + int32_t avail; int res; - impl->flushing = true; - while (spa_ringbuffer_get_read_index(&impl->buffer, &index) > 0) { + flush_count = ++impl->flush_count; + avail = spa_ringbuffer_get_read_index(&impl->buffer, &index); + while (avail > 0) { struct invoke_item *item; bool block; + spa_invoke_func_t func; item = SPA_PTROFF(impl->buffer_data, index & (DATAS_SIZE - 1), struct invoke_item); block = item->block; + func = item->func; spa_log_trace_fp(impl->log, "%p: flush item %p", impl, item); - item->res = item->func ? item->func(&impl->loop, - true, item->seq, item->data, item->size, - item->user_data) : 0; - - spa_ringbuffer_read_update(&impl->buffer, index + item->item_size); + /* first we remove the function from the item so that recursive + * calls don't call the callback again. We can't update the + * read index before we call the function because then the item + * might get overwritten. */ + item->func = NULL; + if (func) + item->res = func(&impl->loop, true, item->seq, item->data, + item->size, item->user_data); + + /* if this function did a recursive invoke, it now flushed the + * ringbuffer and we can exit */ + if (flush_count != impl->flush_count) + break; + + index += item->item_size; + avail -= item->item_size; + spa_ringbuffer_read_update(&impl->buffer, index); if (block) { if ((res = spa_system_eventfd_write(impl->system, impl->ack_fd, 1)) < 0) @@ -190,20 +206,21 @@ static void flush_items(struct impl *impl) impl, impl->ack_fd, spa_strerror(res)); } } - impl->flushing = false; } static int loop_invoke_inthread(struct impl *impl, - spa_invoke_func_t func, - uint32_t seq, - const void *data, - size_t size, - bool block, - void *user_data) + spa_invoke_func_t func, + uint32_t seq, + const void *data, + size_t size, + bool block, + void *user_data) { - if (!impl->flushing) - flush_items(impl); + /* we should probably have a second ringbuffer for the in-thread pending + * callbacks. A recursive callback when flushing will insert itself + * before this one. */ + flush_items(impl); return func ? func(&impl->loop, true, seq, data, size, user_data) : 0; } @@ -222,6 +239,9 @@ loop_invoke(void *object, int32_t filled; uint32_t avail, idx, offset, l0; + /* the ringbuffer can only be written to from one thread, if we are + * in the same thread as the loop, don't write into the ringbuffer + * but try to emit the calback right away after flushing what we have */ if (impl->thread == 0 || pthread_equal(impl->thread, pthread_self())) return loop_invoke_inthread(impl, func, seq, data, size, block, user_data); @@ -247,6 +267,7 @@ loop_invoke(void *object, item->size = size; item->block = block; item->user_data = user_data; + item->res = 0; item->item_size = SPA_ROUND_UP_N(sizeof(struct invoke_item) + size, ITEM_ALIGN); spa_log_trace_fp(impl->log, "%p: add item %p filled:%d", impl, item, filled); @@ -585,10 +606,12 @@ static void source_event_func(struct spa_source *source) uint64_t count = 0; int res; - if ((res = spa_system_eventfd_read(s->impl->system, source->fd, &count)) < 0) - spa_log_warn(s->impl->log, "%p: failed to read event fd:%d: %s", - source, source->fd, spa_strerror(res)); - + if ((res = spa_system_eventfd_read(s->impl->system, source->fd, &count)) < 0) { + if (res != -EAGAIN) + spa_log_warn(s->impl->log, "%p: failed to read event fd:%d: %s", + source, source->fd, spa_strerror(res)); + return; + } s->func.event(source->data, count); } @@ -651,10 +674,12 @@ static void source_timer_func(struct spa_source *source) int res; if (SPA_UNLIKELY((res = spa_system_timerfd_read(s->impl->system, - source->fd, &expirations)) < 0)) - spa_log_warn(s->impl->log, "%p: failed to read timer fd:%d: %s", - source, source->fd, spa_strerror(res)); - + source->fd, &expirations)) < 0)) { + if (res != -EAGAIN) + spa_log_warn(s->impl->log, "%p: failed to read timer fd:%d: %s", + source, source->fd, spa_strerror(res)); + return; + } s->func.timer(source->data, expirations); } @@ -731,10 +756,12 @@ static void source_signal_func(struct spa_source *source) struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); int res, signal_number = 0; - if ((res = spa_system_signalfd_read(s->impl->system, source->fd, &signal_number)) < 0) - spa_log_warn(s->impl->log, "%p: failed to read signal fd:%d: %s", - source, source->fd, spa_strerror(res)); - + if ((res = spa_system_signalfd_read(s->impl->system, source->fd, &signal_number)) < 0) { + if (res != -EAGAIN) + spa_log_warn(s->impl->log, "%p: failed to read signal fd:%d: %s", + source, source->fd, spa_strerror(res)); + return; + } s->func.signal(source->data, signal_number); } diff --git a/spa/plugins/support/node-driver.c b/spa/plugins/support/node-driver.c index 7fb6027ba668c7712e7e70380ba296f98fab4764..9701a478c1ff3b65188411278ecec0bb9fb7246e 100644 --- a/spa/plugins/support/node-driver.c +++ b/spa/plugins/support/node-driver.c @@ -32,6 +32,7 @@ #include <spa/support/log.h> #include <spa/support/loop.h> #include <spa/utils/names.h> +#include <spa/utils/result.h> #include <spa/utils/string.h> #include <spa/node/node.h> #include <spa/node/keys.h> @@ -180,14 +181,16 @@ static void on_timeout(struct spa_source *source) struct impl *this = source->data; uint64_t expirations, nsec, duration; uint32_t rate; + int res; spa_log_trace(this->log, "timeout"); - if (spa_system_timerfd_read(this->data_system, - this->timer_source.fd, &expirations) < 0) { - if (errno == EAGAIN) - return; - perror("read timerfd"); + if ((res = spa_system_timerfd_read(this->data_system, + this->timer_source.fd, &expirations)) < 0) { + if (res != EAGAIN) + spa_log_error(this->log, NAME " %p: timerfd error: %s", + this, spa_strerror(res)); + return; } nsec = this->next_time; diff --git a/spa/plugins/support/null-audio-sink.c b/spa/plugins/support/null-audio-sink.c index e42c3c1c8ecf7891c746f4b7311f0e9344ee2098..c39c15afbe8ac26f8bbaf4d6fda496072f7a8a3b 100644 --- a/spa/plugins/support/null-audio-sink.c +++ b/spa/plugins/support/null-audio-sink.c @@ -35,6 +35,7 @@ #include <spa/utils/list.h> #include <spa/utils/keys.h> #include <spa/utils/json.h> +#include <spa/utils/result.h> #include <spa/utils/string.h> #include <spa/node/node.h> #include <spa/node/utils.h> @@ -282,14 +283,16 @@ static void on_timeout(struct spa_source *source) struct impl *this = source->data; uint64_t expirations, nsec, duration = 10; uint32_t rate; + int res; spa_log_trace(this->log, "timeout"); - if (spa_system_timerfd_read(this->data_system, - this->timer_source.fd, &expirations) < 0) { - if (errno == EAGAIN) - return; - perror("read timerfd"); + if ((res = spa_system_timerfd_read(this->data_system, + this->timer_source.fd, &expirations)) < 0) { + if (res != EAGAIN) + spa_log_error(this->log, NAME " %p: timerfd error: %s", + this, spa_strerror(res)); + return; } nsec = this->next_time; diff --git a/spa/plugins/test/fakesink.c b/spa/plugins/test/fakesink.c index 104f27eb9da639e30c1d4f57ae7aaf6e277e4695..3c42b223924345a77fc0db5decdf94bb6253a47e 100644 --- a/spa/plugins/test/fakesink.c +++ b/spa/plugins/test/fakesink.c @@ -32,6 +32,7 @@ #include <spa/support/log.h> #include <spa/support/loop.h> #include <spa/utils/list.h> +#include <spa/utils/result.h> #include <spa/utils/string.h> #include <spa/node/node.h> #include <spa/node/utils.h> @@ -215,15 +216,20 @@ static void set_timer(struct impl *this, bool enabled) } } -static inline void read_timer(struct impl *this) +static inline int read_timer(struct impl *this) { uint64_t expirations; + int res = 0; if (this->callbacks.funcs || this->props.live) { - if (spa_system_timerfd_read(this->data_system, - this->timer_source.fd, &expirations) < 0) - perror("read timerfd"); + if ((res = spa_system_timerfd_read(this->data_system, + this->timer_source.fd, &expirations)) < 0) { + if (res != -EAGAIN) + spa_log_error(this->log, NAME " %p: timerfd error: %s", + this, spa_strerror(res)); + } } + return res; } static void render_buffer(struct impl *this, struct buffer *b) @@ -237,7 +243,8 @@ static int consume_buffer(struct impl *this) struct spa_io_buffers *io = port->io; int n_bytes; - read_timer(this); + if (read_timer(this) < 0) + return 0; if (spa_list_is_empty(&port->ready)) { io->status = SPA_STATUS_NEED_DATA; diff --git a/spa/plugins/test/fakesrc.c b/spa/plugins/test/fakesrc.c index 6dde68455d1c56d68f7ed7e482a3c87f808f9b9f..d4965a98b28f1cd64e94094c349095617d5f264e 100644 --- a/spa/plugins/test/fakesrc.c +++ b/spa/plugins/test/fakesrc.c @@ -32,6 +32,7 @@ #include <spa/support/log.h> #include <spa/support/loop.h> #include <spa/utils/list.h> +#include <spa/utils/result.h> #include <spa/utils/string.h> #include <spa/node/node.h> #include <spa/node/utils.h> @@ -230,15 +231,20 @@ static void set_timer(struct impl *this, bool enabled) } } -static inline void read_timer(struct impl *this) +static inline int read_timer(struct impl *this) { uint64_t expirations; + int res = 0; if (this->callbacks.funcs || this->props.live) { - if (spa_system_timerfd_read(this->data_system, - this->timer_source.fd, &expirations) < 0) - perror("read timerfd"); + if ((res = spa_system_timerfd_read(this->data_system, + this->timer_source.fd, &expirations)) < 0) { + if (res != -EAGAIN) + spa_log_error(this->log, NAME " %p: timerfd error: %s", + this, spa_strerror(res)); + } } + return res; } static int make_buffer(struct impl *this) @@ -248,7 +254,8 @@ static int make_buffer(struct impl *this) struct spa_io_buffers *io = port->io; int n_bytes; - read_timer(this); + if (read_timer(this) < 0) + return 0; if (spa_list_is_empty(&port->empty)) { set_timer(this, false); diff --git a/spa/plugins/videotestsrc/videotestsrc.c b/spa/plugins/videotestsrc/videotestsrc.c index f2f42c0609689ccf9ae931e48d1b8b88aff31a27..af3bc9688ffcafd1dd567f04cf94283680fa1a0f 100644 --- a/spa/plugins/videotestsrc/videotestsrc.c +++ b/spa/plugins/videotestsrc/videotestsrc.c @@ -33,6 +33,7 @@ #include <spa/support/loop.h> #include <spa/utils/list.h> #include <spa/utils/keys.h> +#include <spa/utils/result.h> #include <spa/utils/string.h> #include <spa/node/node.h> #include <spa/node/utils.h> @@ -280,14 +281,20 @@ static void set_timer(struct impl *this, bool enabled) } } -static void read_timer(struct impl *this) +static int read_timer(struct impl *this) { uint64_t expirations; + int res = 0; if (this->async || this->props.live) { - if (spa_system_timerfd_read(this->data_system, this->timer_source.fd, &expirations) < 0) - perror("read timerfd"); + if ((res = spa_system_timerfd_read(this->data_system, + this->timer_source.fd, &expirations)) < 0) { + if (res != -EAGAIN) + spa_log_error(this->log, NAME " %p: timerfd error: %s", + this, spa_strerror(res)); + } } + return res; } static int make_buffer(struct impl *this) @@ -297,7 +304,8 @@ static int make_buffer(struct impl *this) struct spa_io_buffers *io = port->io; uint32_t n_bytes; - read_timer(this); + if (read_timer(this) < 0) + return 0; if (spa_list_is_empty(&port->empty)) { set_timer(this, false); diff --git a/spa/plugins/vulkan/vulkan-compute-source.c b/spa/plugins/vulkan/vulkan-compute-source.c index 8cce5489f6475570203f2497dfaa751866c8af16..dade5a5d35f8a043f5ce0e951c9394500a205e87 100644 --- a/spa/plugins/vulkan/vulkan-compute-source.c +++ b/spa/plugins/vulkan/vulkan-compute-source.c @@ -34,6 +34,7 @@ #include <spa/utils/list.h> #include <spa/utils/keys.h> #include <spa/utils/names.h> +#include <spa/utils/result.h> #include <spa/utils/string.h> #include <spa/node/node.h> #include <spa/node/utils.h> @@ -267,14 +268,20 @@ static void set_timer(struct impl *this, bool enabled) } } -static void read_timer(struct impl *this) +static int read_timer(struct impl *this) { uint64_t expirations; + int res = 0; if (this->async || this->props.live) { - if (spa_system_timerfd_read(this->data_system, this->timer_source.fd, &expirations) < 0) - perror("read timerfd"); + if ((res = spa_system_timerfd_read(this->data_system, + this->timer_source.fd, &expirations)) < 0) { + if (res != -EAGAIN) + spa_log_error(this->log, NAME " %p: timerfd error: %s", + this, spa_strerror(res)); + } } + return res; } static int make_buffer(struct impl *this) @@ -284,7 +291,8 @@ static int make_buffer(struct impl *this) uint32_t n_bytes; int res; - read_timer(this); + if (read_timer(this) < 0) + return 0; if ((res = spa_vulkan_ready(&this->state)) < 0) { res = SPA_STATUS_OK; diff --git a/src/daemon/client-rt.conf.in b/src/daemon/client-rt.conf.in index f62b1e2281054af0e073362ff8ab4789645130ac..ba46bd421afd66740450eb981d7138a4aea51d79 100644 --- a/src/daemon/client-rt.conf.in +++ b/src/daemon/client-rt.conf.in @@ -82,7 +82,7 @@ stream.properties = { #node.autoconnect = true #resample.quality = 4 #channelmix.normalize = false - #channelmix.mix-lfe = false + #channelmix.mix-lfe = true #channelmix.upmix = true #channelmix.upmix-method = psd # none, simple #channelmix.lfe-cutoff = 150 diff --git a/src/daemon/client.conf.in b/src/daemon/client.conf.in index 3931149f7e842bcaf6f7af8556eeafd1cadbee79..b465eb6445cb5d9bf78048cd0c983490ca64c83e 100644 --- a/src/daemon/client.conf.in +++ b/src/daemon/client.conf.in @@ -73,7 +73,7 @@ stream.properties = { #node.autoconnect = true #resample.quality = 4 #channelmix.normalize = false - #channelmix.mix-lfe = false + #channelmix.mix-lfe = true #channelmix.upmix = true #channelmix.upmix-method = psd # none, simple #channelmix.lfe-cutoff = 150 diff --git a/src/daemon/minimal.conf.in b/src/daemon/minimal.conf.in index 8de1f21e1f76677a1acd2ce078997470d2fb65c2..4a3a2cb0740295a44112a5b8663ba27e51bb5bee 100644 --- a/src/daemon/minimal.conf.in +++ b/src/daemon/minimal.conf.in @@ -203,7 +203,7 @@ context.objects = [ resample.disable = true #monitor.channel-volumes = false #channelmix.normalize = false - #channelmix.mix-lfe = false + #channelmix.mix-lfe = true #channelmix.upmix = true #channelmix.upmix-method = psd # none, simple #channelmix.lfe-cutoff = 150 @@ -265,7 +265,7 @@ context.objects = [ #resample.quality = 4 resample.disable = true #channelmix.normalize = false - #channelmix.mix-lfe = false + #channelmix.mix-lfe = true #channelmix.upmix = true #channelmix.upmix-method = psd # none, simple #channelmix.lfe-cutoff = 150 diff --git a/src/daemon/pipewire-avb.conf.in b/src/daemon/pipewire-avb.conf.in index b4e465f07082948d767784c8ae3670567d4f177f..68f89ca4c915270037a2a967aee3b67f74781944 100644 --- a/src/daemon/pipewire-avb.conf.in +++ b/src/daemon/pipewire-avb.conf.in @@ -54,7 +54,7 @@ stream.properties = { #node.autoconnect = true #resample.quality = 4 #channelmix.normalize = false - #channelmix.mix-lfe = false + #channelmix.mix-lfe = true #channelmix.upmix = true #channelmix.lfe-cutoff = 120 #channelmix.fc-cutoff = 6000 diff --git a/src/daemon/pipewire-pulse.conf.in b/src/daemon/pipewire-pulse.conf.in index 2d381a14542007bd6ee6679cec974247e0914318..18bca3a1ec3e77b622685b1bde668c96457c8f3f 100644 --- a/src/daemon/pipewire-pulse.conf.in +++ b/src/daemon/pipewire-pulse.conf.in @@ -46,19 +46,30 @@ context.modules = [ } ] -# Extra modules can be loaded here. Setup in default.pa can be moved here +# Extra scripts can be started here. Setup in default.pa can be moved in +# a script or in pulse.cmd below context.exec = [ - { path = "pactl" args = "load-module module-always-sink" } - #{ path = "pactl" args = "load-module module-switch-on-connect" } + #{ path = "pactl" args = "load-module module-always-sink" } + #{ path = "pactl" args = "upload-sample my-sample.wav my-sample" } #{ path = "/usr/bin/sh" args = "~/.config/pipewire/default.pw" } ] +# Extra commands can be executed here. +# load-module : loads a module with args and flags +# args = "<module-name> <module-args>" +# flags = [ "no-fail" ] +pulse.cmd = [ + { cmd = "load-module" args = "module-always-sink" flags = [ ] } + #{ cmd = "load-module" args = "module-switch-on-connect" } + #{ cmd = "load-module" args = "module-gsettings" flags = [ "nofail" ] } +] + stream.properties = { #node.latency = 1024/48000 #node.autoconnect = true #resample.quality = 4 #channelmix.normalize = false - #channelmix.mix-lfe = false + #channelmix.mix-lfe = true #channelmix.upmix = true #channelmix.upmix-method = psd # none, simple #channelmix.lfe-cutoff = 150 @@ -90,7 +101,7 @@ pulse.properties = { #pulse.default.frag = 96000/48000 # 2 seconds #pulse.default.tlength = 96000/48000 # 2 seconds #pulse.min.quantum = 256/48000 # 5ms - #pulse.idle.timeout = 5 # pause after 5s of underruns + #pulse.idle.timeout = 0 # don't pause after underruns #pulse.default.format = F32 #pulse.default.position = [ FL FR ] # These overrides are only applied when running in a vm. @@ -137,12 +148,12 @@ pulse.rules = [ } { # speech dispatcher asks for too small latency and then underruns. - matches = [ { application.name = "~speech-dispatcher*" } ] + matches = [ { application.name = "~speech-dispatcher.*" } ] actions = { update-props = { - pulse.min.req = 1024/48000 # 21ms - pulse.min.quantum = 1024/48000 # 21ms - #pulse.idle.timeout = 0 + pulse.min.req = 512/48000 # 10.6ms + pulse.min.quantum = 512/48000 # 10.6ms + pulse.idle.timeout = 5 # pause after 5 seconds of underrun } } } diff --git a/src/examples/video-dsp-play.c b/src/examples/video-dsp-play.c index 71f9b01998030de63cc47534c9bd8a0dca51ce3c..6068e2a0f761f3a3825d766ab89a563c13981989 100644 --- a/src/examples/video-dsp-play.c +++ b/src/examples/video-dsp-play.c @@ -139,6 +139,8 @@ on_process(void *_data, struct spa_io_position *position) /* copy video image in texture */ sstride = buf->datas[0].chunk->stride; + if (sstride == 0) + sstride = buf->datas[0].chunk->size / data->position->video.size.height; src = sdata; dst = ddata; diff --git a/src/examples/video-play-fixate.c b/src/examples/video-play-fixate.c index 021eb0df57f0f857cbdbe1f8845f24942c1e1c79..7477c5a06d3f439b114784cd4bd59168463b5e97 100644 --- a/src/examples/video-play-fixate.c +++ b/src/examples/video-play-fixate.c @@ -257,6 +257,8 @@ on_process(void *_data) /* copy video image in texture */ sstride = buf->datas[0].chunk->stride; + if (sstride == 0) + sstride = buf->datas[0].chunk->size / data->size.height; ostride = SPA_MIN(sstride, dstride); src = sdata; diff --git a/src/examples/video-play-pull.c b/src/examples/video-play-pull.c index 8076779629b4e2c66a56b06f930b04298e570263..fd0e3058940953eba1b48ded86f51bca3bd2875b 100644 --- a/src/examples/video-play-pull.c +++ b/src/examples/video-play-pull.c @@ -206,6 +206,8 @@ on_process(void *_data) } sstride = buf->datas[0].chunk->stride; + if (sstride == 0) + sstride = buf->datas[0].chunk->size / data->size.height; ostride = SPA_MIN(sstride, dstride); src = sdata; diff --git a/src/examples/video-play-reneg.c b/src/examples/video-play-reneg.c index f37b66285e6485a488cf93e912a5584f33f7e79c..26b19cb595e1f1ff440a80de0c295ed6c68c44cf 100644 --- a/src/examples/video-play-reneg.c +++ b/src/examples/video-play-reneg.c @@ -136,6 +136,8 @@ on_process(void *_data) /* copy video image in texture */ sstride = buf->datas[0].chunk->stride; + if (sstride == 0) + sstride = buf->datas[0].chunk->size / data->size.height; ostride = SPA_MIN(sstride, dstride); src = sdata; diff --git a/src/examples/video-play.c b/src/examples/video-play.c index 61c3114f798d85f4c7da36b9c339c2fa8b6e96d5..9cbbab6987a9ff8921496346db9df791ae79e7ad 100644 --- a/src/examples/video-play.c +++ b/src/examples/video-play.c @@ -204,6 +204,8 @@ on_process(void *_data) } sstride = buf->datas[0].chunk->stride; + if (sstride == 0) + sstride = buf->datas[0].chunk->size / data->size.height; ostride = SPA_MIN(sstride, dstride); src = sdata; diff --git a/src/gst/gstpipewirepool.c b/src/gst/gstpipewirepool.c index e7b57469d49d15a94348e08f463291e646df68a1..7a298b1db0820af59712e206805be33e98ef57a7 100644 --- a/src/gst/gstpipewirepool.c +++ b/src/gst/gstpipewirepool.c @@ -115,6 +115,8 @@ void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *b) data->crop = spa_buffer_find_meta_data (b->buffer, SPA_META_VideoCrop, sizeof(*data->crop)); if (data->crop) gst_buffer_add_video_crop_meta(buf); + data->videotransform = + spa_buffer_find_meta_data (b->buffer, SPA_META_VideoTransform, sizeof(*data->videotransform)); gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (buf), pool_data_quark, diff --git a/src/gst/gstpipewirepool.h b/src/gst/gstpipewirepool.h index b7b7ca8a754746a2b05873cbda3d4422736b114c..acf81064f2a3b2a0689c3ab2e8867acc06fb7f53 100644 --- a/src/gst/gstpipewirepool.h +++ b/src/gst/gstpipewirepool.h @@ -57,6 +57,7 @@ struct _GstPipeWirePoolData { GstBuffer *buf; gboolean queued; struct spa_meta_region *crop; + struct spa_meta_videotransform *videotransform; }; struct _GstPipeWirePool { diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c index 16234f51d8bcafced38841b91f0622c64b23d8b5..c7dbe38c5c19052cc240f91d49ae879da40b6ca9 100644 --- a/src/gst/gstpipewiresink.c +++ b/src/gst/gstpipewiresink.c @@ -491,6 +491,7 @@ do_send_buffer (GstPipeWireSink *pwsink, GstBuffer *buffer) GstMemory *mem = gst_buffer_peek_memory (buffer, i); d->chunk->offset = mem->offset; d->chunk->size = mem->size; + d->chunk->stride = 0; } if ((res = pw_stream_queue_buffer (pwsink->stream, data->b)) < 0) { diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 4e8e8bd4e7f10f846027443bbb7aadecc6108907..0b222aa0e1fa4d7b09174bde840f4961db025625 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -514,6 +514,25 @@ on_remove_buffer (void *_data, struct pw_buffer *b) } } +static const char * const transform_map[] = { + [SPA_META_TRANSFORMATION_None] = "rotate-0", + [SPA_META_TRANSFORMATION_90] = "rotate-90", + [SPA_META_TRANSFORMATION_180] = "rotate-180", + [SPA_META_TRANSFORMATION_270] = "rotate-270", + [SPA_META_TRANSFORMATION_Flipped] = "flip-rotate-0", + [SPA_META_TRANSFORMATION_Flipped90] = "flip-rotate-270", + [SPA_META_TRANSFORMATION_Flipped180] = "flip-rotate-180", + [SPA_META_TRANSFORMATION_Flipped270] = "flip-rotate-90", +}; + +static const char *spa_transform_value_to_gst_image_orientation(uint32_t transform_value) +{ + if (transform_value >= SPA_N_ELEMENTS(transform_map)) + transform_value = SPA_META_TRANSFORMATION_None; + + return transform_map[transform_value]; +} + static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc) { struct pw_buffer *b; @@ -521,6 +540,7 @@ static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc) GstPipeWirePoolData *data; struct spa_meta_header *h; struct spa_meta_region *crop; + struct spa_meta_videotransform *videotransform; guint i; b = pw_stream_dequeue_buffer (pwsrc->stream); @@ -568,6 +588,27 @@ static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc) meta->height = crop->region.size.height; } } + + videotransform = data->videotransform; + if (videotransform) { + if (pwsrc->transform_value != videotransform->transform) { + GstEvent *tag_event; + const char* tag_string; + + tag_string = + spa_transform_value_to_gst_image_orientation(videotransform->transform); + + GST_LOG_OBJECT (pwsrc, "got new videotransform: %u / %s", + videotransform->transform, tag_string); + + tag_event = gst_event_new_tag(gst_tag_list_new(GST_TAG_IMAGE_ORIENTATION, + tag_string, NULL)); + gst_pad_push_event (GST_BASE_SRC_PAD (pwsrc), tag_event); + + pwsrc->transform_value = videotransform->transform; + } + } + for (i = 0; i < b->buffer->n_datas; i++) { struct spa_data *d = &b->buffer->datas[i]; GstMemory *pmem = gst_buffer_peek_memory (data->buf, i); @@ -913,7 +954,7 @@ on_param_changed (void *data, uint32_t id, pwsrc->negotiated = pwsrc->caps != NULL; if (pwsrc->negotiated) { - const struct spa_pod *params[3]; + const struct spa_pod *params[4]; struct spa_pod_builder b = { NULL }; uint8_t buffer[512]; uint32_t buffers = CLAMP (16, pwsrc->min_buffers, pwsrc->max_buffers); @@ -939,9 +980,13 @@ on_param_changed (void *data, uint32_t id, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop), SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_region))); + params[3] = spa_pod_builder_add_object (&b, + SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoTransform), + SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_videotransform))); GST_DEBUG_OBJECT (pwsrc, "doing finish format"); - pw_stream_update_params (pwsrc->stream, params, 3); + pw_stream_update_params (pwsrc->stream, params, SPA_N_ELEMENTS(params)); } else { GST_WARNING_OBJECT (pwsrc, "finish format with error"); pw_stream_set_error (pwsrc->stream, -EINVAL, "unhandled format"); diff --git a/src/gst/gstpipewiresrc.h b/src/gst/gstpipewiresrc.h index 128cf39438f58dfc95dd3dcdb89dff015abeadb0..e1def85b3ce072305c38e56ca139e99575a7a9e2 100644 --- a/src/gst/gstpipewiresrc.h +++ b/src/gst/gstpipewiresrc.h @@ -95,6 +95,8 @@ struct _GstPipeWireSrc { GstPipeWirePool *pool; GstClock *clock; GstClockTime last_time; + + enum spa_meta_videotransform_value transform_value; }; struct _GstPipeWireSrcClass { diff --git a/src/modules/meson.build b/src/modules/meson.build index c267e681cd0437852b09b9c56c11bbb57a80501e..367031e98a96980ea106bbdd9fda12866a45933f 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -100,7 +100,7 @@ filter_chain_sources = [ 'module-filter-chain/convolver.c' ] filter_chain_dependencies = [ - mathlib, dl_lib, pipewire_dep, sndfile_dep + mathlib, dl_lib, pipewire_dep, sndfile_dep, audioconvert_dep ] if lilv_lib.found() @@ -226,6 +226,7 @@ pipewire_module_protocol_pulse_sources = [ 'module-protocol-pulse.c', 'module-protocol-pulse/client.c', 'module-protocol-pulse/collect.c', + 'module-protocol-pulse/cmd.c', 'module-protocol-pulse/extension.c', 'module-protocol-pulse/extensions/ext-device-manager.c', 'module-protocol-pulse/extensions/ext-device-restore.c', @@ -289,6 +290,14 @@ if avahi_dep.found() cdata.set('HAVE_AVAHI', true) endif +if gio_dep.found() + pipewire_module_protocol_pulse_sources += [ + 'module-protocol-pulse/modules/module-gsettings.c', + ] + pipewire_module_protocol_pulse_deps += gio_dep + cdata.set('HAVE_GIO', true) +endif + if flatpak_support pipewire_module_protocol_pulse_deps += glib2_dep endif diff --git a/src/modules/module-client-node/remote-node.c b/src/modules/module-client-node/remote-node.c index 387711515ec4c16f00df59e92f01661b79b76327..051ab0716205f11918a5bc1dd3d16e24f43f14dc 100644 --- a/src/modules/module-client-node/remote-node.c +++ b/src/modules/module-client-node/remote-node.c @@ -1186,7 +1186,7 @@ static int node_ready(void *d, int status) struct timespec ts; struct pw_impl_port *p; - pw_log_trace("node %p: ready driver:%d exported:%d status:%d", node, + pw_log_trace_fp("node %p: ready driver:%d exported:%d status:%d", node, node->driver, node->exported, status); if (status & SPA_STATUS_HAVE_DATA) { diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index 7e0d81a79fe9f2c9fa8136032006a5b735f38aa4..9dba8c51b083a7209be6b94b4786fe92b91559e2 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -936,8 +936,13 @@ static void core_error(void *data, uint32_t id, int seq, int res, const char *me { struct impl *impl = data; - pw_log_error("error id:%u seq:%d res:%d (%s): %s", - id, seq, res, spa_strerror(res), message); + if (res == -ENOENT) { + pw_log_info("id:%u seq:%d res:%d (%s): %s", + id, seq, res, spa_strerror(res), message); + } else { + pw_log_warn("error id:%u seq:%d res:%d (%s): %s", + id, seq, res, spa_strerror(res), message); + } if (id == PW_ID_CORE && res == -EPIPE) pw_impl_module_schedule_destroy(impl->module); diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index a8f89e394caa8af107baae178a9bb5a91bb00baf..bee9e3e28661ab6d6872dea7c75fbd6ebcf96150 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -505,6 +505,7 @@ struct node { unsigned int n_deps; unsigned int visited:1; + unsigned int disabled:1; }; struct link { @@ -521,6 +522,7 @@ struct graph_port { const struct fc_descriptor *desc; void **hndl; uint32_t port; + unsigned next:1; }; struct graph_hndl { @@ -599,7 +601,7 @@ static void playback_process(void *d) struct impl *impl = d; struct pw_buffer *in, *out; struct graph *graph = &impl->graph; - uint32_t i, insize = 0, outsize = 0, n_hndl = graph->n_hndl; + uint32_t i, j, insize = 0, outsize = 0, n_hndl = graph->n_hndl; int32_t stride = 0; struct graph_port *port; struct spa_data *bd; @@ -613,7 +615,7 @@ static void playback_process(void *d) if (in == NULL || out == NULL) goto done; - for (i = 0; i < in->buffer->n_datas; i++) { + for (i = 0, j = 0; i < in->buffer->n_datas; i++) { uint32_t offs, size; bd = &in->buffer->datas[i]; @@ -621,12 +623,15 @@ static void playback_process(void *d) offs = SPA_MIN(bd->chunk->offset, bd->maxsize); size = SPA_MIN(bd->chunk->size, bd->maxsize - offs); - port = i < graph->n_input ? &graph->input[i] : NULL; - - if (port && port->desc) - port->desc->connect_port(*port->hndl, port->port, - SPA_PTROFF(bd->data, offs, void)); + while (j < graph->n_input) { + port = &graph->input[j++]; + if (port->desc) + port->desc->connect_port(*port->hndl, port->port, + SPA_PTROFF(bd->data, offs, void)); + if (!port->next) + break; + } insize = i == 0 ? size : SPA_MIN(insize, size); stride = SPA_MAX(stride, bd->chunk->stride); } @@ -1849,7 +1854,7 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ n_nodes++; } graph->n_input = 0; - graph->input = calloc(n_input * n_hndl, sizeof(struct graph_port)); + graph->input = calloc(n_input * 16 * n_hndl, sizeof(struct graph_port)); graph->n_output = 0; graph->output = calloc(n_output * n_hndl, sizeof(struct graph_port)); @@ -1869,8 +1874,8 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ } else { struct spa_json it = *inputs; while (spa_json_get_string(&it, v, sizeof(v)) > 0) { - gp = &graph->input[graph->n_input]; if (spa_streq(v, "null")) { + gp = &graph->input[graph->n_input++]; gp->desc = NULL; pw_log_info("ignore input port %d", graph->n_input); } else if ((port = find_port(first, v, FC_PORT_INPUT)) == NULL) { @@ -1893,14 +1898,41 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ res = -EBUSY; goto error; } - pw_log_info("input port %s[%d]:%s", + + if (d->flags & FC_DESCRIPTOR_COPY) { + for (j = 0; j < desc->n_output; j++) { + struct port *p = &port->node->output_port[j]; + struct link *link; + + gp = NULL; + spa_list_for_each(link, &p->link_list, output_link) { + struct port *peer = link->input; + + pw_log_info("copy input port %s[%d]:%s", + port->node->name, i, + d->ports[port->p].name); + peer->external = graph->n_input; + gp = &graph->input[graph->n_input++]; + gp->desc = peer->node->desc->desc; + gp->hndl = &peer->node->hndl[i]; + gp->port = peer->p; + gp->next = true; + } + if (gp != NULL) + gp->next = false; + } + port->node->disabled = true; + } else { + pw_log_info("input port %s[%d]:%s", port->node->name, i, d->ports[port->p].name); - port->external = graph->n_input; - gp->desc = d; - gp->hndl = &port->node->hndl[i]; - gp->port = port->p; + port->external = graph->n_input; + gp = &graph->input[graph->n_input++]; + gp->desc = d; + gp->hndl = &port->node->hndl[i]; + gp->port = port->p; + gp->next = false; + } } - graph->n_input++; } } if (outputs == NULL) { @@ -1965,11 +1997,12 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ desc = node->desc; d = desc->desc; - for (i = 0; i < n_hndl; i++) { - gh = &graph->hndl[graph->n_hndl++]; - gh->hndl = &node->hndl[i]; - gh->desc = d; - + if (!node->disabled) { + for (i = 0; i < n_hndl; i++) { + gh = &graph->hndl[graph->n_hndl++]; + gh->hndl = &node->hndl[i]; + gh->desc = d; + } } for (i = 0; i < desc->n_output; i++) { spa_list_for_each(link, &node->output_port[i].link_list, output_link) @@ -2088,8 +2121,13 @@ static void core_error(void *data, uint32_t id, int seq, int res, const char *me { struct impl *impl = data; - pw_log_error("error id:%u seq:%d res:%d (%s): %s", - id, seq, res, spa_strerror(res), message); + if (res == -ENOENT) { + pw_log_info("message id:%u seq:%d res:%d (%s): %s", + id, seq, res, spa_strerror(res), message); + } else { + pw_log_warn("error id:%u seq:%d res:%d (%s): %s", + id, seq, res, spa_strerror(res), message); + } if (id == PW_ID_CORE && res == -EPIPE) pw_impl_module_schedule_destroy(impl->module); diff --git a/src/modules/module-filter-chain/builtin_plugin.c b/src/modules/module-filter-chain/builtin_plugin.c index 6918ad91d08f14a23da2f06a26b7da4f8786111c..49163686e44cfd8a2ee07e435bc67245f1a72d8d 100644 --- a/src/modules/module-filter-chain/builtin_plugin.c +++ b/src/modules/module-filter-chain/builtin_plugin.c @@ -31,7 +31,9 @@ #endif #include <spa/utils/json.h> +#include <spa/utils/result.h> #include <spa/support/cpu.h> +#include <spa/plugins/audioconvert/resample.h> #include <pipewire/log.h> @@ -101,6 +103,7 @@ static struct fc_port copy_ports[] = { static const struct fc_descriptor copy_desc = { .name = "copy", + .flags = FC_DESCRIPTOR_COPY, .n_ports = 2, .ports = copy_ports, @@ -260,14 +263,11 @@ static struct fc_port bq_ports[] = { static void bq_run(struct builtin *impl, unsigned long samples, int type) { struct biquad *bq = &impl->bq; - unsigned long i; float *out = impl->port[0]; float *in = impl->port[1]; float freq = impl->port[2][0]; float Q = impl->port[3][0]; float gain = impl->port[4][0]; - float x1, x2, y1, y2; - float b0, b1, b2, a1, a2; if (impl->freq != freq || impl->Q != Q || impl->gain != gain) { impl->freq = freq; @@ -275,30 +275,7 @@ static void bq_run(struct builtin *impl, unsigned long samples, int type) impl->gain = gain; biquad_set(bq, type, freq * 2 / impl->rate, Q, gain); } - x1 = bq->x1; - x2 = bq->x2; - y1 = bq->y1; - y2 = bq->y2; - b0 = bq->b0; - b1 = bq->b1; - b2 = bq->b2; - a1 = bq->a1; - a2 = bq->a2; - for (i = 0; i < samples; i++) { - float x = in[i]; - float y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2; - out[i] = y; - x2 = x1; - x1 = x; - y2 = y1; - y1 = y; - } -#define F(x) (-FLT_MIN < (x) && (x) < FLT_MIN ? 0.0f : (x)) - bq->x1 = F(x1); - bq->x2 = F(x2); - bq->y1 = F(y1); - bq->y2 = F(y2); -#undef F + dsp_ops_biquad_run(&dsp_ops, bq, out, in, samples); } /** bq_lowpass */ @@ -564,6 +541,71 @@ static float *create_dirac(const char *filename, float gain, int delay, int offs return samples; } +static float *resample_buffer(float *samples, int *n_samples, + unsigned long in_rate, unsigned long out_rate, uint32_t quality) +{ + uint32_t in_len, out_len, total_out = 0; + int out_n_samples; + float *out_samples, *out_buf, *in_buf; + struct resample r; + int res; + + spa_zero(r); + r.channels = 1; + r.i_rate = in_rate; + r.o_rate = out_rate; + r.cpu_flags = dsp_ops.cpu_flags; + r.quality = quality; + if ((res = resample_native_init(&r)) < 0) { + pw_log_error("resampling failed: %s", spa_strerror(res)); + errno = -res; + return NULL; + } + + out_n_samples = SPA_ROUND_UP(*n_samples * out_rate, in_rate) / in_rate; + out_samples = calloc(out_n_samples, sizeof(float)); + if (out_samples == NULL) + goto error; + + in_len = *n_samples; + in_buf = samples; + out_len = out_n_samples; + out_buf = out_samples; + + pw_log_info("Resampling filter: rate: %lu => %lu, n_samples: %u => %u, q:%u", + in_rate, out_rate, in_len, out_len, quality); + + resample_process(&r, (void*)&in_buf, &in_len, (void*)&out_buf, &out_len); + pw_log_debug("resampled: %u -> %u samples", in_len, out_len); + total_out += out_len; + + in_len = resample_delay(&r); + in_buf = calloc(in_len, sizeof(float)); + if (in_buf == NULL) + goto error; + + out_buf = out_samples + total_out; + out_len = out_n_samples - total_out; + + pw_log_debug("flushing resampler: %u in %u out", in_len, out_len); + resample_process(&r, (void*)&in_buf, &in_len, (void*)&out_buf, &out_len); + pw_log_debug("flushed: %u -> %u samples", in_len, out_len); + total_out += out_len; + + free(in_buf); + free(samples); + resample_free(&r); + + *n_samples = total_out; + return out_samples; + +error: + resample_free(&r); + free(samples); + free(out_samples); + return NULL; +} + static void * convolver_instantiate(const struct fc_descriptor * Descriptor, unsigned long SampleRate, int index, const char *config) { @@ -576,6 +618,7 @@ static void * convolver_instantiate(const struct fc_descriptor * Descriptor, char filename[PATH_MAX] = ""; int blocksize = 0, tailsize = 0; int delay = 0; + int resample_quality = RESAMPLE_DEFAULT_QUALITY; float gain = 1.0f; unsigned long rate; @@ -636,6 +679,12 @@ static void * convolver_instantiate(const struct fc_descriptor * Descriptor, return NULL; } } + else if (spa_streq(key, "resample_quality")) { + if (spa_json_get_int(&it[1], &resample_quality) <= 0) { + pw_log_error("convolver:resample_quality requires a number"); + return NULL; + } + } else if (spa_json_next(&it[1], &val) < 0) break; } @@ -659,10 +708,9 @@ static void * convolver_instantiate(const struct fc_descriptor * Descriptor, rate = SampleRate; samples = read_samples(filename, gain, delay, offset, length, channel, &rate, &n_samples); - if (rate != SampleRate) { - pw_log_warn("Convolver samplerate %lu doesn't match filter rate %lu. " - "Consider forcing a filter rate.", rate, SampleRate); - } + if (rate != SampleRate) + samples = resample_buffer(samples, &n_samples, + rate, SampleRate, resample_quality); } if (samples == NULL) { errno = ENOENT; diff --git a/src/modules/module-filter-chain/convolver.c b/src/modules/module-filter-chain/convolver.c index fff4fbe179098ea755099514f42c302de31ace6c..f834e7a10112d03a595ce408c66b49a63fe24eec 100644 --- a/src/modules/module-filter-chain/convolver.c +++ b/src/modules/module-filter-chain/convolver.c @@ -420,8 +420,6 @@ void convolver_free(struct convolver *conv) int convolver_run(struct convolver *conv, const float *input, float *output, int length) { - int i; - convolver1_run(conv->headConvolver, input, output, length); if (conv->tailInput) { @@ -431,24 +429,14 @@ int convolver_run(struct convolver *conv, const float *input, float *output, int int remaining = length - processed; int processing = SPA_MIN(remaining, conv->headBlockSize - (conv->tailInputFill % conv->headBlockSize)); - const int sumBegin = processed; - const int sumEnd = processed + processing; - - if (conv->tailPrecalculated0) { - int precalculatedPos = conv->precalculatedPos; - for (i = sumBegin; i < sumEnd; i++) { - output[i] += conv->tailPrecalculated0[precalculatedPos]; - precalculatedPos++; - } - } - - if (conv->tailPrecalculated) { - int precalculatedPos = conv->precalculatedPos; - for (i = sumBegin; i < sumEnd; i++) { - output[i] += conv->tailPrecalculated[precalculatedPos]; - precalculatedPos++; - } - } + if (conv->tailPrecalculated0) + fft_sum(&output[processed], &output[processed], + &conv->tailPrecalculated0[conv->precalculatedPos], + processing); + if (conv->tailPrecalculated) + fft_sum(&output[processed], &output[processed], + &conv->tailPrecalculated[conv->precalculatedPos], + processing); conv->precalculatedPos += processing; fft_copy(conv->tailInput + conv->tailInputFill, input + processed, processing); @@ -467,7 +455,8 @@ int convolver_run(struct convolver *conv, const float *input, float *output, int if (conv->tailPrecalculated && conv->tailInputFill == conv->tailBlockSize) { SPA_SWAP(conv->tailPrecalculated, conv->tailOutput); - convolver1_run(conv->tailConvolver, conv->tailInput, conv->tailOutput, conv->tailBlockSize); + convolver1_run(conv->tailConvolver, conv->tailInput, + conv->tailOutput, conv->tailBlockSize); } if (conv->tailInputFill == conv->tailBlockSize) { conv->tailInputFill = 0; diff --git a/src/modules/module-filter-chain/dsp-ops-c.c b/src/modules/module-filter-chain/dsp-ops-c.c index 885c8cd38135995d91cb31eed940c6436263e689..d559a415b88356e88bd4acdc3309ca832633eb45 100644 --- a/src/modules/module-filter-chain/dsp-ops-c.c +++ b/src/modules/module-filter-chain/dsp-ops-c.c @@ -25,6 +25,7 @@ #include <string.h> #include <stdio.h> #include <math.h> +#include <float.h> #include <spa/utils/defs.h> @@ -98,3 +99,37 @@ void dsp_mix_gain_c(struct dsp_ops *ops, } } } + +void dsp_biquad_run_c(struct dsp_ops *ops, struct biquad *bq, + float *out, const float *in, uint32_t n_samples) +{ + float x1, x2, y1, y2; + float b0, b1, b2, a1, a2; + uint32_t i; + + x1 = bq->x1; + x2 = bq->x2; + y1 = bq->y1; + y2 = bq->y2; + b0 = bq->b0; + b1 = bq->b1; + b2 = bq->b2; + a1 = bq->a1; + a2 = bq->a2; + for (i = 0; i < n_samples; i++) { + float x = in[i]; + float y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2; + out[i] = y; + x2 = x1; + x1 = x; + y2 = y1; + y1 = y; + } +#define F(x) (-FLT_MIN < (x) && (x) < FLT_MIN ? 0.0f : (x)) + bq->x1 = F(x1); + bq->x2 = F(x2); + bq->y1 = F(y1); + bq->y2 = F(y2); +#undef F +} + diff --git a/src/modules/module-filter-chain/dsp-ops.c b/src/modules/module-filter-chain/dsp-ops.c index b40278bf644d27919aed280914407df6ec6c4bff..2fbf5c32fe067c0d8eff4e24c5f7d7bc02133137 100644 --- a/src/modules/module-filter-chain/dsp-ops.c +++ b/src/modules/module-filter-chain/dsp-ops.c @@ -42,6 +42,8 @@ struct dsp_info { void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], float gain[], uint32_t n_src, uint32_t n_samples); + void (*biquad_run) (struct dsp_ops *ops, struct biquad *bq, + float *out, const float *in, uint32_t n_samples); }; static struct dsp_info dsp_table[] = @@ -50,11 +52,13 @@ static struct dsp_info dsp_table[] = { SPA_CPU_FLAG_SSE, .copy = dsp_copy_c, .mix_gain = dsp_mix_gain_sse, + .biquad_run = dsp_biquad_run_c, }, #endif { 0, .copy = dsp_copy_c, .mix_gain = dsp_mix_gain_c, + .biquad_run = dsp_biquad_run_c, }, }; @@ -86,6 +90,7 @@ int dsp_ops_init(struct dsp_ops *ops) ops->cpu_flags = info->cpu_flags; ops->copy = info->copy; ops->mix_gain = info->mix_gain; + ops->biquad_run = info->biquad_run; ops->free = impl_dsp_ops_free; return 0; diff --git a/src/modules/module-filter-chain/dsp-ops.h b/src/modules/module-filter-chain/dsp-ops.h index ffbca6ce7a4d549b1c9d40652a695d8a0fc946c5..3bf7cda19080993489473e275c812ea6b8c1efd5 100644 --- a/src/modules/module-filter-chain/dsp-ops.h +++ b/src/modules/module-filter-chain/dsp-ops.h @@ -24,6 +24,8 @@ #include <spa/utils/defs.h> +#include "biquad.h" + struct dsp_ops { uint32_t cpu_flags; @@ -35,6 +37,8 @@ struct dsp_ops { void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], float gain[], uint32_t n_src, uint32_t n_samples); + void (*biquad_run) (struct dsp_ops *ops, struct biquad *bq, + float *out, const float *in, uint32_t n_samples); void (*free) (struct dsp_ops *ops); const void *priv; @@ -44,19 +48,24 @@ int dsp_ops_init(struct dsp_ops *ops); #define dsp_ops_copy(ops,...) (ops)->copy(ops, __VA_ARGS__) #define dsp_ops_mix_gain(ops,...) (ops)->mix_gain(ops, __VA_ARGS__) +#define dsp_ops_biquad_run(ops,...) (ops)->biquad_run(ops, __VA_ARGS__) #define dsp_ops_free(ops) (ops)->free(ops) #define MAKE_COPY_FUNC(arch) \ void dsp_copy_##arch(struct dsp_ops *ops, void * SPA_RESTRICT dst, \ - const void * SPA_RESTRICT src, uint32_t n_samples) + const void * SPA_RESTRICT src, uint32_t n_samples) #define MAKE_MIX_GAIN_FUNC(arch) \ void dsp_mix_gain_##arch(struct dsp_ops *ops, void * SPA_RESTRICT dst, \ const void * SPA_RESTRICT src[], float gain[], uint32_t n_src, uint32_t n_samples) +#define MAKE_BIQUAD_RUN_FUNC(arch) \ +void dsp_biquad_run_##arch (struct dsp_ops *ops, struct biquad *bq, \ + float *out, const float *in, uint32_t n_samples) MAKE_COPY_FUNC(c); MAKE_MIX_GAIN_FUNC(c); +MAKE_BIQUAD_RUN_FUNC(c); #if defined (HAVE_SSE) MAKE_MIX_GAIN_FUNC(sse); #endif diff --git a/src/modules/module-filter-chain/plugin.h b/src/modules/module-filter-chain/plugin.h index ce46b62ecf87bdaf2f442ad499728f2b37fb0b09..12ead2d97ca88090de8b1e8f0fb1094682b860a5 100644 --- a/src/modules/module-filter-chain/plugin.h +++ b/src/modules/module-filter-chain/plugin.h @@ -64,6 +64,7 @@ struct fc_port { struct fc_descriptor { const char *name; #define FC_DESCRIPTOR_SUPPORTS_NULL_DATA (1ULL << 0) +#define FC_DESCRIPTOR_COPY (1ULL << 1) uint64_t flags; void (*free) (const struct fc_descriptor *desc); diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index 505ff1c1381a0352ed375feb968c4dbad69e8b4e..0c61e86e431dd84e4860c80b6cf90d80fb4c522a 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -451,32 +451,31 @@ static int setup_streams(struct impl *impl) &impl->playback_listener, &out_stream_events, impl); + /* connect playback first to activate it before capture triggers it */ n_params = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, - &impl->capture_info); - - if ((res = pw_stream_connect(impl->capture, - PW_DIRECTION_INPUT, + &impl->playback_info); + if ((res = pw_stream_connect(impl->playback, + PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | - PW_STREAM_FLAG_RT_PROCESS, + PW_STREAM_FLAG_RT_PROCESS | + PW_STREAM_FLAG_TRIGGER, params, n_params)) < 0) return res; n_params = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, - &impl->playback_info); - - if ((res = pw_stream_connect(impl->playback, - PW_DIRECTION_OUTPUT, + &impl->capture_info); + if ((res = pw_stream_connect(impl->capture, + PW_DIRECTION_INPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | - PW_STREAM_FLAG_RT_PROCESS | - PW_STREAM_FLAG_TRIGGER, + PW_STREAM_FLAG_RT_PROCESS, params, n_params)) < 0) return res; @@ -487,8 +486,13 @@ static void core_error(void *data, uint32_t id, int seq, int res, const char *me { struct impl *impl = data; - pw_log_error("error id:%u seq:%d res:%d (%s): %s", - id, seq, res, spa_strerror(res), message); + if (res == -ENOENT) { + pw_log_info("message id:%u seq:%d res:%d (%s): %s", + id, seq, res, spa_strerror(res), message); + } else { + pw_log_warn("error id:%u seq:%d res:%d (%s): %s", + id, seq, res, spa_strerror(res), message); + } if (id == PW_ID_CORE && res == -EPIPE) pw_impl_module_schedule_destroy(impl->module); @@ -513,11 +517,11 @@ static const struct pw_proxy_events core_proxy_events = { static void impl_destroy(struct impl *impl) { - /* disconnect both streams before destroying any of them */ + /* deactivate both streams before destroying any of them */ if (impl->capture) - pw_stream_disconnect(impl->capture); + pw_stream_set_active(impl->capture, false); if (impl->playback) - pw_stream_disconnect(impl->playback); + pw_stream_set_active(impl->playback, false); if (impl->capture) pw_stream_destroy(impl->capture); diff --git a/src/modules/module-protocol-pulse.c b/src/modules/module-protocol-pulse.c index 29ba0c09241fc0c3f1b322abaec150cdbc00b0ae..eaf4c2f0e0ae6561ded2e4b4632ed5f8a83613f0 100644 --- a/src/modules/module-protocol-pulse.c +++ b/src/modules/module-protocol-pulse.c @@ -305,6 +305,7 @@ PW_LOG_TOPIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic +PW_LOG_TOPIC(pulse_conn, "conn." NAME); PW_LOG_TOPIC(pulse_ext_dev_restore, "mod." NAME ".device-restore"); PW_LOG_TOPIC(pulse_ext_stream_restore, "mod." NAME ".stream-restore"); @@ -354,6 +355,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) int res; PW_LOG_TOPIC_INIT(mod_topic); + PW_LOG_TOPIC_INIT(pulse_conn); /* it's easier to init these here than adding an init() call to the * extensions */ PW_LOG_TOPIC_INIT(pulse_ext_dev_restore); diff --git a/src/modules/module-protocol-pulse/cmd.c b/src/modules/module-protocol-pulse/cmd.c new file mode 100644 index 0000000000000000000000000000000000000000..e8e6406caaf674264ef8aa8501f6e66900db81ee --- /dev/null +++ b/src/modules/module-protocol-pulse/cmd.c @@ -0,0 +1,136 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <spa/utils/json.h> + +#include <pipewire/utils.h> + +#include "module.h" +#include "cmd.h" + +static const char WHITESPACE[] = " \t\n\r"; + +static int do_load_module(struct impl *impl, char *args, const char *flags) +{ + int res, n; + struct module *module; + char *a[2] = { NULL }; + + n = pw_split_ip(args, WHITESPACE, 2, a); + if (n < 1) { + pw_log_info("load-module expects module name"); + return -EINVAL; + } + + module = module_create(impl, a[0], a[1]); + if (module == NULL) + return -errno; + if ((res = module_load(module)) < 0) + return res; + + return res; +} + +static int do_cmd(struct impl *impl, const char *cmd, char *args, const char *flags) +{ + int res = 0; + if (spa_streq(cmd, "load-module")) { + res = do_load_module(impl, args, flags); + } else { + pw_log_warn("ignoring unknown command `%s` with args `%s`", + cmd, args); + } + if (res < 0) { + if (flags && strstr(flags, "nofail")) { + pw_log_info("nofail command %s %s: %s", + cmd, args, spa_strerror(res)); + res = 0; + } else { + pw_log_error("can't run command %s %s: %s", + cmd, args, spa_strerror(res)); + } + } + return res; +} + +/* + * pulse.cmd = [ + * { cmd = <command> [ args = "<arguments>" ] } + * ... + * ] + */ +static int parse_cmd(void *user_data, const char *location, + const char *section, const char *str, size_t len) +{ + struct impl *impl = user_data; + struct spa_json it[3]; + char key[512], *s; + int res = 0; + + s = strndup(str, len); + spa_json_init(&it[0], s, len); + if (spa_json_enter_array(&it[0], &it[1]) < 0) { + pw_log_error("config file error: pulse.cmd is not an array"); + res = -EINVAL; + goto exit; + } + + while (spa_json_enter_object(&it[1], &it[2]) > 0) { + char *cmd = NULL, *args = NULL, *flags = NULL; + + while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) { + const char *val; + int len; + + if ((len = spa_json_next(&it[2], &val)) <= 0) + break; + + if (spa_streq(key, "cmd")) { + cmd = (char*)val; + spa_json_parse_stringn(val, len, cmd, len+1); + } else if (spa_streq(key, "args")) { + args = (char*)val; + spa_json_parse_stringn(val, len, args, len+1); + } else if (spa_streq(key, "flags")) { + if (spa_json_is_container(val, len)) + len = spa_json_container_len(&it[2], val, len); + flags = (char*)val; + spa_json_parse_stringn(val, len, flags, len+1); + } + } + if (cmd != NULL) + res = do_cmd(impl, cmd, args, flags); + if (res < 0) + break; + } +exit: + free(s); + return res; +} + +int cmd_run(struct impl *impl) +{ + return pw_context_conf_section_for_each(impl->context, "pulse.cmd", + parse_cmd, impl); +} diff --git a/src/modules/module-protocol-pulse/cmd.h b/src/modules/module-protocol-pulse/cmd.h new file mode 100644 index 0000000000000000000000000000000000000000..81f528c3240adeb0424bb45282ce66eef40555bb --- /dev/null +++ b/src/modules/module-protocol-pulse/cmd.h @@ -0,0 +1,32 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef PULSER_SERVER_CMD_H +#define PULSER_SERVER_CMD_H + +#include "internal.h" + +int cmd_run(struct impl *impl); + +#endif /* PULSER_SERVER_CMD_H */ diff --git a/src/modules/module-protocol-pulse/message.c b/src/modules/module-protocol-pulse/message.c index e70b03a2bcd87233221925ed2a74abc6106aa011..daf4d2e95a5549b17c2d77db41a6e7f4970b9737 100644 --- a/src/modules/module-protocol-pulse/message.c +++ b/src/modules/module-protocol-pulse/message.c @@ -33,7 +33,6 @@ #include "defs.h" #include "format.h" #include "internal.h" -#include "log.h" #include "message.h" #include "remap.h" #include "volume.h" @@ -47,6 +46,9 @@ #define PA_CHANNELS_MAX (32u) +PW_LOG_TOPIC_EXTERN(pulse_conn); +#define PW_LOG_TOPIC_DEFAULT pulse_conn + static inline uint32_t volume_from_linear(float vol) { uint32_t v; diff --git a/src/modules/module-protocol-pulse/module.c b/src/modules/module-protocol-pulse/module.c index 27a5b6b1feb5fdaf25d8121c94295e8163b66866..ec1040411b26bb5abecc0d97d47b97dbe5dfe98f 100644 --- a/src/modules/module-protocol-pulse/module.c +++ b/src/modules/module-protocol-pulse/module.c @@ -36,7 +36,6 @@ #include <pipewire/properties.h> #include <pipewire/work-queue.h> -#include "client.h" #include "defs.h" #include "format.h" #include "internal.h" @@ -84,14 +83,14 @@ void module_add_listener(struct module *module, spa_hook_list_append(&module->listener_list, listener, events, data); } -int module_load(struct client *client, struct module *module) +int module_load(struct module *module) { pw_log_info("load module index:%u name:%s", module->index, module->info->name); if (module->info->load == NULL) return -ENOTSUP; /* subscription event is sent when the module does a * module_emit_loaded() */ - return module->info->load(client, module); + return module->info->load(module); } void module_free(struct module *module) @@ -119,9 +118,6 @@ int module_unload(struct module *module) struct impl *impl = module->impl; int res = 0; - /* Note that client can be NULL (when the module is being unloaded - * internally and not by a client request */ - pw_log_info("unload module index:%u name:%s", module->index, module->info->name); if (module->info->unload) @@ -283,9 +279,8 @@ static int find_module_by_name(void *item_data, void *data) return spa_streq(module->info->name, name) ? 1 : 0; } -struct module *module_create(struct client *client, const char *name, const char *args) +struct module *module_create(struct impl *impl, const char *name, const char *args) { - struct impl *impl = client->impl; const struct module_info *info; struct module *module; diff --git a/src/modules/module-protocol-pulse/module.h b/src/modules/module-protocol-pulse/module.h index c767d689698fbcba51dfd404f4422877ff504b39..1a6ffb04c89c3a3baa09061ffc91a86567def4a0 100644 --- a/src/modules/module-protocol-pulse/module.h +++ b/src/modules/module-protocol-pulse/module.h @@ -29,7 +29,6 @@ #include <spa/param/audio/raw.h> #include <spa/utils/hook.h> -#include "client.h" #include "internal.h" struct module; @@ -41,7 +40,7 @@ struct module_info { unsigned int load_once:1; int (*prepare) (struct module *module); - int (*load) (struct client *client, struct module *module); + int (*load) (struct module *module); int (*unload) (struct module *module); const struct spa_dict *properties; @@ -78,9 +77,9 @@ struct module { #define module_emit_loaded(m,r) spa_hook_list_call(&m->listener_list, struct module_events, loaded, 0, r) #define module_emit_destroy(m) spa_hook_list_call(&(m)->listener_list, struct module_events, destroy, 0) -struct module *module_create(struct client *client, const char *name, const char *args); +struct module *module_create(struct impl *impl, const char *name, const char *args); void module_free(struct module *module); -int module_load(struct client *client, struct module *module); +int module_load(struct module *module); int module_unload(struct module *module); void module_schedule_unload(struct module *module); diff --git a/src/modules/module-protocol-pulse/modules/module-always-sink.c b/src/modules/module-protocol-pulse/modules/module-always-sink.c index dc3d7aaadd5460d304cb418616deb566a8dd61df..7549c09286bd879762b3ede4c42ddb26a566fa14 100644 --- a/src/modules/module-protocol-pulse/modules/module-always-sink.c +++ b/src/modules/module-protocol-pulse/modules/module-always-sink.c @@ -51,7 +51,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy }; -static int module_always_sink_load(struct client *client, struct module *module) +static int module_always_sink_load(struct module *module) { struct module_always_sink_data *data = module->user_data; FILE *f; diff --git a/src/modules/module-protocol-pulse/modules/module-combine-sink.c b/src/modules/module-protocol-pulse/modules/module-combine-sink.c index 5d5ff77e5aaa16e4b4d1b73668c6933dfcdbb5da..93f6be69d38e0eac64c8afdbccf0ed9fa3fed075 100644 --- a/src/modules/module-protocol-pulse/modules/module-combine-sink.c +++ b/src/modules/module-protocol-pulse/modules/module-combine-sink.c @@ -391,7 +391,7 @@ static void on_sinks_timeout(void *d, uint64_t count) check_initialized(data); } -static int module_combine_sink_load(struct client *client, struct module *module) +static int module_combine_sink_load(struct module *module) { struct module_combine_sink_data *data = module->user_data; struct pw_properties *props; @@ -402,9 +402,7 @@ static int module_combine_sink_load(struct client *client, struct module *module struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); const char *str; - data->core = pw_context_connect(module->impl->context, - pw_properties_copy(client->props), - 0); + data->core = pw_context_connect(module->impl->context, NULL, 0); if (data->core == NULL) return -errno; diff --git a/src/modules/module-protocol-pulse/modules/module-echo-cancel.c b/src/modules/module-protocol-pulse/modules/module-echo-cancel.c index 9ecf19fa577b581234b2cb951104eb72b76b09c0..47dfa75bbd087055391fb231a81385b930865a31 100644 --- a/src/modules/module-protocol-pulse/modules/module-echo-cancel.c +++ b/src/modules/module-protocol-pulse/modules/module-echo-cancel.c @@ -63,7 +63,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy }; -static int module_echo_cancel_load(struct client *client, struct module *module) +static int module_echo_cancel_load(struct module *module) { struct module_echo_cancel_data *data = module->user_data; FILE *f; diff --git a/src/modules/module-protocol-pulse/modules/module-gsettings.c b/src/modules/module-protocol-pulse/modules/module-gsettings.c new file mode 100644 index 0000000000000000000000000000000000000000..36e216ba7abf034dee2fb57508093aaaf42ca47a --- /dev/null +++ b/src/modules/module-protocol-pulse/modules/module-gsettings.c @@ -0,0 +1,298 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans <wim.taymans@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <gio/gio.h> +#include <glib.h> + +#include <spa/debug/mem.h> +#include <pipewire/pipewire.h> +#include <pipewire/thread.h> + +#include "../module.h" + +#define NAME "gsettings" + +PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); +#define PW_LOG_TOPIC_DEFAULT mod_topic + +#define PA_GSETTINGS_MODULE_GROUP_SCHEMA "org.freedesktop.pulseaudio.module-group" +#define PA_GSETTINGS_MODULE_GROUPS_SCHEMA "org.freedesktop.pulseaudio.module-groups" +#define PA_GSETTINGS_MODULE_GROUPS_PATH "/org/freedesktop/pulseaudio/module-groups/" + +#define MAX_MODULES 10 + +struct module_gsettings_data { + struct module *module; + + GMainContext *context; + GMainLoop *loop; + struct spa_thread *thr; + + GSettings *settings; + gchar **group_names; + + struct spa_list groups; +}; + +struct group { + struct spa_list link; + char *name; + struct module *module; + struct spa_hook module_listener; +}; + +struct info { + bool enabled; + char *name; + char *module[MAX_MODULES]; + char *args[MAX_MODULES]; +}; + +static void clean_info(const struct info *info) +{ + int i; + for (i = 0; i < MAX_MODULES; i++) { + g_free(info->module[i]); + g_free(info->args[i]); + } + g_free(info->name); +} + +static void unload_module(struct module_gsettings_data *d, struct group *g) +{ + spa_list_remove(&g->link); + g_free(g->name); + if (g->module) + module_unload(g->module); + free(g); +} + +static void unload_group(struct module_gsettings_data *d, const char *name) +{ + struct group *g, *t; + spa_list_for_each_safe(g, t, &d->groups, link) { + if (spa_streq(g->name, name)) + unload_module(d, g); + } +} +static void module_destroy(void *data) +{ + struct group *g = data; + if (g->module) { + spa_hook_remove(&g->module_listener); + g->module = NULL; + } +} + +static const struct module_events module_gsettings_events = { + VERSION_MODULE_EVENTS, + .destroy = module_destroy +}; + +static int load_group(struct module_gsettings_data *d, const struct info *info) +{ + struct group *g; + int i, res; + + for (i = 0; i < MAX_MODULES; i++) { + if (info->module[i] == NULL || strlen(info->module[i]) <= 0) + break; + + g = calloc(1, sizeof(struct group)); + if (g == NULL) + return -errno; + + g->name = strdup(info->name); + g->module = module_create(d->module->impl, info->module[i], info->args[i]); + if (g->module == NULL) { + pw_log_info("can't create module:%s args:%s: %m", + info->module[i], info->args[i]); + } else { + module_add_listener(g->module, &g->module_listener, + &module_gsettings_events, g); + if ((res = module_load(g->module)) < 0) { + pw_log_warn("can't load module:%s args:%s: %s", + info->module[i], info->args[i], + spa_strerror(res)); + } + } + spa_list_append(&d->groups, &g->link); + } + return 0; +} + +static int +do_handle_info(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct module_gsettings_data *d = user_data; + const struct info *info = data; + + unload_group(d, info->name); + if (info->enabled) + load_group(d, info); + + clean_info(info); + return 0; +} + +static void handle_module_group(struct module_gsettings_data *d, gchar *name) +{ + struct impl *impl = d->module->impl; + GSettings *settings; + gchar p[1024]; + struct info info; + int i; + + snprintf(p, sizeof(p), PA_GSETTINGS_MODULE_GROUPS_PATH"%s/", name); + + settings = g_settings_new_with_path(PA_GSETTINGS_MODULE_GROUP_SCHEMA, p); + if (settings == NULL) + return; + + spa_zero(info); + info.name = strdup(p); + info.enabled = g_settings_get_boolean(settings, "enabled"); + + for (i = 0; i < MAX_MODULES; i++) { + snprintf(p, sizeof(p), "name%d", i); + info.module[i] = g_settings_get_string(settings, p); + + snprintf(p, sizeof(p), "args%i", i); + info.args[i] = g_settings_get_string(settings, p); + } + pw_loop_invoke(impl->loop, do_handle_info, 0, + &info, sizeof(info), false, d); + + g_object_unref(G_OBJECT(settings)); +} + +static void module_group_callback(GSettings *settings, gchar *key, gpointer user_data) +{ + struct module_gsettings_data *d = g_object_get_data(G_OBJECT(settings), "module-data"); + handle_module_group(d, user_data); +} + +static void *do_loop(void *user_data) +{ + struct module_gsettings_data *d = user_data; + + pw_log_info("enter"); + g_main_context_push_thread_default(d->context); + + d->loop = g_main_loop_new(d->context, FALSE); + + g_main_loop_run(d->loop); + + g_main_context_pop_thread_default(d->context); + g_main_loop_unref (d->loop); + d->loop = NULL; + pw_log_info("leave"); + + return NULL; +} + +static int module_gsettings_load(struct module *module) +{ + struct module_gsettings_data *data = module->user_data; + gchar **name; + + data->context = g_main_context_new(); + g_main_context_push_thread_default(data->context); + + data->settings = g_settings_new(PA_GSETTINGS_MODULE_GROUPS_SCHEMA); + if (data->settings == NULL) + return -EIO; + + data->group_names = g_settings_list_children(data->settings); + + for (name = data->group_names; *name; name++) { + GSettings *child = g_settings_get_child(data->settings, *name); + /* The child may have been removed between the + * g_settings_list_children() and g_settings_get_child() calls. */ + if (child == NULL) + continue; + + g_object_set_data(G_OBJECT(child), "module-data", data); + g_signal_connect(child, "changed", (GCallback) module_group_callback, *name); + handle_module_group(data, *name); + } + g_main_context_pop_thread_default(data->context); + + data->thr = pw_thread_utils_create(NULL, do_loop, data); + return 0; +} + +static gboolean +do_stop(gpointer data) +{ + struct module_gsettings_data *d = data; + if (d->loop) + g_main_loop_quit(d->loop); + return FALSE; +} + +static int module_gsettings_unload(struct module *module) +{ + struct module_gsettings_data *d = module->user_data; + struct group *g; + + g_main_context_invoke(d->context, do_stop, d); + pw_thread_utils_join(d->thr, NULL); + g_main_context_unref(d->context); + + spa_list_consume(g, &d->groups, link) + unload_module(d, g); + + g_strfreev(d->group_names); + g_object_unref(G_OBJECT(d->settings)); + return 0; +} + +static int module_gsettings_prepare(struct module * const module) +{ + PW_LOG_TOPIC_INIT(mod_topic); + + struct module_gsettings_data * const data = module->user_data; + spa_list_init(&data->groups); + data->module = module; + + return 0; +} + +static const struct spa_dict_item module_gsettings_info[] = { + { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { PW_KEY_MODULE_DESCRIPTION, "GSettings Adapter" }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + +DEFINE_MODULE_INFO(module_gsettings) = { + .name = "module-gsettings", + .load_once = true, + .prepare = module_gsettings_prepare, + .load = module_gsettings_load, + .unload = module_gsettings_unload, + .properties = &SPA_DICT_INIT_ARRAY(module_gsettings_info), + .data_size = sizeof(struct module_gsettings_data), +}; diff --git a/src/modules/module-protocol-pulse/modules/module-ladspa-sink.c b/src/modules/module-protocol-pulse/modules/module-ladspa-sink.c index 33532fa8a0b50fa84efa7f3c24f156e0433d87fd..051926ffab270e86ec1f4adb8bd1663008ab4c0e 100644 --- a/src/modules/module-protocol-pulse/modules/module-ladspa-sink.c +++ b/src/modules/module-protocol-pulse/modules/module-ladspa-sink.c @@ -59,7 +59,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy }; -static int module_ladspa_sink_load(struct client *client, struct module *module) +static int module_ladspa_sink_load(struct module *module) { struct module_ladspa_sink_data *data = module->user_data; FILE *f; diff --git a/src/modules/module-protocol-pulse/modules/module-ladspa-source.c b/src/modules/module-protocol-pulse/modules/module-ladspa-source.c index 246f40f9d5a3db4fa4cf61e3e037a464a9cb45e9..4d65a26c0a11603230f1500997f58e5a8b2b8a7a 100644 --- a/src/modules/module-protocol-pulse/modules/module-ladspa-source.c +++ b/src/modules/module-protocol-pulse/modules/module-ladspa-source.c @@ -59,7 +59,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy }; -static int module_ladspa_source_load(struct client *client, struct module *module) +static int module_ladspa_source_load(struct module *module) { struct module_ladspa_source_data *data = module->user_data; FILE *f; diff --git a/src/modules/module-protocol-pulse/modules/module-loopback.c b/src/modules/module-protocol-pulse/modules/module-loopback.c index 94d7fc70a5cc06dca1052970eba26b0455f1bb23..93e17bc1a973978936533bc0b455b305adfee572 100644 --- a/src/modules/module-protocol-pulse/modules/module-loopback.c +++ b/src/modules/module-protocol-pulse/modules/module-loopback.c @@ -63,7 +63,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy }; -static int module_loopback_load(struct client *client, struct module *module) +static int module_loopback_load(struct module *module) { struct module_loopback_data *data = module->user_data; FILE *f; diff --git a/src/modules/module-protocol-pulse/modules/module-native-protocol-tcp.c b/src/modules/module-protocol-pulse/modules/module-native-protocol-tcp.c index f97cac21b874863dbe15b1d42b47fee85f8ddd0e..e8f4c14cdc613b484a7de03c7981b4ccccbf3f10 100644 --- a/src/modules/module-protocol-pulse/modules/module-native-protocol-tcp.c +++ b/src/modules/module-protocol-pulse/modules/module-native-protocol-tcp.c @@ -38,10 +38,10 @@ struct module_native_protocol_tcp_data { struct pw_array servers; }; -static int module_native_protocol_tcp_load(struct client *client, struct module *module) +static int module_native_protocol_tcp_load(struct module *module) { struct module_native_protocol_tcp_data *data = module->user_data; - struct impl *impl = client->impl; + struct impl *impl = module->impl; const char *address; int res; diff --git a/src/modules/module-protocol-pulse/modules/module-null-sink.c b/src/modules/module-protocol-pulse/modules/module-null-sink.c index c413c9932ac0724fb72b2788dac381908971dc26..caae59882c02876dff139b91308ae12707c14d24 100644 --- a/src/modules/module-protocol-pulse/modules/module-null-sink.c +++ b/src/modules/module-protocol-pulse/modules/module-null-sink.c @@ -104,11 +104,11 @@ static const struct pw_core_events core_events = { .error = module_null_sink_core_error, }; -static int module_null_sink_load(struct client *client, struct module *module) +static int module_null_sink_load(struct module *module) { struct module_null_sink_data *d = module->user_data; - d->core = pw_context_connect(module->impl->context, pw_properties_copy(client->props), 0); + d->core = pw_context_connect(module->impl->context, NULL, 0); if (d->core == NULL) return -errno; diff --git a/src/modules/module-protocol-pulse/modules/module-pipe-sink.c b/src/modules/module-protocol-pulse/modules/module-pipe-sink.c index c1ea9a78cc24fed95b9d3178dc0c66e2fe6877ea..18027ac0c382ce9c4bb4ea666d054cec5151bf1f 100644 --- a/src/modules/module-protocol-pulse/modules/module-pipe-sink.c +++ b/src/modules/module-protocol-pulse/modules/module-pipe-sink.c @@ -64,7 +64,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy }; -static int module_pipe_sink_load(struct client *client, struct module *module) +static int module_pipe_sink_load(struct module *module) { struct module_pipesink_data *data = module->user_data; FILE *f; diff --git a/src/modules/module-protocol-pulse/modules/module-pipe-source.c b/src/modules/module-protocol-pulse/modules/module-pipe-source.c index 6827ee4fdaf764b3736a3af74e8848ff32bb42cd..f39e4eddb85d0ff80a17daa35971bf7e361a508e 100644 --- a/src/modules/module-protocol-pulse/modules/module-pipe-source.c +++ b/src/modules/module-protocol-pulse/modules/module-pipe-source.c @@ -64,7 +64,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy }; -static int module_pipe_source_load(struct client *client, struct module *module) +static int module_pipe_source_load(struct module *module) { struct module_pipesrc_data *data = module->user_data; FILE *f; diff --git a/src/modules/module-protocol-pulse/modules/module-raop-discover.c b/src/modules/module-protocol-pulse/modules/module-raop-discover.c index fd6db7f0a1762091700fac252d66874a0cb5cb6a..bb93b024907cebac4aa37003f6bf9ecd5fea4e33 100644 --- a/src/modules/module-protocol-pulse/modules/module-raop-discover.c +++ b/src/modules/module-protocol-pulse/modules/module-raop-discover.c @@ -55,7 +55,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy }; -static int module_raop_discover_load(struct client *client, struct module *module) +static int module_raop_discover_load(struct module *module) { struct module_raop_discover_data *data = module->user_data; diff --git a/src/modules/module-protocol-pulse/modules/module-remap-sink.c b/src/modules/module-protocol-pulse/modules/module-remap-sink.c index 5aed6f8a6e206a87383b188c7a296d794bd482d4..ffce605e1feda621845d4347f667f8a67e140716 100644 --- a/src/modules/module-protocol-pulse/modules/module-remap-sink.c +++ b/src/modules/module-protocol-pulse/modules/module-remap-sink.c @@ -59,7 +59,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy }; -static int module_remap_sink_load(struct client *client, struct module *module) +static int module_remap_sink_load(struct module *module) { struct module_remap_sink_data *data = module->user_data; FILE *f; diff --git a/src/modules/module-protocol-pulse/modules/module-remap-source.c b/src/modules/module-protocol-pulse/modules/module-remap-source.c index 33e770bd1eb59e503e50b449399f33343889c3f1..1f6d5a7bf8a447cb0d1271f1af29724219253c63 100644 --- a/src/modules/module-protocol-pulse/modules/module-remap-source.c +++ b/src/modules/module-protocol-pulse/modules/module-remap-source.c @@ -59,7 +59,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy }; -static int module_remap_source_load(struct client *client, struct module *module) +static int module_remap_source_load(struct module *module) { struct module_remap_source_data *data = module->user_data; FILE *f; diff --git a/src/modules/module-protocol-pulse/modules/module-roc-sink-input.c b/src/modules/module-protocol-pulse/modules/module-roc-sink-input.c index bcbd65053190a82d610786b7609d29c94b8642ee..c1b2b17a77ab389e33cbeec35e687ab35fc7e54b 100644 --- a/src/modules/module-protocol-pulse/modules/module-roc-sink-input.c +++ b/src/modules/module-protocol-pulse/modules/module-roc-sink-input.c @@ -58,7 +58,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy }; -static int module_roc_sink_input_load(struct client *client, struct module *module) +static int module_roc_sink_input_load(struct module *module) { struct module_roc_sink_input_data *data = module->user_data; FILE *f; diff --git a/src/modules/module-protocol-pulse/modules/module-roc-sink.c b/src/modules/module-protocol-pulse/modules/module-roc-sink.c index a50157a271b32470d78c1e241c7041fa4d7b2639..22ff4dc83f1eb0ac9d235210d5e2ebc878f27cc9 100644 --- a/src/modules/module-protocol-pulse/modules/module-roc-sink.c +++ b/src/modules/module-protocol-pulse/modules/module-roc-sink.c @@ -58,7 +58,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy }; -static int module_roc_sink_load(struct client *client, struct module *module) +static int module_roc_sink_load(struct module *module) { struct module_roc_sink_data *data = module->user_data; FILE *f; diff --git a/src/modules/module-protocol-pulse/modules/module-roc-source.c b/src/modules/module-protocol-pulse/modules/module-roc-source.c index e99b24e076794a7303d244e251b473e6044a7e3e..b75e46e4dd531d81a8e0d0da2db30ae66a80f69c 100644 --- a/src/modules/module-protocol-pulse/modules/module-roc-source.c +++ b/src/modules/module-protocol-pulse/modules/module-roc-source.c @@ -58,7 +58,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy }; -static int module_roc_source_load(struct client *client, struct module *module) +static int module_roc_source_load(struct module *module) { struct module_roc_source_data *data = module->user_data; FILE *f; diff --git a/src/modules/module-protocol-pulse/modules/module-rtp-recv.c b/src/modules/module-protocol-pulse/modules/module-rtp-recv.c index 29a0d70da38420b3f506530f7c6a948ef3056b97..59f6fa36f237d09a5c64e3c60ba112769b9b2d92 100644 --- a/src/modules/module-protocol-pulse/modules/module-rtp-recv.c +++ b/src/modules/module-protocol-pulse/modules/module-rtp-recv.c @@ -57,7 +57,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy }; -static int module_rtp_recv_load(struct client *client, struct module *module) +static int module_rtp_recv_load(struct module *module) { struct module_rtp_recv_data *data = module->user_data; FILE *f; diff --git a/src/modules/module-protocol-pulse/modules/module-rtp-send.c b/src/modules/module-protocol-pulse/modules/module-rtp-send.c index 1f13e65075dcbcd50cc6e01beaa2a70cd4546128..743faaae67037f5f926fbbee4422976dd6118418 100644 --- a/src/modules/module-protocol-pulse/modules/module-rtp-send.c +++ b/src/modules/module-protocol-pulse/modules/module-rtp-send.c @@ -58,7 +58,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy }; -static int module_rtp_send_load(struct client *client, struct module *module) +static int module_rtp_send_load(struct module *module) { struct module_rtp_send_data *data = module->user_data; FILE *f; diff --git a/src/modules/module-protocol-pulse/modules/module-simple-protocol-tcp.c b/src/modules/module-protocol-pulse/modules/module-simple-protocol-tcp.c index 2cfcd237b039c4570292cf5204a5589e3eedeca9..3d0f7d780f4112e2d0d010a59f14416d60227b95 100644 --- a/src/modules/module-protocol-pulse/modules/module-simple-protocol-tcp.c +++ b/src/modules/module-protocol-pulse/modules/module-simple-protocol-tcp.c @@ -58,10 +58,10 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy }; -static int module_simple_protocol_tcp_load(struct client *client, struct module *module) +static int module_simple_protocol_tcp_load(struct module *module) { struct module_simple_protocol_tcp_data *data = module->user_data; - struct impl *impl = client->impl; + struct impl *impl = module->impl; char *args; size_t size; uint32_t i; diff --git a/src/modules/module-protocol-pulse/modules/module-switch-on-connect.c b/src/modules/module-protocol-pulse/modules/module-switch-on-connect.c index 7e30cd54d0adc82e92a130805268f252f4b41cb0..63541939c58b3708ce5adf7360886ee885838041 100644 --- a/src/modules/module-protocol-pulse/modules/module-switch-on-connect.c +++ b/src/modules/module-protocol-pulse/modules/module-switch-on-connect.c @@ -175,13 +175,13 @@ static const struct pw_core_events core_events = { .done = on_core_done, }; -static int module_switch_on_connect_load(struct client *client, struct module *module) +static int module_switch_on_connect_load(struct module *module) { - struct impl *impl = client->impl; + struct impl *impl = module->impl; struct module_switch_on_connect_data *d = module->user_data; int res; - d->core = pw_context_connect(impl->context, pw_properties_copy(client->props), 0); + d->core = pw_context_connect(impl->context, NULL, 0); if (d->core == NULL) { res = -errno; goto error; diff --git a/src/modules/module-protocol-pulse/modules/module-tunnel-sink.c b/src/modules/module-protocol-pulse/modules/module-tunnel-sink.c index da4ec87d493ae0a864ad58c5a2ea7af5c1d6b4de..5089285e88a06cf9aeab8bfdcd9509d92a15ce61 100644 --- a/src/modules/module-protocol-pulse/modules/module-tunnel-sink.c +++ b/src/modules/module-protocol-pulse/modules/module-tunnel-sink.c @@ -62,7 +62,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy }; -static int module_tunnel_sink_load(struct client *client, struct module *module) +static int module_tunnel_sink_load(struct module *module) { struct module_tunnel_sink_data *data = module->user_data; FILE *f; diff --git a/src/modules/module-protocol-pulse/modules/module-tunnel-source.c b/src/modules/module-protocol-pulse/modules/module-tunnel-source.c index 7fa4b78e482733fe8ce4811ed38a74ba723c3578..e344754e79a4feff4d478d1b390e407ae8e22627 100644 --- a/src/modules/module-protocol-pulse/modules/module-tunnel-source.c +++ b/src/modules/module-protocol-pulse/modules/module-tunnel-source.c @@ -62,7 +62,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy }; -static int module_tunnel_source_load(struct client *client, struct module *module) +static int module_tunnel_source_load(struct module *module) { struct module_tunnel_source_data *data = module->user_data; FILE *f; diff --git a/src/modules/module-protocol-pulse/modules/module-x11-bell.c b/src/modules/module-protocol-pulse/modules/module-x11-bell.c index a14c843a02fea598265cd4cca53ef568ee50d7e7..9d7e2176dd4423a6f14c5115cbb0c7adf6d8a318 100644 --- a/src/modules/module-protocol-pulse/modules/module-x11-bell.c +++ b/src/modules/module-protocol-pulse/modules/module-x11-bell.c @@ -51,7 +51,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy }; -static int module_x11_bell_load(struct client *client, struct module *module) +static int module_x11_bell_load(struct module *module) { struct module_x11_bell_data *data = module->user_data; FILE *f; diff --git a/src/modules/module-protocol-pulse/modules/module-zeroconf-discover.c b/src/modules/module-protocol-pulse/modules/module-zeroconf-discover.c index 79f14e853c2433b2286ad9742e3b7c91e5ec7c81..c44c6c8677779ab6615602d0728a1a4e7b825b50 100644 --- a/src/modules/module-protocol-pulse/modules/module-zeroconf-discover.c +++ b/src/modules/module-protocol-pulse/modules/module-zeroconf-discover.c @@ -57,7 +57,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy }; -static int module_zeroconf_discover_load(struct client *client, struct module *module) +static int module_zeroconf_discover_load(struct module *module) { struct module_zeroconf_discover_data *data = module->user_data; FILE *f; diff --git a/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c b/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c index 8da1b6ae83edf518bc5264fdccae07e554c6995b..2f048678da34c693ec84b8ca0ffae5e054ae5226 100644 --- a/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c +++ b/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c @@ -73,6 +73,7 @@ struct service { AvahiEntryGroup *entry_group; AvahiStringList *txt; + struct server *server; const char *service_type; enum service_subtype subtype; @@ -154,6 +155,7 @@ static void unpublish_service(struct service *s) spa_list_remove(&s->link); spa_list_append(&s->userdata->pending, &s->link); s->published = false; + s->server = NULL; } static void unpublish_all_services(struct module_zeroconf_publish_data *d) @@ -397,7 +399,7 @@ static AvahiStringList *get_service_txt(const struct service *s) return txt; } -static int find_port(struct service *s, int *proto, uint16_t *port) +static struct server *find_server(struct service *s, int *proto, uint16_t *port) { struct module_zeroconf_publish_data *d = s->userdata; struct impl *impl = d->module->impl; @@ -407,14 +409,15 @@ static int find_port(struct service *s, int *proto, uint16_t *port) if (server->addr.ss_family == AF_INET) { *proto = AVAHI_PROTO_INET; *port = ntohs(((struct sockaddr_in*) &server->addr)->sin_port); - return 0; + return server; } else if (server->addr.ss_family == AF_INET6) { *proto = AVAHI_PROTO_INET6; *port = ntohs(((struct sockaddr_in6*) &server->addr)->sin6_port); - return 0; + return server; } } - return -ENODEV; + + return NULL; } static void publish_service(struct service *s) @@ -423,10 +426,11 @@ static void publish_service(struct service *s) int proto; uint16_t port; - if (find_port(s, &proto, &port) < 0) + struct server *server = find_server(s, &proto, &port); + if (!server) return; - pw_log_debug("found proto:%d port:%d", proto, port); + pw_log_debug("found server:%p proto:%d port:%d", server, proto, port); if (!d->client || avahi_client_get_state(d->client) != AVAHI_CLIENT_S_RUNNING) return; @@ -499,6 +503,7 @@ static void publish_service(struct service *s) spa_list_remove(&s->link); spa_list_append(&d->published, &s->link); + s->server = server; pw_log_info("created service: %s", s->service_name); return; @@ -626,7 +631,13 @@ static void impl_server_stopped(void *data, struct server *server) { struct module_zeroconf_publish_data *d = data; pw_log_info("a server stopped, try republish"); - unpublish_all_services(d); + + struct service *s, *tmp; + spa_list_for_each_safe(s, tmp, &d->published, link) { + if (s->server == server) + unpublish_service(s); + } + publish_pending(d); } @@ -636,14 +647,13 @@ static const struct impl_events impl_events = { .server_stopped = impl_server_stopped, }; -static int module_zeroconf_publish_load(struct client *client, struct module *module) +static int module_zeroconf_publish_load(struct module *module) { struct module_zeroconf_publish_data *data = module->user_data; struct pw_loop *loop; int error; - data->core = pw_context_connect(module->impl->context, - pw_properties_copy(client->props), 0); + data->core = pw_context_connect(module->impl->context, NULL, 0); if (data->core == NULL) { pw_log_error("failed to connect to pipewire: %m"); return -errno; diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index 84bf3b56c5299aed0fc905073f35a9258f6fdfc7..97405e03985ec9fa15b023f9c469618d1a973ccc 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -57,6 +57,7 @@ #include "client.h" #include "collect.h" #include "commands.h" +#include "cmd.h" #include "dbus-name.h" #include "defs.h" #include "extension.h" @@ -85,7 +86,7 @@ #define DEFAULT_MIN_QUANTUM "256/48000" #define DEFAULT_FORMAT "F32" #define DEFAULT_POSITION "[ FL FR ]" -#define DEFAULT_IDLE_TIMEOUT "5" +#define DEFAULT_IDLE_TIMEOUT "0" #define MAX_FORMATS 32 /* The max amount of data we send in one block when capturing. In PulseAudio this @@ -94,6 +95,8 @@ #define TEMPORARY_MOVE_TIMEOUT (SPA_NSEC_PER_SEC) +PW_LOG_TOPIC_EXTERN(pulse_conn); + bool debug_messages = false; struct latency_offset_data { @@ -5131,6 +5134,7 @@ static int do_load_module(struct client *client, uint32_t command, uint32_t tag, .sync = on_load_module_manager_sync, }; + struct impl *impl = client->impl; const char *name, *argument; struct module *module; struct pending_module *pm; @@ -5145,7 +5149,7 @@ static int do_load_module(struct client *client, uint32_t command, uint32_t tag, pw_log_info("[%s] %s name:%s argument:%s", client->name, commands[command].name, name, argument); - module = module_create(client, name, argument); + module = module_create(impl, name, argument); if (module == NULL) return -errno; @@ -5159,7 +5163,7 @@ static int do_load_module(struct client *client, uint32_t command, uint32_t tag, pw_log_debug("pending module %p: start tag:%d", pm, tag); - r = module_load(client, module); + r = module_load(module); module_add_listener(module, &pm->module_listener, &module_events, pm); client_add_listener(client, &pm->client_listener, &client_events, pm); @@ -5624,7 +5628,7 @@ struct pw_protocol_pulse *pw_protocol_pulse_new(struct pw_context *context, load_defaults(&impl->defs, props); - debug_messages = pw_debug_is_category_enabled("connection"); + debug_messages = pw_log_topic_enabled(SPA_LOG_LEVEL_INFO, pulse_conn); impl->context = context; impl->loop = pw_context_get_main_loop(context); @@ -5669,6 +5673,7 @@ struct pw_protocol_pulse *pw_protocol_pulse_new(struct pw_context *context, #ifdef HAVE_DBUS impl->dbus_name = dbus_request_name(context, "org.pulseaudio.Server"); #endif + cmd_run(impl); return (struct pw_protocol_pulse *) impl; diff --git a/src/modules/module-protocol-pulse/server.c b/src/modules/module-protocol-pulse/server.c index 503746f8f83dbbff1e1e1c5b9cd70720e92ba0f9..927e25327641a12a7ed7101bc80f8c0a5b0d9d0b 100644 --- a/src/modules/module-protocol-pulse/server.c +++ b/src/modules/module-protocol-pulse/server.c @@ -823,13 +823,14 @@ static int parse_ip_address(const char *address, struct sockaddr_storage *addrs, if (len < 2) return -ENOSPC; - snprintf(ip, sizeof(ip), "[::]:%d", res); - spa_assert_se(parse_ipv6_address(ip, (struct sockaddr_in6 *) &addr) == 0); - addrs[0] = addr; - snprintf(ip, sizeof(ip), "0.0.0.0:%d", res); spa_assert_se(parse_ipv4_address(ip, (struct sockaddr_in *) &addr) == 0); + addrs[0] = addr; + + snprintf(ip, sizeof(ip), "[::]:%d", res); + spa_assert_se(parse_ipv6_address(ip, (struct sockaddr_in6 *) &addr) == 0); addrs[1] = addr; + return 2; } diff --git a/src/modules/module-pulse-tunnel.c b/src/modules/module-pulse-tunnel.c index 2c9a97df781fd21130ead6e3754dff392fbd2309..916400d22be272f914591be69f217556fa15ab33 100644 --- a/src/modules/module-pulse-tunnel.c +++ b/src/modules/module-pulse-tunnel.c @@ -189,6 +189,7 @@ struct impl { uint32_t target_latency; uint32_t current_latency; uint32_t target_buffer; + struct spa_io_rate_match *rate_match; struct spa_dll dll; float max_error; unsigned resync:1; @@ -250,6 +251,28 @@ static void stream_state_changed(void *d, enum pw_stream_state old, } } +static void update_rate(struct impl *impl, bool playback) +{ + float error, corr; + + if (impl->rate_match == NULL) + return; + + if (playback) + error = (float)impl->target_latency - (float)impl->current_latency; + else + error = (float)impl->current_latency - (float)impl->target_latency; + error = SPA_CLAMP(error, -impl->max_error, impl->max_error); + + corr = spa_dll_update(&impl->dll, error); + pw_log_debug("error:%f corr:%f current:%u target:%u", + error, corr, + impl->current_latency, impl->target_latency); + + SPA_FLAG_SET(impl->rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE); + impl->rate_match->rate = corr; +} + static void playback_stream_process(void *d) { struct impl *impl = d; @@ -279,17 +302,7 @@ static void playback_stream_process(void *d) size, RINGBUFFER_SIZE); impl->resync = true; } else { - float error, corr; - - error = (float)impl->target_latency - (float)impl->current_latency; - error = SPA_CLAMP(error, -impl->max_error, impl->max_error); - - corr = spa_dll_update(&impl->dll, error); - pw_log_debug("filled:%u target:%u error:%f corr:%f %u %u", filled, - impl->target_buffer, error, corr, - impl->current_latency, impl->target_latency); - pw_stream_set_control(impl->stream, - SPA_PROP_rate, 1, &corr, NULL); + update_rate(impl, true); } spa_ringbuffer_write_data(&impl->ring, impl->buffer, RINGBUFFER_SIZE, @@ -326,24 +339,12 @@ static void capture_stream_process(void *d) if (avail < (int32_t)size) { memset(bd->data, 0, size); } else { - float error, corr; - if (avail > (int32_t)RINGBUFFER_SIZE) { avail = impl->target_buffer; index += avail - impl->target_buffer; } else { - error = (float)(impl->current_latency) - (float)impl->target_latency; - error = SPA_CLAMP(error, -impl->max_error, impl->max_error); - - corr = spa_dll_update(&impl->dll, error); - - pw_log_debug("avail:%u target:%u error:%f corr:%f %u %u", avail, - impl->target_buffer, error, corr, - impl->current_latency, impl->target_latency); - pw_stream_set_control(impl->stream, - SPA_PROP_rate, 1, &corr, NULL); + update_rate(impl, false); } - spa_ringbuffer_read_data(&impl->ring, impl->buffer, RINGBUFFER_SIZE, index & RINGBUFFER_MASK, @@ -359,10 +360,21 @@ static void capture_stream_process(void *d) pw_stream_queue_buffer(impl->stream, buf); } +static void stream_io_changed(void *data, uint32_t id, void *area, uint32_t size) +{ + struct impl *impl = data; + switch (id) { + case SPA_IO_RateMatch: + impl->rate_match = area; + break; + } +} + static const struct pw_stream_events playback_stream_events = { PW_VERSION_STREAM_EVENTS, .destroy = stream_destroy, .state_changed = stream_state_changed, + .io_changed = stream_io_changed, .process = playback_stream_process }; @@ -370,6 +382,7 @@ static const struct pw_stream_events capture_stream_events = { PW_VERSION_STREAM_EVENTS, .destroy = stream_destroy, .state_changed = stream_state_changed, + .io_changed = stream_io_changed, .process = capture_stream_process }; diff --git a/src/modules/module-rtp-sink.c b/src/modules/module-rtp-sink.c index 014446d5c057951ef913cac94e84dd1a32e27715..8a00848eccfdc15b6ea89778cfb6290f75135552 100644 --- a/src/modules/module-rtp-sink.c +++ b/src/modules/module-rtp-sink.c @@ -306,8 +306,17 @@ static void flush_packets(struct impl *impl) &iov[1], tosend); n = sendmsg(impl->rtp_fd, &msg, MSG_NOSIGNAL); - if (n < 0) - pw_log_warn("sendmsg() failed: %m"); + if (n < 0) { + switch (errno) { + case ECONNREFUSED: + case ECONNRESET: + pw_log_debug("remote end not listening"); + break; + default: + pw_log_warn("sendmsg() failed: %m"); + break; + } + } impl->seq++; impl->timestamp += tosend / impl->frame_size; diff --git a/src/modules/module-rtp-source.c b/src/modules/module-rtp-source.c index 503bfc1961b064cafab2fbf78b511fd9ac051a90..bc25cdc3db53463cf43339f2640268acf6f3b010 100644 --- a/src/modules/module-rtp-source.c +++ b/src/modules/module-rtp-source.c @@ -214,6 +214,7 @@ struct session { struct spa_ringbuffer ring; uint8_t buffer[BUFFER_SIZE]; + struct spa_io_rate_match *rate_match; struct spa_dll dll; uint32_t target_buffer; float max_error; @@ -269,8 +270,10 @@ static void stream_process(void *data) pw_log_debug("avail:%u target:%u error:%f corr:%f", avail, sess->target_buffer, error, corr); - pw_stream_set_control(sess->stream, - SPA_PROP_rate, 1, &corr, NULL); + if (sess->rate_match) { + SPA_FLAG_SET(sess->rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE); + sess->rate_match->rate = corr; + } } spa_ringbuffer_read_data(&sess->ring, sess->buffer, @@ -308,10 +311,21 @@ static void on_stream_state_changed(void *d, enum pw_stream_state old, } } +static void stream_io_changed(void *data, uint32_t id, void *area, uint32_t size) +{ + struct session *sess = data; + switch (id) { + case SPA_IO_RateMatch: + sess->rate_match = area; + break; + } +} + static const struct pw_stream_events out_stream_events = { PW_VERSION_STREAM_EVENTS, .destroy = stream_destroy, .state_changed = on_stream_state_changed, + .io_changed = stream_io_changed, .process = stream_process }; diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index 5eeff5b01dcfc799da18dd8cd7d55a8aa6791dbf..f121ce83aab340134441f9bf08f03ed54d03497b 100644 --- a/src/pipewire/impl-link.c +++ b/src/pipewire/impl-link.c @@ -622,8 +622,8 @@ int pw_impl_link_activate(struct pw_impl_link *this) pw_log_debug("%p: activate activated:%d state:%s", this, impl->activated, pw_link_state_as_string(this->info.state)); - if (impl->activated || !this->prepared || !impl->inode->active || - !impl->inode->added || !impl->onode->active) + if (impl->activated || !this->prepared || + !impl->inode->active || !impl->onode->active) return 0; if (!impl->io_set) { @@ -812,7 +812,7 @@ do_deactivate_link(struct spa_loop *loop, spa_list_remove(&this->rt.out_mix.rt_link); spa_list_remove(&this->rt.in_mix.rt_link); - if (this->input->node != this->output->node) { + if (impl->inode != impl->onode) { struct pw_node_activation_state *state; spa_list_remove(&this->rt.target.link); diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index 5480d273e1c4274fd84dd43a7ddb9bae2d013770..dd20620a94c5e5145f7285d20e61faf23e6d1580 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -158,12 +158,51 @@ static void remove_node(struct pw_impl_node *this) this->rt.driver_target.node = NULL; } +static int +do_node_add(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct pw_impl_node *this = user_data; + struct pw_impl_node *driver = this->driver_node; + + this->added = true; + if (this->source.loop == NULL) { + struct spa_system *data_system = this->context->data_system; + uint64_t dummy; + int res; + + /* clear the eventfd in case it was written to while the node was stopped */ + res = spa_system_eventfd_read(data_system, this->source.fd, &dummy); + if (SPA_UNLIKELY(res != -EAGAIN && res != 0)) + pw_log_warn("%p: read failed %m", this); + + spa_loop_add_source(loop, &this->source); + add_node(this, driver); + } + return 0; +} + +static int +do_node_remove(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct pw_impl_node *this = user_data; + if (this->source.loop != NULL) { + spa_loop_remove_source(loop, &this->source); + remove_node(this); + } + this->added = false; + return 0; +} + static void node_deactivate(struct pw_impl_node *this) { struct pw_impl_port *port; struct pw_impl_link *link; pw_log_debug("%p: deactivate", this); + + /* make sure the node doesn't get woken up while not active */ + pw_loop_invoke(this->data_loop, do_node_remove, 1, NULL, 0, true, this); + spa_list_for_each(port, &this->input_ports, link) { spa_list_for_each(link, &port->links, input_link) pw_impl_link_deactivate(link); @@ -200,7 +239,7 @@ static int idle_node(struct pw_impl_node *this) return res; } -static void node_activate_outputs(struct pw_impl_node *this) +static void node_activate(struct pw_impl_node *this) { struct pw_impl_port *port; @@ -210,13 +249,6 @@ static void node_activate_outputs(struct pw_impl_node *this) spa_list_for_each(link, &port->links, output_link) pw_impl_link_activate(link); } -} - -static void node_activate_inputs(struct pw_impl_node *this) -{ - struct pw_impl_port *port; - - pw_log_debug("%p: activate", this); spa_list_for_each(port, &this->input_ports, link) { struct pw_impl_link *link; spa_list_for_each(link, &port->links, input_link) @@ -229,9 +261,7 @@ static int start_node(struct pw_impl_node *this) struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); int res = 0; - /* First activate the outputs so that when the node starts pushing, - * we can process the outputs */ - node_activate_outputs(this); + node_activate(this); if (impl->pending_state >= PW_NODE_STATE_RUNNING) return 0; @@ -243,6 +273,10 @@ static int start_node(struct pw_impl_node *this) impl->pending_play = true; res = spa_node_send_command(this->node, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start)); + } else { + /* driver nodes will wait until all other nodes are started before + * they are started */ + res = EBUSY; } if (res < 0) @@ -325,34 +359,6 @@ static void emit_params(struct pw_impl_node *node, uint32_t *changed_ids, uint32 } } -static int -do_node_add(struct spa_loop *loop, - bool async, uint32_t seq, const void *data, size_t size, void *user_data) -{ - struct pw_impl_node *this = user_data; - struct pw_impl_node *driver = this->driver_node; - - if (this->source.loop == NULL) { - spa_loop_add_source(loop, &this->source); - add_node(this, driver); - } - this->added = true; - return 0; -} - -static int -do_node_remove(struct spa_loop *loop, - bool async, uint32_t seq, const void *data, size_t size, void *user_data) -{ - struct pw_impl_node *this = user_data; - if (this->source.loop != NULL) { - spa_loop_remove_source(loop, &this->source); - remove_node(this); - } - this->added = false; - return 0; -} - static void node_update_state(struct pw_impl_node *node, enum pw_node_state state, int res, char *error) { struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); @@ -375,10 +381,6 @@ static void node_update_state(struct pw_impl_node *node, enum pw_node_state stat pw_loop_invoke(node->data_loop, do_node_remove, 1, NULL, 0, true, node); } } - if (res >= 0) { - /* now activate the inputs */ - node_activate_inputs(node); - } break; case PW_NODE_STATE_IDLE: case PW_NODE_STATE_SUSPENDED: @@ -807,17 +809,9 @@ do_move_nodes(struct spa_loop *loop, struct impl *impl = user_data; struct pw_impl_node *driver = *(struct pw_impl_node **)data; struct pw_impl_node *node = &impl->this; - int res; pw_log_trace("%p: driver:%p->%p", node, node->driver_node, driver); - if ((res = spa_node_set_io(node->node, - SPA_IO_Position, - &driver->rt.activation->position, - sizeof(struct spa_io_position))) < 0) { - pw_log_debug("%p: set position: %s", node, spa_strerror(res)); - } - pw_log_trace("%p: set position %p", node, &driver->rt.activation->position); node->rt.position = &driver->rt.activation->position; @@ -843,6 +837,8 @@ int pw_impl_node_set_driver(struct pw_impl_node *node, struct pw_impl_node *driv { struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); struct pw_impl_node *old = node->driver_node; + int res; + bool was_driving; if (driver == NULL) driver = node; @@ -865,8 +861,16 @@ int pw_impl_node_set_driver(struct pw_impl_node *node, struct pw_impl_node *driv old->name, old->info.id, driver->name, driver->info.id); } + was_driving = node->driving; node->driving = node->driver && driver == node; + /* When a node was driver (and is waiting for all nodes to complete + * the Start command) cancel the pending state and let the new driver + * calculate a new state so that the Start command is sent to the + * node */ + if (was_driving && !node->driving) + impl->pending_state = node->info.state; + pw_log_debug("%p: driver %p driving:%u", node, driver, node->driving); pw_log_info("(%s-%u) -> change driver (%s-%d -> %s-%d)", @@ -876,6 +880,13 @@ int pw_impl_node_set_driver(struct pw_impl_node *node, struct pw_impl_node *driv node->driver_node = driver; node->moved = true; + if ((res = spa_node_set_io(node->node, + SPA_IO_Position, + &driver->rt.activation->position, + sizeof(struct spa_io_position))) < 0) { + pw_log_debug("%p: set position: %s", node, spa_strerror(res)); + } + pw_loop_invoke(node->data_loop, do_move_nodes, SPA_ID_INVALID, &driver, sizeof(struct pw_impl_node *), true, impl); @@ -1110,28 +1121,30 @@ static inline int process_node(void *data) a->status = PW_NODE_ACTIVATION_AWAKE; a->awake_time = SPA_TIMESPEC_TO_NSEC(&ts); - if (!this->added) { - /* This should not happen here. We activate the input - * links after we add the node to the graph. */ - pw_log_warn("%p: scheduling non-active node", this); - return -EIO; - } pw_log_trace_fp("%p: process %"PRIu64, this, a->awake_time); /* when transport sync is not supported, just clear the flag */ if (!this->transport_sync) a->pending_sync = false; - spa_list_for_each(p, &this->rt.input_mix, rt.node_link) - spa_node_process(p->mix); + if (this->added) { + spa_list_for_each(p, &this->rt.input_mix, rt.node_link) + spa_node_process(p->mix); - status = spa_node_process(this->node); - a->state[0].status = status; + status = spa_node_process(this->node); - if (status & SPA_STATUS_HAVE_DATA) { - spa_list_for_each(p, &this->rt.output_mix, rt.node_link) - spa_node_process(p->mix); + if (status & SPA_STATUS_HAVE_DATA) { + spa_list_for_each(p, &this->rt.output_mix, rt.node_link) + spa_node_process(p->mix); + } + } else { + /* This can happen when we deactivated the node but some links are + * still not shut down. We simply don't schedule the node and make + * sure we trigger the peers in resume_node below. */ + pw_log_debug("%p: scheduling non-active node %s", this, this->name); + status = SPA_STATUS_HAVE_DATA; } + a->state[0].status = status; if (SPA_UNLIKELY(this == this->driver_node && !this->exported)) { spa_system_clock_gettime(data_system, CLOCK_MONOTONIC, &ts); @@ -1636,7 +1649,11 @@ static int node_ready(void *data, int status) node->driver, node->exported, driver, status, node->added); if (!node->added) { - pw_log_warn("%p: ready non-active node", node); + /* This can happen when we are stopping a node and removed it from the + * graph but we still have not completed the Pause/Suspend command on + * the node. In that case, the node might still emit ready events, + * which we should simply ignore here. */ + pw_log_info("%p: ready non-active node %s in state %d", node, node->name, node->info.state); return -EIO; } @@ -2155,6 +2172,10 @@ static void on_state_complete(void *obj, void *data, int res, uint32_t seq) enum pw_node_state state = SPA_PTR_TO_INT(data); char *error = NULL; + /* driver nodes added -EBUSY. This is then not an error */ + if (res == -EBUSY) + res = 0; + impl->pending_id = SPA_ID_INVALID; impl->pending_play = false; @@ -2242,9 +2263,13 @@ int pw_impl_node_set_state(struct pw_impl_node *node, enum pw_node_state state) pw_work_queue_cancel(impl->work, node, impl->pending_id); node->info.state = impl->pending_state; } + /* driver nodes return EBUSY to add a -EBUSY to the work queue. This + * will wait until all previous items in the work queue are + * completed */ impl->pending_state = state; impl->pending_id = pw_work_queue_add(impl->work, - node, res, on_state_complete, SPA_INT_TO_PTR(state)); + node, res == EBUSY ? -EBUSY : res, + on_state_complete, SPA_INT_TO_PTR(state)); } return res; } @@ -2255,9 +2280,9 @@ int pw_impl_node_set_active(struct pw_impl_node *node, bool active) bool old = node->active; if (old != active) { - pw_log_debug("%p: %s registered:%d", node, + pw_log_debug("%p: %s registered:%d exported:%d", node, active ? "activate" : "deactivate", - node->registered); + node->registered, node->exported); node->active = active; pw_impl_node_emit_active_changed(node, active); diff --git a/src/pipewire/pipewire.c b/src/pipewire/pipewire.c index 255760c90df8ec5206905def6d341c7379044f57..cc5610ed80ee241760acb2850e104fd5d682fd66 100644 --- a/src/pipewire/pipewire.c +++ b/src/pipewire/pipewire.c @@ -64,7 +64,6 @@ struct plugin { char *filename; void *hnd; spa_handle_factory_enum_func_t enum_func; - struct spa_list handles; int ref; }; @@ -78,10 +77,10 @@ struct handle { struct registry { struct spa_list plugins; + struct spa_list handles; /* all handles across all plugins by age (youngest first) */ }; struct support { - char **categories; const char *plugin_dir; const char *support_lib; struct registry registry; @@ -93,6 +92,7 @@ struct support { unsigned int in_valgrind:1; unsigned int no_color:1; unsigned int no_config:1; + unsigned int do_dlclose:1; }; static pthread_mutex_t init_lock = PTHREAD_MUTEX_INITIALIZER; @@ -149,7 +149,6 @@ open_plugin(struct registry *registry, plugin->filename = strdup(filename); plugin->hnd = hnd; plugin->enum_func = enum_func; - spa_list_init(&plugin->handles); spa_list_append(®istry->plugins, &plugin->link); @@ -168,7 +167,7 @@ unref_plugin(struct plugin *plugin) if (--plugin->ref == 0) { spa_list_remove(&plugin->link); pw_log_debug("unloaded plugin:'%s'", plugin->filename); - if (!global_support.in_valgrind) + if (global_support.do_dlclose) dlclose(plugin->hnd); free(plugin->filename); free(plugin); @@ -290,7 +289,7 @@ static struct spa_handle *load_spa_handle(const char *lib, handle->ref = 1; handle->plugin = plugin; handle->factory_name = strdup(factory_name); - spa_list_append(&plugin->handles, &handle->link); + spa_list_prepend(&sup->registry.handles, &handle->link); return &handle->handle; @@ -321,15 +320,13 @@ struct spa_handle *pw_load_spa_handle(const char *lib, static struct handle *find_handle(struct spa_handle *handle) { struct registry *registry = &global_support.registry; - struct plugin *p; struct handle *h; - spa_list_for_each(p, ®istry->plugins, link) { - spa_list_for_each(h, &p->handles, link) { - if (&h->handle == handle) - return h; - } + spa_list_for_each(h, ®istry->handles, link) { + if (&h->handle == handle) + return h; } + return NULL; } @@ -490,22 +487,30 @@ static struct spa_log *load_journal_logger(struct support *support, } #endif -static enum spa_log_level -parse_log_level(const char *str) -{ - enum spa_log_level l = SPA_LOG_LEVEL_NONE; - switch(str[0]) { - case 'X': l = SPA_LOG_LEVEL_NONE; break; - case 'E': l = SPA_LOG_LEVEL_ERROR; break; - case 'W': l = SPA_LOG_LEVEL_WARN; break; - case 'I': l = SPA_LOG_LEVEL_INFO; break; - case 'D': l = SPA_LOG_LEVEL_DEBUG; break; - case 'T': l = SPA_LOG_LEVEL_TRACE; break; +static bool +parse_log_level(const char *str, enum spa_log_level *l) +{ + uint32_t lvl; + if (strlen(str) == 1) { + switch(str[0]) { + case 'X': lvl = SPA_LOG_LEVEL_NONE; break; + case 'E': lvl = SPA_LOG_LEVEL_ERROR; break; + case 'W': lvl = SPA_LOG_LEVEL_WARN; break; + case 'I': lvl = SPA_LOG_LEVEL_INFO; break; + case 'D': lvl = SPA_LOG_LEVEL_DEBUG; break; + case 'T': lvl = SPA_LOG_LEVEL_TRACE; break; default: - l = atoi(str); - break; + goto check_int; + } + } else { +check_int: + if (!spa_atou32(str, &lvl, 0)) + return false; + if (lvl > SPA_LOG_LEVEL_TRACE) + return false; } - return l; + *l = lvl; + return true; } static char * @@ -518,6 +523,7 @@ parse_pw_debug_env(void) char json[1024] = {0}; char *pos = json; char *end = pos + sizeof(json) - 1; + enum spa_log_level lvl; str = getenv("PIPEWIRE_DEBUG"); @@ -530,13 +536,6 @@ parse_pw_debug_env(void) */ pos += spa_scnprintf(pos, end - pos, "[ { conn.* = %d },", SPA_LOG_LEVEL_NONE); - /* We only have single-digit log levels, so any single-character - * string is of the form PIPEWIRE_DEBUG=<N> */ - if (slen == 1) { - pw_log_set_level(parse_log_level(str)); - goto out; - } - tokens = pw_split_strv(str, ",", INT_MAX, &n_tokens); if (n_tokens > 0) { int i; @@ -544,24 +543,23 @@ parse_pw_debug_env(void) int n_tok; char **tok; char *pattern; - enum spa_log_level lvl; tok = pw_split_strv(tokens[i], ":", 2, &n_tok); - if (n_tok == 2) { + if (n_tok == 2 && parse_log_level(tok[1], &lvl)) { pattern = tok[0]; - lvl = parse_log_level(tok[1]); - pos += spa_scnprintf(pos, end - pos, "{ %s = %d },", pattern, lvl); + } else if (n_tok == 1 && parse_log_level(tok[0], &lvl)) { + pw_log_set_level(lvl); } else { - pw_log_warn("Ignoring invalid format in PIPEWIRE_DEBUG: '%s'\n", tokens[i]); + pw_log_warn("Ignoring invalid format in PIPEWIRE_DEBUG: '%s'", + tokens[i]); } pw_free_strv(tok); } } pw_free_strv(tokens); -out: pos += spa_scnprintf(pos, end - pos, "]"); return strdup(json); } @@ -594,6 +592,10 @@ void pw_init(int *argc, char **argv[]) pthread_mutex_lock(&support_lock); support->in_valgrind = RUNNING_ON_VALGRIND; + support->do_dlclose = true; + if ((str = getenv("PIPEWIRE_DLCLOSE")) != NULL) + support->do_dlclose = pw_properties_parse_bool(str); + if (getenv("NO_COLOR") != NULL) support->no_color = true; @@ -611,6 +613,7 @@ void pw_init(int *argc, char **argv[]) support->support_lib = str; spa_list_init(&support->registry.plugins); + spa_list_init(&support->registry.handles); if (pw_log_is_default()) { char *patterns = NULL; @@ -684,7 +687,7 @@ void pw_deinit(void) { struct support *support = &global_support; struct registry *registry = &support->registry; - struct plugin *p; + struct handle *h; pthread_mutex_lock(&init_lock); if (support->init_count == 0) @@ -694,14 +697,10 @@ void pw_deinit(void) pthread_mutex_lock(&support_lock); pw_log_set(NULL); - spa_list_consume(p, ®istry->plugins, link) { - struct handle *h; - p->ref++; - spa_list_consume(h, &p->handles, link) - unref_handle(h); - unref_plugin(p); - } - pw_free_strv(support->categories); + + spa_list_consume(h, ®istry->handles, link) + unref_handle(h); + free(support->i18n_domain); spa_zero(global_support); pthread_mutex_unlock(&support_lock); @@ -721,16 +720,9 @@ done: SPA_EXPORT bool pw_debug_is_category_enabled(const char *name) { - int i; - - if (global_support.categories == NULL) - return false; - - for (i = 0; global_support.categories[i]; i++) { - if (spa_streq(global_support.categories[i], name)) - return true; - } - return false; + struct spa_log_topic t = SPA_LOG_TOPIC(0, name); + PW_LOG_TOPIC_INIT(&t); + return t.has_custom_level; } /** Get the application name */ @@ -826,6 +818,8 @@ bool pw_check_option(const char *option, const char *value) return global_support.no_color == spa_atob(value); else if (spa_streq(option, "no-config")) return global_support.no_config == spa_atob(value); + else if (spa_streq(option, "do-dlclose")) + return global_support.do_dlclose == spa_atob(value); return false; } diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 808eb47473c460fd659949cf8fda5dc6a5b26415..a7c1da1870c0e92527940bb1792c52f92bfeae87 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -324,7 +324,8 @@ static inline int queue_push(struct stream *stream, struct queue *queue, struct { uint32_t index; - if (SPA_FLAG_IS_SET(buffer->flags, BUFFER_FLAG_QUEUED)) + if (SPA_FLAG_IS_SET(buffer->flags, BUFFER_FLAG_QUEUED) || + buffer->id >= stream->n_buffers) return -EINVAL; SPA_FLAG_SET(buffer->flags, BUFFER_FLAG_QUEUED); @@ -921,6 +922,9 @@ static int impl_port_use_buffers(void *object, if (impl->disconnecting && n_buffers > 0) return -EIO; + if (n_buffers > MAX_BUFFERS) + return -EINVAL; + prot = PROT_READ | (direction == SPA_DIRECTION_OUTPUT ? PROT_WRITE : 0); clear_buffers(stream); @@ -956,6 +960,7 @@ static int impl_port_use_buffers(void *object, pw_log_debug("%p: got buffer id:%d datas:%d, mapped size %d", stream, i, buffers[i]->n_datas, size); } + impl->n_buffers = n_buffers; for (i = 0; i < n_buffers; i++) { struct buffer *b = &impl->buffers[i]; @@ -972,9 +977,6 @@ static int impl_port_use_buffers(void *object, pw_stream_emit_add_buffer(stream, &b->this); } - - impl->n_buffers = n_buffers; - return 0; } @@ -1000,6 +1002,7 @@ static int impl_node_process_input(void *object) if (io->status == SPA_STATUS_HAVE_DATA && (b = get_buffer(stream, io->buffer_id)) != NULL) { /* push new buffer */ + pw_log_trace_fp("%p: push %d %p", stream, b->id, io); if (queue_push(impl, &impl->dequeued, b) == 0) { copy_position(impl, impl->dequeued.incount); if (b->busy) @@ -1007,13 +1010,15 @@ static int impl_node_process_input(void *object) call_process(impl); } } - if (io->status != SPA_STATUS_NEED_DATA) { + if (io->status != SPA_STATUS_NEED_DATA || io->buffer_id == SPA_ID_INVALID) { /* pop buffer to recycle */ if ((b = queue_pop(impl, &impl->queued))) { pw_log_trace_fp("%p: recycle buffer %d", stream, b->id); - } else if (io->status == -EPIPE) - return io->status; - io->buffer_id = b ? b->id : SPA_ID_INVALID; + io->buffer_id = b->id; + } else { + pw_log_trace_fp("%p: no buffers to recycle", stream); + io->buffer_id = SPA_ID_INVALID; + } io->status = SPA_STATUS_NEED_DATA; } if (impl->driving && impl->using_trigger) diff --git a/src/pipewire/utils.c b/src/pipewire/utils.c index 072f472777842b3e25aaee44887b763093766fcb..1d02714f3ba54af83e933f4089fe730e37c81fc7 100644 --- a/src/pipewire/utils.c +++ b/src/pipewire/utils.c @@ -97,6 +97,37 @@ char **pw_split_strv(const char *str, const char *delimiter, int max_tokens, int return arr.data; } +/** Split a string in-place based on delimiters + * \param str a string to split + * \param delimiter delimiter characters to split on + * \param max_tokens the max number of tokens to split + * \param[out] tokens an array to hold up to \a max_tokens of strings + * \return the number of tokens in \a tokens + * + * \a str will be modified in-place so that \a tokens will contain zero terminated + * strings split at \a delimiter characters. + */ +SPA_EXPORT +int pw_split_ip(char *str, const char *delimiter, int max_tokens, char *tokens[]) +{ + const char *state = NULL; + char *s, *t; + size_t len, l2; + int n = 0; + + s = (char *)pw_split_walk(str, delimiter, &len, &state); + while (s && n + 1 < max_tokens) { + t = (char*)pw_split_walk(str, delimiter, &l2, &state); + s[len] = '\0'; + tokens[n++] = s; + s = t; + len = l2; + } + if (s) + tokens[n++] = s; + return n; +} + /** Free a NULL terminated array of strings * \param str a NULL terminated array of string * diff --git a/src/pipewire/utils.h b/src/pipewire/utils.h index c04d6ea136fa851edeedef68e2226ede375173dd..8106710dc0009752968a06b6f8cfb3136f1ecb1a 100644 --- a/src/pipewire/utils.h +++ b/src/pipewire/utils.h @@ -58,6 +58,9 @@ pw_split_walk(const char *str, const char *delimiter, size_t *len, const char ** char ** pw_split_strv(const char *str, const char *delimiter, int max_tokens, int *n_tokens); +int +pw_split_ip(char *str, const char *delimiter, int max_tokens, char *tokens[]); + void pw_free_strv(char **str); diff --git a/src/tools/pw-cli.c b/src/tools/pw-cli.c index bb5ab99cb521fa6ad2fc5af668ffc39424f4184a..4dfa8c71b5e3e373b4a744876aabbc86b8e7508e 100644 --- a/src/tools/pw-cli.c +++ b/src/tools/pw-cli.c @@ -142,26 +142,6 @@ static struct global * obj_global(struct remote_data *rd, uint32_t id); static int children_of(struct remote_data *rd, uint32_t parent_id, const char *child_type, uint32_t **children); -static int pw_split_ip(char *str, const char *delimiter, int max_tokens, char *tokens[]) -{ - const char *state = NULL; - char *s, *t; - size_t len, l2; - int n = 0; - - s = (char *)pw_split_walk(str, delimiter, &len, &state); - while (s && n + 1 < max_tokens) { - t = (char*)pw_split_walk(str, delimiter, &l2, &state); - s[len] = '\0'; - tokens[n++] = s; - s = t; - len = l2; - } - if (s) - tokens[n++] = s; - return n; -} - static void print_properties(struct spa_dict *props, char mark, bool header) { const struct spa_dict_item *item; diff --git a/src/tools/pw-mon.c b/src/tools/pw-mon.c index f7e088c65229b1d2f9ec4c2582c0ae8712a84924..2eeb4dd0e370fe6bad87473f3935f5a492b34ea6 100644 --- a/src/tools/pw-mon.c +++ b/src/tools/pw-mon.c @@ -780,7 +780,7 @@ int main(int argc, char *argv[]) if (isatty(STDERR_FILENO) && getenv("NO_COLOR") == NULL) colors = true; - while ((c = getopt_long(argc, argv, "hVr:", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hVr:NC", long_options, NULL)) != -1) { switch (c) { case 'h': show_help(argv[0], false); diff --git a/src/tools/pw-top.c b/src/tools/pw-top.c index 61a05c5958f0ace96a628efc696c9ff3e240171e..7574cab906b41be3aa844f7b87f230631556941a 100644 --- a/src/tools/pw-top.c +++ b/src/tools/pw-top.c @@ -663,11 +663,20 @@ static void on_core_error(void *_data, uint32_t id, int seq, int res, const char { struct data *data = _data; - pw_log_error("error id:%u seq:%d res:%d (%s): %s", - id, seq, res, spa_strerror(res), message); - - if (id == PW_ID_CORE && res == -EPIPE) - pw_main_loop_quit(data->loop); + if (id == PW_ID_CORE) { + switch (res) { + case -EPIPE: + pw_main_loop_quit(data->loop); + break; + default: + pw_log_error("error id:%u seq:%d res:%d (%s): %s", + id, seq, res, spa_strerror(res), message); + break; + } + } else { + pw_log_info("error id:%u seq:%d res:%d (%s): %s", + id, seq, res, spa_strerror(res), message); + } } static void on_core_done(void *_data, uint32_t id, int seq) diff --git a/test/test-logger.c b/test/test-logger.c index 5aca7d1998448472ebc274bfa473517069601136..b40445a3ea6af52a03ef97c156258ade0e0fc8c3 100644 --- a/test/test-logger.c +++ b/test/test-logger.c @@ -350,8 +350,10 @@ PWTEST(logger_debug_env_invalid) "invalid value", "*:5,some invalid value", "*:W,foo.bar:3,invalid:", - "*:W,1,foo.bar:D", - "*:W,D,foo.bar:3", + "*:W,2,foo.bar:Q", + "*:W,7,foo.bar:D", + "*:W,Q,foo.bar:5", + "*:W,D,foo.bar:8", }; pwtest_int_lt(which, SPA_N_ELEMENTS(envvars)); @@ -644,7 +646,7 @@ PWTEST_SUITE(logger) PWTEST_ARG_RANGE, SPA_LOG_LEVEL_NONE, SPA_LOG_LEVEL_TRACE + 1, PWTEST_NOARG); pwtest_add(logger_debug_env_invalid, - PWTEST_ARG_RANGE, 0, 5, /* see the test */ + PWTEST_ARG_RANGE, 0, 7, /* see the test */ PWTEST_NOARG); pwtest_add(logger_topics, PWTEST_NOARG); pwtest_add(logger_journal, PWTEST_NOARG); diff --git a/test/test-spa-buffer.c b/test/test-spa-buffer.c index e9f5620b00fc4aa25de05fed6653462c1a677940..89bfabdcb8dfcf18a28baa6ed96ed6c8ac650ee9 100644 --- a/test/test-spa-buffer.c +++ b/test/test-spa-buffer.c @@ -47,7 +47,8 @@ PWTEST(buffer_abi_types) pwtest_int_eq(SPA_META_Cursor, 5); pwtest_int_eq(SPA_META_Control, 6); pwtest_int_eq(SPA_META_Busy, 7); - pwtest_int_eq(_SPA_META_LAST, 8); + pwtest_int_eq(SPA_META_VideoTransform, 8); + pwtest_int_eq(_SPA_META_LAST, 9); return PWTEST_PASS; } @@ -64,6 +65,7 @@ PWTEST(buffer_abi_sizes) pwtest_int_eq(sizeof(struct spa_meta_region), 16U); pwtest_int_eq(sizeof(struct spa_meta_bitmap), 20U); pwtest_int_eq(sizeof(struct spa_meta_cursor), 28U); + pwtest_int_eq(sizeof(struct spa_meta_videotransform), 4U); return PWTEST_PASS; #else @@ -75,6 +77,7 @@ PWTEST(buffer_abi_sizes) fprintf(stderr, "%zd\n", sizeof(struct spa_meta_region)); fprintf(stderr, "%zd\n", sizeof(struct spa_meta_bitmap)); fprintf(stderr, "%zd\n", sizeof(struct spa_meta_cursor)); + fprintf(stderr, "%zd\n", sizeof(struct spa_meta_videotransform)); return PWTEST_SKIP; #endif } diff --git a/test/test-utils.c b/test/test-utils.c index 9b7c520e244679b43945c8297722f68dd922f011..84a2fcb4cd4fb8b71830fd0ca37e6d0a5fd66d7d 100644 --- a/test/test-utils.c +++ b/test/test-utils.c @@ -193,6 +193,8 @@ static void test__pw_split_strv(void) { const char *test1 = "a \n test string \n \r "; const char *del = "\n\r "; + const char *test2 = "a:"; + const char *del2 = ":"; int n_tokens; char **res; @@ -212,6 +214,13 @@ static void test__pw_split_strv(void) pwtest_str_eq(res[1], "test string \n \r "); pwtest_ptr_null(res[2]); pw_free_strv(res); + + res = pw_split_strv(test2, del2, 2, &n_tokens); + pwtest_ptr_notnull(res); + pwtest_int_eq(n_tokens, 1); + pwtest_str_eq(res[0], "a"); + pwtest_ptr_null(res[1]); + pw_free_strv(res); } PWTEST(utils_split)