diff --git a/NEWS b/NEWS index eec8787760b81e07aeddfd058993d7261c2f528c..72d442f54fffd7b342c03ad877f571490e17d987 100644 --- a/NEWS +++ b/NEWS @@ -1,9 +1,92 @@ +# PipeWire 0.3.58 (2022-09-15) + +This is a bugfix release that is API and ABI compatible with previous +0.3.x releases. + +## Highlights + - Fix a regression that could cause audio crackling. + - Fix a regression in RTKit because rlimit was not set correctly. + - JAVA sound applications will now alsa work with the pulseaudio-alsa plugin. + - pw-top will now show the negotiated formats of devices and streams. + - Fix some potential crashes when starting streams. + - The ALSA plugin has had improved timing reporting and poll descriptor + handling that should improve compatibility. + - Many more improvements and bugfixes. + + +## PipeWire + - Avoid scheduling nodes before they are added to the graph. This could + avoid some crashes when scheduling nodes that were not completely + started yet. (#2677) + +## Tools + - pw-top now also shows the negotiated formats of streams and devices. + (#2566) + - pw-top prints microseconds as "us" now to avoid unicode problems. + +## Modules + - Fix compilation with newer lv2. + - Fix setting realtime priority with RTKit, it was not setting rlimit + correctly and RTKit would refuse to change the priority. + - Fix some playback problems with RAOP sink. (#2673) + - Filter chain will now warn when a non-existing control property is + used in the config file. (#2685) + - Filter chain can now handle control port names with ":" in the name. + (#2685) + - The echo-cancel module and interface now has activate/deactivate + functions to make it possible for plugins to reset their state. + +## SPA + - Make sure audioconvert uses the given channelmap and channels for the + volumes, even when not negotiated yet. This makes it possible to change + the volume before the node has been negotiated. + - Refactor the peaks resampler. Fix an error in the SSE code. + - Fix DSD min/max rates, avoid exposing impossible rates. + - Set monitor port buffer size correctly. This could cause some crackling + and hickups. (#2677) + - Make ALSA sequencer port names unique. + +## Pulse-server + - Rework the capture buffer attributes to better match pulseaudio. This + fixes a regression where opening pavucontrol could cause crackling. + (#2671) + - Implement TRIGGER and PREBUF methods. + - Handle clients that send more than the requested amount of data. + PipeWire will now also keep this as extra buffered data like PulseAudio. + This fixes JAVA sound applications when they are running on top of the + PulseAudio ALSA plugin. (#2626,#2674) + - Update the requested amount of bytes more like PulseAudio. Fixes + stuttering after resume with the GStreamer pulseaudio sink. (#2680) + +## ALSA Plugin + - More debug info was added. The time reporting was improved. + - The poll descriptor handling was improved, avoiding some spurious + wakeups. (#1697) + + +Older versions: + + # PipeWire 0.3.57 (2022-09-02) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights + - Support masking of conf.d/ files. (#2629) + - Use org.freedesktop.portal.Realtime when available. This does the + correct PID/TID mappings to make realtime also work from flatpaks. + - Fix rate adjustement logic in pulse-tunnel. This would cause + increasing delays and hickups when using tunnels. (#2548) + - Add OPUS as a new vendor codec. Add OPUS-A2DP spec. PipeWire can now + send and reveive OPUS data over bluetooth. + - An AAC decoder was added so that PipeWire can now also function as + an A2DP AAC receiver. + - Fix some issues where the wrong samplerate was used. (#2614) + - Fix rate match for sources. This fixes an error where follower sources + would generate many resync warnings. + - Many more bugfixes and improvements. + ## PipeWire - Support masking of conf.d/ files. (#2629) @@ -16,7 +99,6 @@ This is a bugfix release that is API and ABI compatible with previous host_machine. - Check return values of pw_getrandom(). - ## Tools - Updates to pw-cli manpages. (#2552) - Remove the pw-cli dump command. It is mostly implemented as part of @@ -85,9 +167,6 @@ This is a bugfix release that is API and ABI compatible with previous - JACK clients from the same application will be added to the same group so that they share the quantum and rate. - -Older versions: - # PipeWire 0.3.56 (2022-07-19) This is a quick bugfix release that is API and ABI compatible with previous diff --git a/debian/changelog b/debian/changelog index 49184d6f3f6551a0a706f01f715d00bad4d10911..e4f13fa60a6c43d2ceeb11600d07212610b7f99e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,46 @@ +pipewire (0.3.58-2~bpo11+1) bullseye-backports; urgency=medium + + * Rebuild for bullseye-backports. + * Disable XFixes, minimum required version not available in Bullseye + * Reenable libfreeaptx, now in bullseye-backports + * Do not mark pipewire-pulse in conflict with pulseaudio + Bullseye (and users) is not ready for this change. + + -- Dylan Aïssi <daissi@debian.org> Fri, 30 Sep 2022 11:30:26 +0200 + +pipewire (0.3.58-2) unstable; urgency=medium + + * Mention to install pipewire-alsa and pipewire-jack + in README.Debian (Closes: #1019971) + * Add debian/pipewire-alsa.TODO + * Patch pipewire-pulse.service to be sure it is started + after a session manager (Closes: #1019944) + Because of a bug in the way systemd handles aliases, they have been removed + in wireplumber and pipewire-media-session services to avoid a conflict. + This change needs to be reflected in the pipewire-pulse service to be sure + it is started after a session manager, otherwise pipewire-pulse doesn't + see any devices. + + -- Dylan Aïssi <daissi@debian.org> Fri, 23 Sep 2022 11:01:16 +0200 + +pipewire (0.3.58-1) unstable; urgency=medium + + [ Dylan Aïssi ] + * New upstream release + - Fix crackling sound if pavucontrol is open (Closes: #1019888) + * Create a pipewire group and define real-time priority limits + (Closes: #1011399) + * Add suggestion to install wireplumber in pipewire.README.Debian + * Clarify relation between pipewire and libspa-0.2-bluetooth in + pipewire.README.Debian (Closes: #998220, #1011035) + * Remove reference to experimental status of pipewire for audio + + [ Sebastien Bacher ] + * Let pipewire-pulse conflicts on pulseaudio + (Closes: #1013276, LP: #1975823) + + -- Dylan Aïssi <daissi@debian.org> Fri, 16 Sep 2022 15:41:31 +0200 + pipewire (0.3.57-1~bpo11+1) bullseye-backports; urgency=medium * Rebuild for bullseye-backports. diff --git a/debian/control b/debian/control index 04abc683df47068c01e0c025d197faa91d00c525..e8505e31b65c35fb301ee7d3a390f5e085aae55e 100644 --- a/debian/control +++ b/debian/control @@ -20,7 +20,7 @@ Build-Depends: debhelper-compat (= 13), libldacbt-enc-dev [!s390x !hppa !m68k !powerpc !ppc64 !sparc64], liblilv-dev, libncurses-dev, -# libfreeaptx-dev, + libfreeaptx-dev, libpulse-dev, libreadline-dev, libsbc-dev, @@ -348,9 +348,7 @@ Description: libraries for the PipeWire multimedia server - bluetooth plugins - Generating graphs for audio and video processing. . This package contains a plugin to make Bluetooth audio devices such as - speakers and headsets available to the PipeWire server. It is considered - to be experimental, and is disabled by default (even if installed) to - avoid conflicts with equivalent functionality in PulseAudio. + speakers and headsets available to the PipeWire server. Package: libspa-0.2-jack Architecture: linux-any @@ -369,7 +367,6 @@ Description: libraries for the PipeWire multimedia server - JACK client . This package contains a plugin to make PipeWire able to connect to a JACK server, which will be used for audio playback and recording. - Using PipeWire for audio is considered to be experimental. Package: pipewire-audio-client-libraries Section: oldlibs diff --git a/debian/patches/Fix_services.patch b/debian/patches/Fix_services.patch new file mode 100644 index 0000000000000000000000000000000000000000..fe19732919546ca84c8d4be1bddb55ef23483e5c --- /dev/null +++ b/debian/patches/Fix_services.patch @@ -0,0 +1,23 @@ +Description: Update Wants and After fields with real services (NOT alias) + Due to a systemd bug in the way it manages alias, they have been removed in + wireplumber and pipewire-media-session services. Reflecting this change in + the pipewire-pulse service to be sure it is started AFTER a session manager. +Bug: https://github.com/systemd/systemd/issues/23694 +Bug-Debian: https://bugs.debian.org/997818 + https://bugs.debian.org/1019944 +Author: Dylan Aïssi <daissi@debian.org> +Forwarded: not-needed + +--- a/src/daemon/systemd/user/pipewire-pulse.service.in ++++ b/src/daemon/systemd/user/pipewire-pulse.service.in +@@ -15,8 +15,8 @@ + # socket-service relationship, see systemd.socket(5). + Requires=pipewire-pulse.socket + ConditionUser=!root +-Wants=pipewire.service pipewire-session-manager.service +-After=pipewire.service pipewire-session-manager.service ++Wants=pipewire.service wireplumber.service pipewire-media-session.service ++After=pipewire.service wireplumber.service pipewire-media-session.service + Conflicts=pulseaudio.service + + [Service] diff --git a/debian/patches/series b/debian/patches/series index 68ea67cb3cb189bac09334b1c603440c2c43ecd8..2eb644dcb91d7d58e65d2cb8ab0f14e08ca8d81e 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,3 +1,4 @@ Don-t-automatically-start-pipewire-for-root-logins.patch Don-t-build_same_binary_twice.patch +Fix_services.patch # Recommended patch for 0.3.5X diff --git a/debian/pipewire-alsa.TODO b/debian/pipewire-alsa.TODO new file mode 100644 index 0000000000000000000000000000000000000000..e74161c603be857565561a2ce5cc099a699d584f --- /dev/null +++ b/debian/pipewire-alsa.TODO @@ -0,0 +1,13 @@ +# https://bugs.debian.org/1019971 + +Since pipewire-alsa has been split from the previous pipewire-audio-client-libraries +package, it would be useful to directly install the 99-pipewire-default.conf +file in the right location: Install 99-pipewire-default.conf in +usr/share/alsa/alsa.conf.d/ and create a symlink to etc/alsa/conf.d/. +Similarly to what is done for 50-pipewire.conf. + +But, people upgrading their pipewire-audio-client-libraries package will +have pipewire-alsa installed and enabled by default on their system +even if it was not enabled before the upgrade. + +Thus, this should be done at least after the release of Bookworm. diff --git a/debian/pipewire-bin.install b/debian/pipewire-bin.install index e5808a3e4bc538a340cb230c9baedf1bead0237e..0b7a86247bb4425d44745e95b19bf17651acb75a 100644 --- a/debian/pipewire-bin.install +++ b/debian/pipewire-bin.install @@ -37,3 +37,4 @@ usr/share/man/man1/pw-mon.* usr/share/man/man1/pw-profiler.* usr/share/man/man1/pw-top.* usr/share/man/man5 +debian/rlimits/95-pipewire.conf /etc/security/limits.d/ diff --git a/debian/pipewire.README.Debian b/debian/pipewire.README.Debian index 7b07c97a7a95fd909b16497c4cea1a4437550186..27071dae185e301d71d01502bb9313594fca8288 100644 --- a/debian/pipewire.README.Debian +++ b/debian/pipewire.README.Debian @@ -1,21 +1,42 @@ Using pipewire for audio ======================== -pipewire in Debian is primarily intended to be used for video -(screen sharing and remote desktop, particularly in GNOME and KDE Wayland -sessions). However, it can also be used for audio. +pipewire in Debian was initially used for video (screen sharing and remote +desktop, particularly in GNOME and KDE Wayland sessions). However, it has +matured enough to also be used for audio. -This is not a supported scenario for Debian 11, and is considered -experimental. Using pipewire as a substitute for PulseAudio --------------------------------------------- -Install the pipewire-pulse package and log back in +Install the pipewire-pulse package and log back in. +And potentially install the new recommended session manager +wireplumber instead of the deprecated pipewire-media-session. + + +Using pipewire with Bluetooth +--------------------------------------------- + +Install the libspa-0.2-bluetooth package to add Bluetooth support to pipewire. + +Pipewire (nor any other package) does not depend or recommend libspa-0.2-bluetooth +as not all users are interested in Bluetooth support. +The same apply to pulseaudio and pulseaudio-module-bluetooth: +pulseaudio does not depend or recommend pulseaudio-module-bluetooth. + +However, pulseaudio-module-bluetooth and libspa-0.2-bluetooth are installed on +a system through the dependency of a Desktop Environment. For example, +with GNOME, the bluetooth plugin is pulled by the meta package gnome-core. + +Moreover, both pipewire-pulse and wireplumber packages already suggest +to install the libspa-0.2-bluetooth package. + Using pipewire as the default ALSA output device ------------------------------------------------ +Install the pipewire-alsa package. + ALSA clients can be configured to output via pipewire instead of PulseAudio or directly to ALSA. @@ -26,9 +47,12 @@ To enable this: * copy /usr/share/doc/pipewire/examples/alsa.conf.d/99-pipewire-default.conf into /etc/alsa/conf.d/ + Using pipewire as a substitute for JACK --------------------------------------- +Install the pipewire-jack package. + JACK clients can be configured to output via pipewire instead of JACK. To enable this: @@ -38,3 +62,28 @@ To enable this: * either run JACK clients using the pw-jack(1) wrapper, or copy /usr/share/doc/pipewire/examples/ld.so.conf.d/pipewire-jack-*.conf into /etc/ld.so.conf.d/ and run ldconfig as root. + + +Setting pipewire real-time priority limits +--------------------------------------- + +!!! WARNING !!! +Your system has real-time priority limits for a good reason. This allows it +to remain stable in case a process goes crazy. RTKit allows limited use of +real-time priority without the risk of locking up the system if a real-time +task starts spinning. +Some upstream recommendations for real-time are to increase these limits to +bypass RTKit or to disable most of its safeguards. By following them, your +system could be blocked if a process goes wrong. +These performance tweaks are not needed for a normal use of pipewire, instead +modifying pipewire configuration is enough. +!!! WARNING !!! + +The "pipewire" package creates a system group called "pipewire". +The upstream recommended priority limits for this group are defined in: + +* /etc/security/limits.d/95-pipewire.conf + +To enable these limits for your user, add it to the "pipewire" group. + +* sudo adduser yourusername pipewire diff --git a/debian/pipewire.postinst b/debian/pipewire.postinst new file mode 100644 index 0000000000000000000000000000000000000000..0c706702189f9d3673234428942d2a2d93473f5c --- /dev/null +++ b/debian/pipewire.postinst @@ -0,0 +1,11 @@ +#!/bin/sh +set -e + +if [ "$1" = "configure" ] ; then + # Create the pipewire system group for setting real-time priority limits + if ! getent group pipewire > /dev/null; then + addgroup --quiet --system pipewire + fi +fi + +#DEBHELPER# diff --git a/debian/pipewire.postrm b/debian/pipewire.postrm new file mode 100644 index 0000000000000000000000000000000000000000..ccde1e254f0fb7c7a2cd7c3a945a848eb7d171a4 --- /dev/null +++ b/debian/pipewire.postrm @@ -0,0 +1,8 @@ +#!/bin/sh +set -e + +if [ "$1" = "purge" ] ; then + delgroup --quiet --system pipewire > /dev/null || true +fi + +#DEBHELPER# diff --git a/debian/rlimits/95-pipewire.conf b/debian/rlimits/95-pipewire.conf new file mode 100644 index 0000000000000000000000000000000000000000..47f9ab46c093a39b72e8d50cd92582530ea97e05 --- /dev/null +++ b/debian/rlimits/95-pipewire.conf @@ -0,0 +1,4 @@ +# Default limits for users of pipewire +@pipewire - rtprio 95 +@pipewire - nice -19 +@pipewire - memlock 4194304 diff --git a/debian/rlimits/README b/debian/rlimits/README new file mode 100644 index 0000000000000000000000000000000000000000..58d4ad24ecf20dc7c1d81b6c9d23f8b1affd4118 --- /dev/null +++ b/debian/rlimits/README @@ -0,0 +1,8 @@ +Setting pipewire real-time priority limits +--------------------------------------- + +The debian/rlimits/95-pipewire.conf defines the real-time priority limits +for the system group "pipewire" as recommended by upstream. +This file is installed in /etc/security/limits.d/. + +See https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Performance-tuning#rlimits diff --git a/debian/rules b/debian/rules index bbf9fe65c35d10a62aaab32d5802cfd1a318c4bf..39aace0c3b7cc658f3e6601d9f3ea7a4b256f95d 100755 --- a/debian/rules +++ b/debian/rules @@ -31,7 +31,6 @@ override_dh_auto_configure: -Dauto_features=enabled \ -Davahi=enabled \ -Dbluez5-codec-aac=disabled \ - -Dbluez5-codec-aptx=disabled \ -Dbluez5-codec-lc3plus=disabled \ -Dbluez5-codec-ldac=$(BLUEZ5_CODEC_LDAC) \ -Dlibcamera=disabled \ diff --git a/doc/index.dox b/doc/index.dox index e61642cc1fd570d236fdcac745a88b17f6c3b2af..1602b7b7f80d2c19c06066db408a85ae808f6796 100644 --- a/doc/index.dox +++ b/doc/index.dox @@ -40,5 +40,6 @@ More information on how to configure and use PipeWire. - [PipeWire: The Linux audio/video bus (LWN)](https://lwn.net/Articles/847412) - [PipeWire Wikipedia](https://en.wikipedia.org/wiki/PipeWire) - [Bluetooth, PipeWire and Whatsapp calls](https://gjhenrique.com/pipewire.html) +- [Intoduction to PipeWire](https://bootlin.com/blog/an-introduction-to-pipewire/) */ diff --git a/man/pw-cat.1.rst.in b/man/pw-cat.1.rst.in index bc386bb33af3fc7409514a8bf2652d997bc50b8a..1e7687f82d75a6a788be0357f4d6a660a7db5cf4 100644 --- a/man/pw-cat.1.rst.in +++ b/man/pw-cat.1.rst.in @@ -26,9 +26,14 @@ capturing raw or encoded media files on a PipeWire server. It understands all audio file formats supported by ``libsndfile`` for PCM capture and playback. -It understands standard MIDI files for playback and recording, +It understands standard MIDI files for playback and recording. This tool +will not render MIDI files, it will simply make the MIDI events available +to the graph. You need a MIDI renderer such as qsynth, timidity or a hardware +MIDI rendered to hear the MIDI. -DSD playback is supported with the DSF file format. +DSD playback is supported with the DSF file format. This tool will only work +with native DSD capable hardware and will produce an error when no such +hardware was found. When the *FILE* is - input and output will be from STDIN and STDOUT respectively. @@ -60,10 +65,15 @@ OPTIONS -m | --midi MIDI mode. *FILE* is a MIDI file. If the tool is called under the name **pw-midiplay** or **pw-midirecord** this is the default. + Note that this program will *not* render the MIDI events into audible samples, + it will simply provide the MIDI events in the graph. You need a separate + MIDI renderer such as qsynth, timidity or a hardware renderer to hear the MIDI. -d | --dsd DSD mode. *FILE* is a DSF file. If the tool is called under the name **pw-dsdplay** this is the default. + Note that this program will *not* render the DSD audio. You need a DSD capable + device to play DSD content or this program will exit with an error. --media-type=VALUE Set the media type property (default Audio/Midi depending on mode). diff --git a/man/pw-top.1.rst.in b/man/pw-top.1.rst.in index 36625e308f3e2b16f49c0e457217b9cfcb384ca9..afd3573475a8dba839761cbff1a1d395f508cd28 100644 --- a/man/pw-top.1.rst.in +++ b/man/pw-top.1.rst.in @@ -61,7 +61,7 @@ RATE The current rate (for drivers) and the suggested rate for follower nodes. - This is the rate at which the graph processes data and needs to be combined with + This is the rate at which the *graph* processes data and needs to be combined with the QUANT value to derive the duration of a processing cycle in the graph. Some nodes can have a 0 RATE, which means that they don't have any rate @@ -72,7 +72,8 @@ RATE The RATE on (audio) driver nodes usually also translates directly to the samplerate used by the device. Although some devices might not be able to operate at the given samplerate, in which case resampling will need to be - done. + done. The negotiated samplerate with the device and stream can be found in + the FORMAT column. WAIT The waiting time of a node is the elapsed time between when the node @@ -125,6 +126,19 @@ ERR Xruns for followers are incremented when the node started processing but did not complete before the end of the graph cycle deadline. +FORMAT + The format used by the driver node or the stream. This is the hardware format + negotiated with the device or stream. + + If the stream of driver has a different rate than the graph, resampling will + be done. + + For raw audio formats, the layout is <sampleformat> <channels> <samplerate>. + + For DSD formats, the layout is <dsd-rate> <channels>. + + For Video formats, the layout is <pixelformat> <width>x<height>. + NAME Name assigned to the device/node, as found in *pw-dump* node.name diff --git a/meson.build b/meson.build index 2634b711757f544556703f2e5ac05f9da77fc0ca..e7a1d8590183df74c7dfe50b8749b8a530915783 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('pipewire', ['c' ], - version : '0.3.57', + version : '0.3.58', license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ], meson_version : '>= 0.59.0', default_options : [ 'warning_level=3', diff --git a/pipewire-alsa/alsa-plugins/pcm_pipewire.c b/pipewire-alsa/alsa-plugins/pcm_pipewire.c index 412b71fc26e6f8309cf437a48d5730d672c003e4..e3c58ccef4f87d21e9c87173f76687634a1b68c0 100644 --- a/pipewire-alsa/alsa-plugins/pcm_pipewire.c +++ b/pipewire-alsa/alsa-plugins/pcm_pipewire.c @@ -44,6 +44,15 @@ #include <pipewire/pipewire.h> +#define ATOMIC_INC(s) __atomic_add_fetch(&(s), 1, __ATOMIC_SEQ_CST) +#define ATOMIC_LOAD(s) __atomic_load_n(&(s), __ATOMIC_SEQ_CST) + +#define SEQ_WRITE(s) ATOMIC_INC(s) +#define SEQ_WRITE_SUCCESS(s1,s2) ((s1) + 1 == (s2) && ((s2) & 1) == 0) + +#define SEQ_READ(s) ATOMIC_LOAD(s) +#define SEQ_READ_SUCCESS(s1,s2) ((s1) == (s2) && ((s2) & 1) == 0) + PW_LOG_TOPIC_STATIC(alsa_log_topic, "alsa.pcm"); #define PW_LOG_TOPIC_DEFAULT alsa_log_topic @@ -58,6 +67,9 @@ PW_LOG_TOPIC_STATIC(alsa_log_topic, "alsa.pcm"); typedef struct { snd_pcm_ioplug_t io; + snd_output_t *output; + FILE *log_file; + char *node_name; char *target; char *role; @@ -69,6 +81,7 @@ typedef struct { unsigned int draining:1; unsigned int xrun_detected:1; unsigned int hw_params_changed:1; + unsigned int active:1; snd_pcm_uframes_t hw_ptr; snd_pcm_uframes_t boundary; @@ -89,53 +102,62 @@ typedef struct { struct pw_stream *stream; struct spa_hook stream_listener; - struct pw_time time; + int64_t delay; + uint64_t now; + uintptr_t seq; struct spa_audio_info_raw format; } snd_pcm_pipewire_t; static int snd_pcm_pipewire_stop(snd_pcm_ioplug_t *io); -static int block_check(snd_pcm_ioplug_t *io) +static int check_active(snd_pcm_ioplug_t *io) { snd_pcm_pipewire_t *pw = io->private_data; snd_pcm_sframes_t avail; - uint64_t val; + bool active; avail = snd_pcm_ioplug_avail(io, pw->hw_ptr, io->appl_ptr); - if (avail >= 0 && avail < (snd_pcm_sframes_t)pw->min_avail) { - spa_system_eventfd_read(pw->system, io->poll_fd, &val); - return 1; + + if (io->state == SND_PCM_STATE_DRAINING) { + active = pw->drained; } - return 0; + else if (avail >= 0 && avail < (snd_pcm_sframes_t)pw->min_avail) { + active = false; + } + else if (avail >= (snd_pcm_sframes_t)pw->min_avail) { + active = true; + } else { + active = false; + } + if (pw->active != active) { + pw_log_trace("%p: avail:%lu min-avail:%lu state:%s hw:%lu appl:%lu active:%d->%d state:%s", + pw, avail, pw->min_avail, snd_pcm_state_name(io->state), + pw->hw_ptr, io->appl_ptr, pw->active, active, + snd_pcm_state_name(io->state)); + } + return active; } -static int pcm_poll_block_check(snd_pcm_ioplug_t *io) + +static int update_active(snd_pcm_ioplug_t *io) { snd_pcm_pipewire_t *pw = io->private_data; + bool active; - if (io->state == SND_PCM_STATE_DRAINING) { + active = check_active(io); + + if (pw->active != active) { uint64_t val; - spa_system_eventfd_read(pw->system, io->poll_fd, &val); - return 0; - } else if (io->state == SND_PCM_STATE_RUNNING || - (io->state == SND_PCM_STATE_PREPARED && io->stream == SND_PCM_STREAM_CAPTURE)) { - return block_check(io); - } - return 0; -} -static inline int pcm_poll_unblock_check(snd_pcm_ioplug_t *io) -{ - snd_pcm_pipewire_t *pw = io->private_data; - snd_pcm_uframes_t avail; + pw->active = active; - avail = snd_pcm_ioplug_avail(io, pw->hw_ptr, io->appl_ptr); - if (avail >= pw->min_avail || io->state == SND_PCM_STATE_DRAINING) { - spa_system_eventfd_write(pw->system, pw->fd, 1); - return 1; + if (active) + spa_system_eventfd_write(pw->system, io->poll_fd, 1); + else + spa_system_eventfd_read(pw->system, io->poll_fd, &val); } - return 0; + return active; } static void snd_pcm_pipewire_free(snd_pcm_pipewire_t *pw) @@ -143,7 +165,7 @@ static void snd_pcm_pipewire_free(snd_pcm_pipewire_t *pw) if (pw == NULL) return; - pw_log_debug("%p:", pw); + pw_log_debug("%p: free", pw); if (pw->main_loop) pw_thread_loop_stop(pw->main_loop); if (pw->stream) @@ -156,13 +178,15 @@ static void snd_pcm_pipewire_free(snd_pcm_pipewire_t *pw) pw_thread_loop_destroy(pw->main_loop); free(pw->node_name); free(pw->target); + snd_output_close(pw->output); + fclose(pw->log_file); free(pw); } static int snd_pcm_pipewire_close(snd_pcm_ioplug_t *io) { snd_pcm_pipewire_t *pw = io->private_data; - pw_log_debug("%p:", pw); + pw_log_debug("%p: close", pw); snd_pcm_pipewire_free(pw); return 0; } @@ -170,7 +194,7 @@ static int snd_pcm_pipewire_close(snd_pcm_ioplug_t *io) static int snd_pcm_pipewire_poll_descriptors(snd_pcm_ioplug_t *io, struct pollfd *pfds, unsigned int space) { snd_pcm_pipewire_t *pw = io->private_data; - pcm_poll_unblock_check(io); /* unblock socket for polling if needed */ + update_active(io); pfds->fd = pw->fd; pfds->events = POLLIN | POLLERR | POLLNVAL; return 1; @@ -188,7 +212,7 @@ static int snd_pcm_pipewire_poll_revents(snd_pcm_ioplug_t *io, return pw->error; *revents = pfds[0].revents & ~(POLLIN | POLLOUT); - if (pfds[0].revents & POLLIN && !pcm_poll_block_check(io)) + if (pfds[0].revents & POLLIN && check_active(io)) *revents |= (io->stream == SND_PCM_STREAM_PLAYBACK) ? POLLOUT : POLLIN; return 0; @@ -213,29 +237,40 @@ static snd_pcm_sframes_t snd_pcm_pipewire_pointer(snd_pcm_ioplug_t *io) static int snd_pcm_pipewire_delay(snd_pcm_ioplug_t *io, snd_pcm_sframes_t *delayp) { snd_pcm_pipewire_t *pw = io->private_data; - int64_t elapsed = 0, filled, avail; + uintptr_t seq1, seq2; + int64_t elapsed = 0, delay, now, avail; + struct timespec ts; + int64_t diff; + + do { + seq1 = SEQ_READ(pw->seq); + + delay = pw->delay; + now = pw->now; + if (io->stream == SND_PCM_STREAM_PLAYBACK) + avail = snd_pcm_ioplug_hw_avail(io, pw->hw_ptr, io->appl_ptr); + else + avail = snd_pcm_ioplug_avail(io, pw->hw_ptr, io->appl_ptr); + + seq2 = SEQ_READ(pw->seq); + } while (!SEQ_READ_SUCCESS(seq1, seq2)); - if (pw->time.rate.num != 0) { - struct timespec ts; - int64_t diff; + if (now != 0 && (io->state == SND_PCM_STATE_RUNNING || + io->state == SND_PCM_STATE_DRAINING)) { clock_gettime(CLOCK_MONOTONIC, &ts); - diff = SPA_TIMESPEC_TO_NSEC(&ts) - pw->time.now; - elapsed = (pw->time.rate.denom * diff) / (pw->time.rate.num * SPA_NSEC_PER_SEC); - } - if (io->stream == SND_PCM_STREAM_PLAYBACK) - avail = snd_pcm_ioplug_hw_avail(io, pw->hw_ptr, io->appl_ptr); - else - avail = snd_pcm_ioplug_avail(io, pw->hw_ptr, io->appl_ptr); + diff = SPA_TIMESPEC_TO_NSEC(&ts) - now; + elapsed = (io->rate * diff) / SPA_NSEC_PER_SEC; - filled = pw->time.delay + avail; + if (io->stream == SND_PCM_STREAM_PLAYBACK) + delay -= SPA_MIN(elapsed, delay); + else + delay += SPA_MIN(elapsed, (int64_t)io->buffer_size); + } - if (io->stream == SND_PCM_STREAM_PLAYBACK) - *delayp = filled - SPA_MIN(elapsed, filled); - else - *delayp = filled + elapsed; + *delayp = delay + avail; - pw_log_trace("avail:%"PRIi64" filled %"PRIi64" elapsed:%"PRIi64" delay:%ld %lu %lu", - avail, filled, elapsed, *delayp, pw->hw_ptr, io->appl_ptr); + pw_log_trace("avail:%"PRIi64" filled %"PRIi64" elapsed:%"PRIi64" delay:%ld hw:%lu appl:%lu", + avail, delay, elapsed, *delayp, pw->hw_ptr, io->appl_ptr); return 0; } @@ -388,21 +423,20 @@ static void on_stream_process(void *data) snd_pcm_ioplug_t *io = &pw->io; struct pw_buffer *b; snd_pcm_uframes_t hw_avail, before, want, xfer; + struct pw_time pwt; + int64_t delay; - pw_stream_get_time_n(pw->stream, &pw->time, sizeof(pw->time)); + pw_stream_get_time_n(pw->stream, &pwt, sizeof(pwt)); - if (pw->time.rate.num != 0) { - pw->time.delay = pw->time.delay * io->rate * pw->time.rate.num / pw->time.rate.denom; - pw->time.rate.denom = io->rate; - pw->time.rate.num = 1; + delay = pwt.delay; + if (pwt.rate.num != 0) { + delay = delay * io->rate * pwt.rate.num / pwt.rate.denom; } before = hw_avail = snd_pcm_ioplug_hw_avail(io, pw->hw_ptr, io->appl_ptr); - if (pw->drained) { - pcm_poll_unblock_check(io); /* unblock socket for polling if needed */ - return; - } + if (pw->drained) + goto done; b = pw_stream_dequeue_buffer(pw->stream); if (b == NULL) @@ -410,15 +444,20 @@ static void on_stream_process(void *data) want = b->requested ? b->requested : hw_avail; - xfer = snd_pcm_pipewire_process(pw, b, &hw_avail, want); + SEQ_WRITE(pw->seq); - pw_log_trace("%p: avail-before:%lu avail:%lu want:%lu xfer:%lu", - pw, before, hw_avail, want, xfer); + xfer = snd_pcm_pipewire_process(pw, b, &hw_avail, want); + pw->delay = delay; + /* the buffer is now queued in the stream and consumed */ if (io->stream == SND_PCM_STREAM_PLAYBACK) - pw->time.delay += xfer; - else - pw->time.delay -= SPA_MIN(pw->time.delay, (int64_t)xfer); + pw->delay += xfer; + + pw->now = pwt.now; + SEQ_WRITE(pw->seq); + + pw_log_trace("%p: avail-before:%lu avail:%lu want:%lu xfer:%lu hw:%lu appl:%lu", + pw, before, hw_avail, want, xfer, pw->hw_ptr, io->appl_ptr); pw_stream_queue_buffer(pw->stream, b); @@ -431,7 +470,8 @@ static void on_stream_process(void *data) pw->drained = false; } } - pcm_poll_unblock_check(io); /* unblock socket for polling if needed */ +done: + update_active(io); } static const struct pw_stream_events stream_events = { @@ -484,6 +524,8 @@ static int snd_pcm_pipewire_prepare(snd_pcm_ioplug_t *io) if (snd_pcm_sw_params_current(io->pcm, swparams) == 0) { snd_pcm_sw_params_get_avail_min(swparams, &pw->min_avail); snd_pcm_sw_params_get_boundary(swparams, &pw->boundary); + snd_pcm_sw_params_dump(swparams, pw->output); + fflush(pw->log_file); } else { pw->min_avail = io->period_size; pw->boundary = io->buffer_size; @@ -492,8 +534,10 @@ static int snd_pcm_pipewire_prepare(snd_pcm_ioplug_t *io) min_period = (MIN_PERIOD * io->rate / 48000); pw->min_avail = SPA_MAX(pw->min_avail, min_period); - pw_log_debug("%p: prepare %d %p %lu %ld", pw, - pw->error, pw->stream, io->period_size, pw->min_avail); + pw_log_debug("%p: prepare error:%d stream:%p buffer-size:%lu " + "period-size:%lu min-avail:%ld", pw, pw->error, + pw->stream, io->buffer_size, io->period_size, pw->min_avail); + if (pw->error >= 0 && pw->stream != NULL && !pw->hw_params_changed) goto done; pw->hw_params_changed = false; @@ -550,6 +594,7 @@ static int snd_pcm_pipewire_prepare(snd_pcm_ioplug_t *io) done: pw->hw_ptr = 0; + pw->now = 0; pw->xrun_detected = false; pw->drained = false; pw->draining = false; @@ -568,9 +613,8 @@ static int snd_pcm_pipewire_start(snd_pcm_ioplug_t *io) snd_pcm_pipewire_t *pw = io->private_data; pw_thread_loop_lock(pw->main_loop); - pw_log_debug("%p:", pw); + pw_log_debug("%p: start", pw); pipewire_start(pw); - block_check(io); /* unblock socket for polling if needed */ pw_thread_loop_unlock(pw->main_loop); return 0; } @@ -579,8 +623,8 @@ static int snd_pcm_pipewire_stop(snd_pcm_ioplug_t *io) { snd_pcm_pipewire_t *pw = io->private_data; - pw_log_debug("%p:", pw); - pcm_poll_unblock_check(io); + pw_log_debug("%p: stop", pw); + update_active(io); pw_thread_loop_lock(pw->main_loop); if (pw->activated && pw->stream != NULL) { @@ -593,7 +637,7 @@ static int snd_pcm_pipewire_stop(snd_pcm_ioplug_t *io) static int snd_pcm_pipewire_pause(snd_pcm_ioplug_t * io, int enable) { - pw_log_debug("%p:", io); + pw_log_debug("%p: pause", io); if (enable) snd_pcm_pipewire_stop(io); @@ -646,6 +690,9 @@ static int snd_pcm_pipewire_hw_params(snd_pcm_ioplug_t * io, snd_pcm_pipewire_t *pw = io->private_data; bool planar; + snd_pcm_hw_params_dump(params, pw->output); + fflush(pw->log_file); + pw_log_debug("%p: hw_params buffer_size:%lu period_size:%lu", pw, io->buffer_size, io->period_size); switch(io->access) { @@ -822,7 +869,10 @@ static int snd_pcm_pipewire_set_chmap(snd_pcm_ioplug_t * io, pw->format.channels = map->channels; for (i = 0; i < map->channels; i++) { pw->format.position[i] = chmap_to_channel(map->pos[i]); - pw_log_debug("map %d: %d %d", i, map->pos[i], pw->format.position[i]); + pw_log_debug("map %d: %s / %s", i, + snd_pcm_chmap_name(map->pos[i]), + spa_debug_type_find_short_name(spa_type_audio_channel, + pw->format.position[i])); } return 1; } @@ -996,7 +1046,7 @@ static void on_core_error(void *data, uint32_t id, int seq, int res, const char if (id == PW_ID_CORE) { pw->error = res; if (pw->fd != -1) - pcm_poll_unblock_check(&pw->io); + update_active(&pw->io); } pw_thread_loop_signal(pw->main_loop, false); } @@ -1006,6 +1056,25 @@ static const struct pw_core_events core_events = { .error = on_core_error, }; + +static ssize_t log_write(void *cookie, const char *buf, size_t size) +{ + int len; + + while (size > 0) { + len = strcspn(buf, "\n"); + if (len > 0) + pw_log_debug("%.*s", (int)len, buf); + buf += len + 1; + size -= len + 1; + } + return size; +} + +static cookie_io_functions_t io_funcs = { + .write = log_write, +}; + static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, const char *name, const char *node_name, const char *server_name, @@ -1037,14 +1106,23 @@ static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, const char *name, str = getenv("PIPEWIRE_NODE"); - pw_log_debug("%p: open %s %d %d %08x %d %s %d %d '%s'", pw, name, - stream, mode, flags, rate, - format != SND_PCM_FORMAT_UNKNOWN ? snd_pcm_format_name(format) : "none", - channels, period_bytes, str); + pw_log_debug("%p: open name:%s stream:%s mode:%d flags:%08x rate:%d format:%s " + "channels:%d period-bytes:%d target:'%s'", pw, name, + snd_pcm_stream_name(stream), mode, flags, rate, + snd_pcm_format_name(format), channels, period_bytes, str); pw->fd = -1; pw->io.poll_fd = -1; pw->flags = flags; + pw->log_file = fopencookie(pw, "w", io_funcs); + if (pw->log_file == NULL) { + pw_log_error("can't create log file: %m"); + return -errno; + } + if ((err = snd_output_stdio_attach(&pw->output, pw->log_file, 0)) < 0) { + pw_log_error("can't attach log file: %s", snd_strerror(err)); + return err; + } if (node_name == NULL) pw->node_name = spa_aprintf("ALSA %s", @@ -1126,17 +1204,20 @@ static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, const char *name, if ((err = snd_pcm_ioplug_create(&pw->io, name, stream, mode)) < 0) goto error; - pw_log_debug("%p: open %s %d %d", pw, name, pw->io.stream, mode); if ((err = pipewire_set_hw_constraint(pw, rate, format, channels, period_bytes)) < 0) goto error; + pw_log_debug("%p: opened name:%s stream:%s mode:%d", pw, name, + snd_pcm_stream_name(pw->io.stream), mode); + *pcmp = pw->io.pcm; return 0; error: + pw_log_debug("%p: failed to open %s :%s", pw, name, spa_strerror(err)); pw_properties_free(props); snd_pcm_pipewire_free(pw); return err; diff --git a/spa/include/spa/interfaces/audio/aec.h b/spa/include/spa/interfaces/audio/aec.h index 601f7b61e211b6d5c4f6edce01b266c9b9d2cab1..9fb109df21b11a5b59373370017a20ca18edaf49 100644 --- a/spa/include/spa/interfaces/audio/aec.h +++ b/spa/include/spa/interfaces/audio/aec.h @@ -36,7 +36,7 @@ extern "C" { #define SPA_TYPE_INTERFACE_AUDIO_AEC SPA_TYPE_INFO_INTERFACE_BASE "Audio:AEC" -#define SPA_VERSION_AUDIO_AEC 0 +#define SPA_VERSION_AUDIO_AEC 1 struct spa_audio_aec { struct spa_interface iface; const char *name; @@ -60,7 +60,7 @@ struct spa_audio_aec_events { }; struct spa_audio_aec_methods { -#define SPA_VERSION_AUDIO_AEC_METHODS 0 +#define SPA_VERSION_AUDIO_AEC_METHODS 1 uint32_t version; int (*add_listener) (void *object, @@ -71,6 +71,10 @@ struct spa_audio_aec_methods { int (*init) (void *object, const struct spa_dict *args, const struct spa_audio_info_raw *info); int (*run) (void *object, const float *rec[], const float *play[], float *out[], uint32_t n_samples); int (*set_props) (void *object, const struct spa_dict *args); + /* since 0.3.58, version 1:1 */ + int (*activate) (void *object); + /* since 0.3.58, version 1:1 */ + int (*deactivate) (void *object); }; #define spa_audio_aec_method(o,method,version,...) \ @@ -87,6 +91,8 @@ struct spa_audio_aec_methods { #define spa_audio_aec_init(o,...) spa_audio_aec_method(o, init, 0, __VA_ARGS__) #define spa_audio_aec_run(o,...) spa_audio_aec_method(o, run, 0, __VA_ARGS__) #define spa_audio_aec_set_props(o,...) spa_audio_aec_method(o, set_props, 0, __VA_ARGS__) +#define spa_audio_aec_activate(o) spa_audio_aec_method(o, activate, 1) +#define spa_audio_aec_deactivate(o) spa_audio_aec_method(o, deactivate, 1) #ifdef __cplusplus } /* extern "C" */ diff --git a/spa/include/spa/support/log.h b/spa/include/spa/support/log.h index e4990d1bb69d64944e0463c1a8c9f9160266c883..1475eedf2416bb71f06b52a608503b7470eb6bad 100644 --- a/spa/include/spa/support/log.h +++ b/spa/include/spa/support/log.h @@ -293,6 +293,24 @@ do { \ #define spa_log_trace_fp(l,...) #endif +#define spa_log_hexdump(l,lev,indent,data,len) \ +({ \ + char str[512]; \ + uint8_t *buf = (uint8_t *)data; \ + size_t i; \ + int pos = 0; \ + \ + for (i = 0; i < len; i++) { \ + if (i % 16 == 0) \ + pos = 0; \ + pos += sprintf(str + pos, "%02x ", buf[i]); \ + if (i % 16 == 15 || i == len - 1) { \ + spa_log_log(l,lev,__FILE__,__LINE__,__func__, \ + "%*s" "%s",indent,"", str); \ + } \ + } \ +}) + /** \fn spa_log_error */ /** keys can be given when initializing the logger handle */ diff --git a/spa/include/spa/utils/keys.h b/spa/include/spa/utils/keys.h index 80d578fc0ee9a1fdb72c7be69ab18ec8a74c9c89..8a2003abf5d5112c76c2a19b1988845e2f62f5eb 100644 --- a/spa/include/spa/utils/keys.h +++ b/spa/include/spa/utils/keys.h @@ -62,6 +62,8 @@ extern "C" { #define SPA_KEY_API_ALSA_USE_UCM "api.alsa.use-ucm" /**< if UCM should be used */ #define SPA_KEY_API_ALSA_IGNORE_DB "api.alsa.ignore-dB" /**< if decibel info should be ignored */ #define SPA_KEY_API_ALSA_OPEN_UCM "api.alsa.open.ucm" /**< if UCM should be opened card */ +#define SPA_KEY_API_ALSA_DISABLE_LONGNAME \ + "api.alsa.disable-longname" /**< if card long name should not be passed to MIDI port */ /** info from alsa card_info */ #define SPA_KEY_API_ALSA_CARD_ID "api.alsa.card.id" /**< id from card_info */ diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index aea4b440d02e677be9b5d130dea54f1496290df5..f23232ed3ab9b249180dd4f09bae79ba4d88837a 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -1546,7 +1546,7 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props) } impl->ucm.default_sample_spec.format = PA_SAMPLE_S16NE; - impl->ucm.default_sample_spec.rate = 44100; + impl->ucm.default_sample_spec.rate = 48000; impl->ucm.default_sample_spec.channels = 2; pa_channel_map_init_extend(&impl->ucm.default_channel_map, impl->ucm.default_sample_spec.channels, PA_CHANNEL_MAP_ALSA); diff --git a/spa/plugins/alsa/alsa-pcm-sink.c b/spa/plugins/alsa/alsa-pcm-sink.c index a0b5b4e6862571828872d54c9456eaf85725a53d..73c421eec52d173bcd7e507749fba56ecdfbb08b 100644 --- a/spa/plugins/alsa/alsa-pcm-sink.c +++ b/spa/plugins/alsa/alsa-pcm-sink.c @@ -56,9 +56,9 @@ static void emit_node_info(struct state *this, bool full) if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { - struct spa_dict_item items[4]; + struct spa_dict_item items[7]; uint32_t i, n_items = 0; - char latency[64]; + char latency[64], period[64], nperiods[64], headroom[64]; items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "alsa"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Sink"); @@ -66,6 +66,12 @@ static void emit_node_info(struct state *this, bool full) if (this->have_format) { snprintf(latency, sizeof(latency), "%lu/%d", this->buffer_frames / 2, this->rate); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_MAX_LATENCY, latency); + snprintf(period, sizeof(period), "%lu", this->period_frames); + items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-size", period); + snprintf(nperiods, sizeof(nperiods), "%lu", this->buffer_frames / this->period_frames); + items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-num", nperiods); + snprintf(headroom, sizeof(headroom), "%u", this->headroom); + items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.headroom", headroom); } this->info.props = &SPA_DICT_INIT(items, n_items); diff --git a/spa/plugins/alsa/alsa-pcm-source.c b/spa/plugins/alsa/alsa-pcm-source.c index a1e9690cf138002c2647f914239c02d0e414309b..7f72d53ab62fbc5f1254757f6b7e6fd6024f9289 100644 --- a/spa/plugins/alsa/alsa-pcm-source.c +++ b/spa/plugins/alsa/alsa-pcm-source.c @@ -57,9 +57,9 @@ static void emit_node_info(struct state *this, bool full) if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { - struct spa_dict_item items[4]; + struct spa_dict_item items[7]; uint32_t i, n_items = 0; - char latency[64]; + char latency[64], period[64], nperiods[64], headroom[64]; items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "alsa"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Source"); @@ -67,6 +67,12 @@ static void emit_node_info(struct state *this, bool full) if (this->have_format) { snprintf(latency, sizeof(latency), "%lu/%d", this->buffer_frames / 2, this->rate); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_MAX_LATENCY, latency); + snprintf(period, sizeof(period), "%lu", this->period_frames); + items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-size", period); + snprintf(nperiods, sizeof(nperiods), "%lu", this->buffer_frames / this->period_frames); + items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-num", nperiods); + snprintf(headroom, sizeof(headroom), "%u", this->headroom); + items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.headroom", headroom); } this->info.props = &SPA_DICT_INIT(items, n_items); diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index c171e5f888a8203d4c1473e090dab0c095127803..6bcb7d851eb8fa2729507ec8c8953e26a7ffa6f1 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -782,7 +782,7 @@ static bool uint32_array_contains(uint32_t *vals, uint32_t n_vals, uint32_t val) return false; } -static int add_rate(struct state *state, uint32_t scale, bool all, uint32_t index, uint32_t *next, +static int add_rate(struct state *state, uint32_t scale, uint32_t interleave, bool all, uint32_t index, uint32_t *next, uint32_t min_allowed_rate, snd_pcm_hw_params_t *params, struct spa_pod_builder *b) { struct spa_pod_frame f[1]; @@ -794,11 +794,13 @@ static int add_rate(struct state *state, uint32_t scale, bool all, uint32_t inde CHECK(snd_pcm_hw_params_get_rate_min(params, &min, &dir), "get_rate_min"); CHECK(snd_pcm_hw_params_get_rate_max(params, &max, &dir), "get_rate_max"); - spa_log_debug(state->log, "min:%u max:%u min-allowed:%u scale:%u all:%d", - min, max, min_allowed_rate, scale, all); + spa_log_debug(state->log, "min:%u max:%u min-allowed:%u scale:%u interleave:%u all:%d", + min, max, min_allowed_rate, scale, interleave, all); - min_allowed_rate /= scale; - min = SPA_MAX(min_allowed_rate, min); + min = SPA_MAX(min_allowed_rate * scale / interleave, min) * interleave / scale; + max = max * interleave / scale; + if (max < min) + return 0; if (!state->multi_rate && state->card->format_ref > 0) rate = state->card->rate; @@ -1068,7 +1070,7 @@ static int enum_pcm_formats(struct state *state, uint32_t index, uint32_t *next, choice->body.type = SPA_CHOICE_Enum; spa_pod_builder_pop(b, &f[1]); - if ((res = add_rate(state, 1, false, index & 0xffff, next, 0, params, b)) != 1) + if ((res = add_rate(state, 1, 1, false, index & 0xffff, next, 0, params, b)) != 1) return res; if ((res = add_channels(state, false, index & 0xffff, next, params, b)) != 1) @@ -1163,7 +1165,7 @@ static int enum_iec958_formats(struct state *state, uint32_t index, uint32_t *ne } spa_pod_builder_pop(b, &f[1]); - if ((res = add_rate(state, 1, true, index & 0xffff, next, 0, params, b)) != 1) + if ((res = add_rate(state, 1, 1, true, index & 0xffff, next, 0, params, b)) != 1) return res; (*next)++; @@ -1227,7 +1229,8 @@ static int enum_dsd_formats(struct state *state, uint32_t index, uint32_t *next, * 176400. This would correspond to "DSD32" (which does not exist). Trying * to use such a rate with DSD hardware does not work and may cause undefined * behavior in said hardware. */ - if ((res = add_rate(state, SPA_ABS(interleave), true, index & 0xffff, next, 44100 * 64 / 8, params, b)) != 1) + if ((res = add_rate(state, 8, SPA_ABS(interleave), true, index & 0xffff, + next, 44100, params, b)) != 1) return res; if ((res = add_channels(state, true, index & 0xffff, next, params, b)) != 1) diff --git a/spa/plugins/alsa/alsa-seq-bridge.c b/spa/plugins/alsa/alsa-seq-bridge.c index 57ce40434f3ed6989682939f0480416f5cb0187c..bf29301472d7c0639a35e0dcec91d2e468987c23 100644 --- a/spa/plugins/alsa/alsa-seq-bridge.c +++ b/spa/plugins/alsa/alsa-seq-bridge.c @@ -48,6 +48,7 @@ static void reset_props(struct props *props) { strncpy(props->device, DEFAULT_DEVICE, sizeof(props->device)); strncpy(props->clock_name, DEFAULT_CLOCK_NAME, sizeof(props->clock_name)); + props->disable_longname = 0; } static int impl_node_enum_params(void *object, int seq, @@ -249,7 +250,7 @@ static void emit_port_info(struct seq_state *this, struct seq_port *port, bool f snd_seq_port_info_t *info; snd_seq_client_info_t *client_info; char card[8]; - char name[128]; + char name[256]; char path[128]; char alias[128]; @@ -261,11 +262,34 @@ static void emit_port_info(struct seq_state *this, struct seq_port *port, bool f snd_seq_get_any_client_info(this->sys.hndl, port->addr.client, client_info); - snprintf(name, sizeof(name), "%s:(%s_%d) %s", - snd_seq_client_info_get_name(client_info), - port->direction == SPA_DIRECTION_OUTPUT ? "capture" : "playback", - port->addr.port, - snd_seq_port_info_get_name(info)); + int card_id; + + // Failed to obtain card number (software device) or disabled + if (this->props.disable_longname || (card_id = snd_seq_client_info_get_card(client_info)) < 0) { + snprintf(name, sizeof(name), "%s:(%s_%d) %s", + snd_seq_client_info_get_name(client_info), + port->direction == SPA_DIRECTION_OUTPUT ? "capture" : "playback", + port->addr.port, + snd_seq_port_info_get_name(info)); + } else { + char *longname; + if (snd_card_get_longname(card_id, &longname) == 0) { + snprintf(name, sizeof(name), "%s:(%s_%d) %s", + longname, + port->direction == SPA_DIRECTION_OUTPUT ? "capture" : "playback", + port->addr.port, + snd_seq_port_info_get_name(info)); + free(longname); + } else { + // At least add card number to be distinct + snprintf(name, sizeof(name), "%s %d:(%s_%d) %s", + snd_seq_client_info_get_name(client_info), + card_id, + port->direction == SPA_DIRECTION_OUTPUT ? "capture" : "playback", + port->addr.port, + snd_seq_port_info_get_name(info)); + } + } clean_name(name); snprintf(path, sizeof(path), "alsa:seq:%s:client_%d:%s_%d", @@ -927,6 +951,8 @@ impl_init(const struct spa_handle_factory *factory, } else if (spa_streq(k, "clock.name")) { spa_scnprintf(this->props.clock_name, sizeof(this->props.clock_name), "%s", s); + } else if (spa_streq(k, SPA_KEY_API_ALSA_DISABLE_LONGNAME)) { + this->props.disable_longname = spa_atob(s); } } diff --git a/spa/plugins/alsa/alsa-seq.h b/spa/plugins/alsa/alsa-seq.h index 91f763e241df52b8d12e39dedffb686cb3315d7b..5d5ed51378de2fe6ea95c9585a898b6341a833bd 100644 --- a/spa/plugins/alsa/alsa-seq.h +++ b/spa/plugins/alsa/alsa-seq.h @@ -52,6 +52,7 @@ extern "C" { struct props { char device[64]; char clock_name[64]; + bool disable_longname; }; #define MAX_EVENT_SIZE 1024 diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 7873efdb0436ef22488cbb6646b5401dd18bc921..bb6614d13d2615d5651eff04d20e69d233fa17c2 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -32,6 +32,7 @@ #include <spa/support/log.h> #include <spa/utils/result.h> #include <spa/utils/list.h> +#include <spa/utils/json.h> #include <spa/utils/names.h> #include <spa/utils/string.h> #include <spa/node/node.h> @@ -839,10 +840,6 @@ static int parse_prop_params(struct impl *this, struct spa_pod *params) spa_log_info(this->log, "key:'%s' val:'%s'", name, value); changed += audioconvert_set_param(this, name, value); } - if (changed) { - channelmix_init(&this->mix); - set_volume(this); - } return changed; } @@ -924,6 +921,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param) else if (have_channel_volume) p->have_soft_volume = false; + channelmix_init(&this->mix); set_volume(this); } return changed; @@ -1255,7 +1253,7 @@ static void set_volume(struct impl *this) float volumes[SPA_AUDIO_MAX_CHANNELS]; struct dir *dir = &this->dir[this->direction]; - spa_log_debug(this->log, "%p", this); + spa_log_debug(this->log, "%p have_format:%d", this, dir->have_format); if (dir->have_format) remap_volumes(this, &dir->format); @@ -1726,20 +1724,20 @@ impl_node_port_enum_params(void *object, int seq, case SPA_PARAM_Buffers: { uint32_t size; - struct dir *dir; if (!port->have_format) return -EIO; if (result.index > 0) return 0; - dir = &this->dir[direction]; - if (dir->mode == SPA_PARAM_PORT_CONFIG_MODE_dsp) { + if (PORT_IS_DSP(this, direction, port_id)) { /* DSP ports always use the quantum_limit as the buffer * size. */ size = this->quantum_limit; } else { uint32_t irate, orate; + struct dir *dir = &this->dir[direction]; + /* Convert ports are scaled so that they can always * provide one quantum of data */ irate = dir->format.info.raw.rate; @@ -2772,6 +2770,34 @@ impl_get_size(const struct spa_handle_factory *factory, return sizeof(struct impl); } +static uint32_t channel_from_name(const char *name) +{ + int i; + for (i = 0; spa_type_audio_channel[i].name; i++) { + if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_channel[i].name))) + return spa_type_audio_channel[i].type; + } + return SPA_AUDIO_CHANNEL_UNKNOWN; +} + +static inline uint32_t parse_position(uint32_t *pos, const char *val, size_t len) +{ + struct spa_json it[2]; + char v[256]; + uint32_t i = 0; + + spa_json_init(&it[0], val, len); + if (spa_json_enter_array(&it[0], &it[1]) <= 0) + spa_json_init(&it[1], val, len); + + while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && + i < SPA_AUDIO_MAX_CHANNELS) { + pos[i++] = channel_from_name(v); + } + return i; +} + + static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, @@ -2822,10 +2848,16 @@ impl_init(const struct spa_handle_factory *factory, else this->direction = SPA_DIRECTION_INPUT; } + else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) + this->props.n_channels = parse_position(this->props.channel_map, s, strlen(s)); else audioconvert_set_param(this, k, s); } + this->props.channel.n_volumes = this->props.n_channels; + this->props.soft.n_volumes = this->props.n_channels; + this->props.monitor.n_volumes = this->props.n_channels; + this->dir[SPA_DIRECTION_INPUT].latency = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); this->dir[SPA_DIRECTION_OUTPUT].latency = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); diff --git a/spa/plugins/audioconvert/channelmix-ops.c b/spa/plugins/audioconvert/channelmix-ops.c index 54e094fe679871efd4297b4158c0e3430bc1bbfb..56faa7ea6cadeda7748873e4ec770fcdf6ac33e9 100644 --- a/spa/plugins/audioconvert/channelmix-ops.c +++ b/spa/plugins/audioconvert/channelmix-ops.c @@ -31,21 +31,9 @@ #include <spa/support/log.h> #include <spa/utils/defs.h> -#define VOLUME_MIN 0.0f -#define VOLUME_NORM 1.0f - #include "channelmix-ops.h" #include "hilbert.h" - -#define _M(ch) (1UL << SPA_AUDIO_CHANNEL_ ## ch) -#define MASK_MONO _M(FC)|_M(MONO)|_M(UNKNOWN) -#define MASK_STEREO _M(FL)|_M(FR)|_M(UNKNOWN) -#define MASK_QUAD _M(FL)|_M(FR)|_M(RL)|_M(RR)|_M(UNKNOWN) -#define MASK_3_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE) -#define MASK_5_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE)|_M(SL)|_M(SR)|_M(RL)|_M(RR) -#define MASK_7_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE)|_M(SL)|_M(SR)|_M(RL)|_M(RR) - #define ANY ((uint32_t)-1) #define EQ ((uint32_t)-2) @@ -199,8 +187,11 @@ static int make_matrix(struct channelmix *mix) for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) matrix[i][i]= 1.0f; } + if (dst_mask & FRONT) + filter_fc = true; + if (dst_mask & _MASK(LFE)) + filter_lfe = true; src_mask = dst_mask = ~0LU; - filter_fc = filter_lfe = true; goto done; } else { spa_log_debug(mix->log, "matching channels"); diff --git a/spa/plugins/audioconvert/meson.build b/spa/plugins/audioconvert/meson.build index d84699242dcff1f665c3c0423c27871f34c16cca..ab0581e765ec5e44556f712b81f00196fe19f392 100644 --- a/spa/plugins/audioconvert/meson.build +++ b/spa/plugins/audioconvert/meson.build @@ -27,7 +27,7 @@ if have_sse 'resample-peaks-sse.c', 'volume-ops-sse.c', 'channelmix-ops-sse.c' ], - c_args : [sse_args, '-O3', '-DHAVE_SSE'], + c_args : [sse_args, '-Ofast', '-DHAVE_SSE'], dependencies : [ spa_dep ], install : false ) diff --git a/spa/plugins/audioconvert/resample-native-avx.c b/spa/plugins/audioconvert/resample-native-avx.c index b23c0b729dcb4e63c94046149d2863f87ce24405..136d6cb2df1cf966219fbda4808f62be03fc7c75 100644 --- a/spa/plugins/audioconvert/resample-native-avx.c +++ b/spa/plugins/audioconvert/resample-native-avx.c @@ -27,7 +27,7 @@ #include <assert.h> #include <immintrin.h> -static void inner_product_avx(float *d, const float * SPA_RESTRICT s, +static inline void inner_product_avx(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT taps, uint32_t n_taps) { __m256 sy[2] = { _mm256_setzero_ps(), _mm256_setzero_ps() }, ty; @@ -56,7 +56,7 @@ static void inner_product_avx(float *d, const float * SPA_RESTRICT s, _mm_store_ss(d, sx[0]); } -static void inner_product_ip_avx(float *d, const float * SPA_RESTRICT s, +static inline void inner_product_ip_avx(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, uint32_t n_taps) { diff --git a/spa/plugins/audioconvert/resample-native-c.c b/spa/plugins/audioconvert/resample-native-c.c index 3fe50b769d1e6a4b4adad7a38a5586e5d1522691..ce6c57d923ade391af554fa21fef0b2a36ad3ec9 100644 --- a/spa/plugins/audioconvert/resample-native-c.c +++ b/spa/plugins/audioconvert/resample-native-c.c @@ -24,7 +24,7 @@ #include "resample-native-impl.h" -static void inner_product_c(float *d, const float * SPA_RESTRICT s, +static inline void inner_product_c(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT taps, uint32_t n_taps) { float sum = 0.0f; @@ -40,7 +40,7 @@ static void inner_product_c(float *d, const float * SPA_RESTRICT s, *d = sum; } -static void inner_product_ip_c(float *d, const float * SPA_RESTRICT s, +static inline void inner_product_ip_c(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, uint32_t n_taps) { diff --git a/spa/plugins/audioconvert/resample-native-neon.c b/spa/plugins/audioconvert/resample-native-neon.c index afe68914ae6f882a9b825f4a5b4e8252b0a4950b..079152afdc0c478f1f054073e20723a37e7afe22 100644 --- a/spa/plugins/audioconvert/resample-native-neon.c +++ b/spa/plugins/audioconvert/resample-native-neon.c @@ -26,7 +26,7 @@ #include <arm_neon.h> -static void inner_product_neon(float *d, const float * SPA_RESTRICT s, +static inline void inner_product_neon(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT taps, uint32_t n_taps) { unsigned int remainder = n_taps % 16; @@ -137,7 +137,7 @@ static void inner_product_neon(float *d, const float * SPA_RESTRICT s, #endif } -static void inner_product_ip_neon(float *d, const float * SPA_RESTRICT s, +static inline void inner_product_ip_neon(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, uint32_t n_taps) { diff --git a/spa/plugins/audioconvert/resample-native-sse.c b/spa/plugins/audioconvert/resample-native-sse.c index d0ebe39ecabd02fb905b42249262cb576fb46e4f..fcdb32c0866dfa9aab0cb04afc6d102c31d200ce 100644 --- a/spa/plugins/audioconvert/resample-native-sse.c +++ b/spa/plugins/audioconvert/resample-native-sse.c @@ -26,7 +26,7 @@ #include <xmmintrin.h> -static void inner_product_sse(float *d, const float * SPA_RESTRICT s, +static inline void inner_product_sse(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT taps, uint32_t n_taps) { __m128 sum = _mm_setzero_ps(); @@ -68,7 +68,7 @@ static void inner_product_sse(float *d, const float * SPA_RESTRICT s, _mm_store_ss(d, sum); } -static void inner_product_ip_sse(float *d, const float * SPA_RESTRICT s, +static inline void inner_product_ip_sse(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, uint32_t n_taps) { diff --git a/spa/plugins/audioconvert/resample-native-ssse3.c b/spa/plugins/audioconvert/resample-native-ssse3.c index c39bc610aa8df5d0bef2e0f5fb828dd17f14a36c..ac3675f0396af77c21573f2c7d68edfc3374b046 100644 --- a/spa/plugins/audioconvert/resample-native-ssse3.c +++ b/spa/plugins/audioconvert/resample-native-ssse3.c @@ -26,7 +26,7 @@ #include <tmmintrin.h> -static void inner_product_ssse3(float *d, const float * SPA_RESTRICT s, +static inline void inner_product_ssse3(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT taps, uint32_t n_taps) { __m128 sum = _mm_setzero_ps(); @@ -97,7 +97,7 @@ static void inner_product_ssse3(float *d, const float * SPA_RESTRICT s, _mm_store_ss(d, sum); } -static void inner_product_ip_ssse3(float *d, const float * SPA_RESTRICT s, +static inline void inner_product_ip_ssse3(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, uint32_t n_taps) { diff --git a/spa/plugins/audioconvert/resample-peaks-c.c b/spa/plugins/audioconvert/resample-peaks-c.c index 3d27016e573e84978131751c869fd83680edcbbf..161e06fe00eadb9d7cc214d8dcf20a55584b41af 100644 --- a/spa/plugins/audioconvert/resample-peaks-c.c +++ b/spa/plugins/audioconvert/resample-peaks-c.c @@ -26,48 +26,12 @@ #include "resample-peaks-impl.h" -void resample_peaks_process_c(struct resample *r, - const void * SPA_RESTRICT src[], uint32_t *in_len, - void * SPA_RESTRICT dst[], uint32_t *out_len) +static inline float find_abs_max_c(const float *s, uint32_t n_samples, float m) { - struct peaks_data *pd = r->data; - uint32_t c, i, o, end, chunk, o_count, i_count; - - if (SPA_UNLIKELY(r->channels == 0)) - return; - - for (c = 0; c < r->channels; c++) { - const float *s = src[c]; - float *d = dst[c], m = pd->max_f[c]; - - o_count = pd->o_count; - i_count = pd->i_count; - o = i = 0; - - while (i < *in_len && o < *out_len) { - end = ((uint64_t) (o_count + 1) * r->i_rate) / r->o_rate; - end = end > i_count ? end - i_count : 0; - chunk = SPA_MIN(end, *in_len); - - for (; i < chunk; i++) - m = SPA_MAX(fabsf(s[i]), m); - - if (i == end) { - d[o++] = m; - m = 0.0f; - o_count++; - } - } - pd->max_f[c] = m; - } - - *out_len = o; - *in_len = i; - pd->o_count = o_count; - pd->i_count = i_count + i; - - while (pd->i_count >= r->i_rate) { - pd->i_count -= r->i_rate; - pd->o_count -= r->o_rate; - } + uint32_t n; + for (n = 0; n < n_samples; n++) + m = fmaxf(fabsf(s[n]), m); + return m; } + +MAKE_PEAKS(c); diff --git a/spa/plugins/audioconvert/resample-peaks-impl.h b/spa/plugins/audioconvert/resample-peaks-impl.h index 7a39af078a4ed6d433ae8d1114d53a3f53ccc47e..9d9d55cff12023bb3fedbcdab4a5d718f7e749c9 100644 --- a/spa/plugins/audioconvert/resample-peaks-impl.h +++ b/spa/plugins/audioconvert/resample-peaks-impl.h @@ -34,11 +34,59 @@ struct peaks_data { float max_f[]; }; -void resample_peaks_process_c(struct resample *r, - const void * SPA_RESTRICT src[], uint32_t *in_len, - void * SPA_RESTRICT dst[], uint32_t *out_len); +#define DEFINE_PEAKS(arch) \ +void resample_peaks_process_##arch(struct resample *r, \ + const void * SPA_RESTRICT src[], uint32_t *in_len, \ + void * SPA_RESTRICT dst[], uint32_t *out_len) + +#define MAKE_PEAKS(arch) \ +DEFINE_PEAKS(arch) \ +{ \ + struct peaks_data *pd = r->data; \ + uint32_t c, i, o, end, chunk, i_count, o_count; \ + \ + if (SPA_UNLIKELY(r->channels == 0)) \ + return; \ + \ + for (c = 0; c < r->channels; c++) { \ + const float *s = src[c]; \ + float *d = dst[c], m = pd->max_f[c]; \ + \ + o_count = pd->o_count; \ + i_count = pd->i_count; \ + o = i = 0; \ + \ + while (i < *in_len && o < *out_len) { \ + end = ((uint64_t) (o_count + 1) \ + * r->i_rate) / r->o_rate; \ + end = end > i_count ? end - i_count : 0; \ + chunk = SPA_MIN(end, *in_len); \ + \ + m = find_abs_max_##arch(&s[i], chunk - i, m); \ + \ + i += chunk; \ + \ + if (i == end) { \ + d[o++] = m; \ + m = 0.0f; \ + o_count++; \ + } \ + } \ + pd->max_f[c] = m; \ + } \ + *out_len = o; \ + *in_len = i; \ + pd->o_count = o_count; \ + pd->i_count = i_count + i; \ + \ + while (pd->i_count >= r->i_rate) { \ + pd->i_count -= r->i_rate; \ + pd->o_count -= r->o_rate; \ + } \ +} + + +DEFINE_PEAKS(c); #if defined (HAVE_SSE) -void resample_peaks_process_sse(struct resample *r, - const void * SPA_RESTRICT src[], uint32_t *in_len, - void * SPA_RESTRICT dst[], uint32_t *out_len); +DEFINE_PEAKS(sse); #endif diff --git a/spa/plugins/audioconvert/resample-peaks-sse.c b/spa/plugins/audioconvert/resample-peaks-sse.c index 13886efdb6772059de34760f32e96e68e3c6b988..26adb8b91a4b2035d20ebec13dfed70eac754dc2 100644 --- a/spa/plugins/audioconvert/resample-peaks-sse.c +++ b/spa/plugins/audioconvert/resample-peaks-sse.c @@ -32,65 +32,33 @@ static inline float hmax_ps(__m128 val) { __m128 t = _mm_movehl_ps(val, val); t = _mm_max_ps(t, val); - val = _mm_shuffle_ps(val, t, 0x55); + val = _mm_shuffle_ps(t, t, 0x55); val = _mm_max_ss(t, val); return _mm_cvtss_f32(val); } -void resample_peaks_process_sse(struct resample *r, - const void * SPA_RESTRICT src[], uint32_t *in_len, - void * SPA_RESTRICT dst[], uint32_t *out_len) +static inline float find_abs_max_sse(const float *s, uint32_t n_samples, float m) { - struct peaks_data *pd = r->data; - uint32_t c, i, o, end, chunk, unrolled, i_count, o_count; - __m128 in, max, mask = _mm_andnot_ps(_mm_set_ps1(-0.0f), - _mm_cmpeq_ps(_mm_setzero_ps(), _mm_setzero_ps())); + __m128 in[2], max; + uint32_t n, unrolled; + const __m128 mask = _mm_set1_ps(-0.0f); - if (r->channels == 0) - return; + max = _mm_set1_ps(m); - for (c = 0; c < r->channels; c++) { - const float *s = src[c]; - float *d = dst[c], m = pd->max_f[c]; + unrolled = n_samples & ~7; - o_count = pd->o_count; - i_count = pd->i_count; - o = i = 0; - - max = _mm_set1_ps(m); - - while (i < *in_len && o < *out_len) { - end = ((uint64_t) (o_count + 1) * r->i_rate) / r->o_rate; - end = end > i_count ? end - i_count : 0; - chunk = SPA_MIN(end, *in_len); - - unrolled = chunk - ((chunk - i) & 3); - - for (; i < unrolled; i+=4) { - in = _mm_loadu_ps(&s[i]); - in = _mm_and_ps(mask, in); - max = _mm_max_ps(in, max); - } - for (; i < chunk; i++) - m = SPA_MAX(fabsf(s[i]), m); - - if (i == end) { - d[o++] = SPA_MAX(hmax_ps(max), m); - m = 0.0f; - max = _mm_set1_ps(m); - o_count++; - } - } - pd->max_f[c] = SPA_MAX(hmax_ps(max), m); + for (n = 0; n < unrolled; n += 8) { + in[0] = _mm_loadu_ps(&s[n + 0]); + in[1] = _mm_loadu_ps(&s[n + 4]); + in[0] = _mm_andnot_ps(mask, in[0]); + in[1] = _mm_andnot_ps(mask, in[1]); + max = _mm_max_ps(max, in[0]); + max = _mm_max_ps(max, in[1]); } + for (; n < n_samples; n++) + m = fmaxf(fabsf(s[n]), m); - *out_len = o; - *in_len = i; - pd->o_count = o_count; - pd->i_count = i_count + i; - - while (pd->i_count >= r->i_rate) { - pd->i_count -= r->i_rate; - pd->o_count -= r->o_rate; - } + return fmaxf(hmax_ps(max), m); } + +MAKE_PEAKS(sse); diff --git a/spa/plugins/audiomixer/audiomixer.c b/spa/plugins/audiomixer/audiomixer.c index 5e682bba5d8c666fc46beacda2eb896a67f6904f..6aba1a0120361f7713f5d439bcba5b9f9a5735e0 100644 --- a/spa/plugins/audiomixer/audiomixer.c +++ b/spa/plugins/audiomixer/audiomixer.c @@ -45,6 +45,9 @@ #define SPA_LOG_TOPIC_DEFAULT log_topic static struct spa_log_topic *log_topic = &SPA_LOG_TOPIC(0, "spa.audiomixer"); +#define DEFAULT_RATE 48000 +#define DEFAULT_CHANNELS 2 + #define MAX_BUFFERS 64 #define MAX_PORTS 128 #define MAX_CHANNELS 64 @@ -353,8 +356,10 @@ static int port_enum_formats(void *object, SPA_AUDIO_FORMAT_U24_32, SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_F64), - SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(44100, 1, INT32_MAX), - SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(2, 1, INT32_MAX)); + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int( + DEFAULT_RATE, 1, INT32_MAX), + SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int( + DEFAULT_CHANNELS, 1, INT32_MAX)); } break; default: diff --git a/spa/plugins/audiotestsrc/audiotestsrc.c b/spa/plugins/audiotestsrc/audiotestsrc.c index d02b735803a1d7cb0fb2d9c1d9b87be1cd79135a..ff58e6a2aa7225617349eb79ef65e026f1a08e9f 100644 --- a/spa/plugins/audiotestsrc/audiotestsrc.c +++ b/spa/plugins/audiotestsrc/audiotestsrc.c @@ -55,6 +55,9 @@ enum wave_type { WAVE_SQUARE, }; +#define DEFAULT_RATE 48000 +#define DEFAULT_CHANNELS 2 + #define DEFAULT_LIVE true #define DEFAULT_WAVE WAVE_SINE #define DEFAULT_FREQ 440.0 @@ -583,8 +586,10 @@ port_enum_formats(struct impl *this, SPA_AUDIO_FORMAT_S32, SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_F64), - SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(44100, 1, INT32_MAX), - SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(2, 1, INT32_MAX)); + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int( + DEFAULT_RATE, 1, INT32_MAX), + SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int( + DEFAULT_CHANNELS, 1, INT32_MAX)); break; default: return 0; diff --git a/spa/plugins/bluez5/README-OPUS-A2DP.md b/spa/plugins/bluez5/README-OPUS-A2DP.md index e94623f7516f99caa5554c86495accca8a7168d7..a7aefc1c615729121d4369473e5dcbb08beba2a5 100644 --- a/spa/plugins/bluez5/README-OPUS-A2DP.md +++ b/spa/plugins/bluez5/README-OPUS-A2DP.md @@ -6,31 +6,18 @@ date: Jun 4, 2022 # OPUS-A2DP-0.5 specification -DRAFT +In this file, a way to use Opus as an A2DP vendor codec is specified. -In this file, we specify how to use Opus as an A2DP vendor codec. We -will call this "OPUS-A2DP-0.5". There is no previous public +We will call this "OPUS-A2DP-0.5". There is no previous public specification for using Opus as an A2DP vendor codec (to my knowledge), which is why we need this one. [[_TOC_]] -# A2DP Codec Capabilities +# Media Codec Capabilities -The A2DP capability structure is as follows. - -Integer fields and multi-byte bitfields are laid out in **little -endian** order. All integer fields are unsigned. - -Each entry may have different meaning when present as a capability. -Below, we indicate this by abbreviations CAP/SNK for sink capability, -CAP/SRC for source capability, CAP for capability as either, and SEL -for the selected value by SRC. - -Bits in fields marked RFA (Reserved For Additions) shall be set to -zero. - -The capability and configuration structure is as follows: +The Media Codec Specific Information Elements ([AVDTP v1.3], §8.21.5) +capability and configuration structure is as follows: | Octet | Bits | Meaning | |-------|------|-----------------------------------------------| @@ -42,7 +29,19 @@ The capability and configuration structure is as follows: | 17-20 | 0-7 | Return Direction Audio Location Configuration | | 21-23 | 0-7 | Return Direction Limits Configuration | -See `a2dp-codec-caps.h` for definition as C structs. +All integer fields and multi-byte bitfields are laid out in **little +endian** order. All integer fields are unsigned. + +Each entry may have different meaning when present as a capability. +Below, we indicate this by abbreviations CAP for capability and SEL +for the value selected by SRC. + +Bits in fields marked RFA (Reserved For Additions) shall be set to +zero. + +> **Note** +> +> See `a2dp-codec-caps.h` for definition as C structs. ## Vendor ID Part @@ -53,13 +52,16 @@ The fixed value | 0-3 | 0-7 | A2DP Vendor ID (0x05F1) | | 4-5 | 0-7 | A2DP Vendor Codec ID (0x1005) | -The Vendor ID is that of the Linux Foundation, and we are using it -here unofficially. +> **Note** +> +> The Vendor ID is that of the Linux Foundation, and we are using it +> here unofficially. ## Channel Configuration -The channel configuration consists of the channel count and a bitfield -indicating which of them are encoded in coupled streams. +The channel configuration consists of the channel count, and the count +of coupled streams. The latter indicates which channels are encoded as +left/right pairs, as defined in Sec. 5.1.1 of Opus Ogg Encapsulation [RFC7845]. | Octet | Bits | Meaning | |-------|------|------------------------------------------------------------| @@ -74,26 +76,23 @@ coupled (left & right) channel pair. The count shall satisfy `(Channel Count) >= 2*(Coupled Stream Count)`. The Stream Count is `(Channel Count) - (Coupled Stream Count)`. -Streams and Coupled Streams have the same meaning as in Sec. 5.1.1 of -Opus Multistream [RFC7845]. - The logical Channels are identified by a Channel Index *j* such that `0 <= j < (Channel Count)`. The channels `0 <= j < 2*(Coupled Stream Count)` are encoded in the *k*-th stream of the payload, where `k = floor(j/2)` and `j mod 2` determines which of the two channels of the stream the logical channel is. The channels `2*(Coupled Stream Count) <= j < (Channel Count)` are encoded in the *k*-th stream of the payload, where `k = j - (Coupled Stream Count)`. -The prescription here is identical to [RFC7845] with channel mapping -`mapping[j] = j`. -The semantic meaning for each channel is determined by their Audio -Location. +> **Note** +> +> The prescription here is identical to [RFC7845] with channel mapping +> `mapping[j] = j`. We do not want to include the mapping table in the +> A2DP capabilities, so it is assumed to be trivial. ## Audio Location Configuration -The channel audio location specification is similar to the location -bitfield of the `Audio_Channel_Allocation` LTV structure in Bluetooth -SIG [Assigned Numbers, Generic Audio] used in the LE Audio. +The semantic meaning for each channel is determined by their Audio +Location bitfield. | Octet | Bits | Meaning | |-------|------|------------------------------------------------------| @@ -103,11 +102,9 @@ The values specified in CAP are informative, and SEL may contain bits that were not set in CAP. SNK shall handle unsupported audio locations. It may do this for example by ignoring unsupported channels or via suitable up/downmixing. Hence, SRC may transmit channels with -audio locations that are not marked supported by SNK. The maximum -Channel Count however shall not be exceeded. +audio locations that are not marked supported by SNK. -The audio location bitfield values defined in [Assigned Numbers, -Generic Audio] are: +The audio location bit values are: | Channel Order | Bitmask | Audio Location | |---------------|------------|-------------------------| @@ -144,8 +141,8 @@ Generic Audio] are: | 30 | 0x40000000 | RFA | | 31 | 0x80000000 | RFA | -In addition, we define a specific Channel Order for each. The bits -set in the bitfield define audio locations for the streams present in the +Each bit value is associated with a Channel Order. The bits set in +the bitfield define audio locations for the streams present in the payload. The set bit with the smallest Channel Order value defines the audio location for the Channel Index *j=0*, the bit with the next lowest Channel Order value defines the audio location for the Channel @@ -154,46 +151,53 @@ Index *j=1*, and so forth. When the Channel Count is larger than the number of bits set in the Audio Location bitfield, the audio locations of the remaining channels are unspecified. Implementations may handle them as appropriate for -their use case, considering them as AUX0-AUXN, or in the case of +their use case, considering them as AUX0–AUXN, or in the case of Channel Count = 1, as the single mono audio channel. When the Channel Count is smaller than the number of bits set in the Audio Location bitfield, the audio locations for the channels are assigned as above, and remaining excess bits shall be ignored. -The channel ordering defined here is compatible with the internal -stream ordering in the reference Opus Multistream surround encoder -Mapping Family 0 and 1 output. This allows making use of its surround -masking and LFE handling capabilities. The stream ordering of the -reference Opus surround encoder, although being unchanged since its -addition in 2013, is an internal detail of the -encoder. Implementations using the surround encoder shall check that -the mapping table used by the encoder corresponds to the above channel -ordering. - -For reference, we list the Audio Location bitfield values -corresponding to the different channel counts in Opus Mapping Family 0 -and 1 surround encoder output, and the expected mapping table: - -| Mapping Family | Channel Count | Audio Location Value | Stream Ordering | Mapping Table | -|----------------|---------------|----------------------|---------------------------------|--------------------------| -| 0 | 1 | 0x00000000 | mono | {0} | -| 0 | 2 | 0x00000003 | FL, FR | {0, 1} | -| 1 | 1 | 0x00000000 | mono | {0} | -| 1 | 2 | 0x00000003 | FL, FR | {0, 1} | -| 1 | 3 | 0x00000007 | FL, FR, FC | {0, 2, 1} | -| 1 | 4 | 0x00000033 | FL, FR, BL, BR | {0, 1, 2, 3} | -| 1 | 5 | 0x00000037 | FL, FR, BL, BR, FC | {0, 4, 1, 2, 3} | -| 1 | 6 | 0x0000003f | FL, FR, BL, BR, FC, LFE | {0, 4, 1, 2, 3, 5} | -| 1 | 7 | 0x00000d0f | FL, FR, SL, SR, FC, BC, LFE | {0, 4, 1, 2, 3, 5, 6} | -| 1 | 8 | 0x00000c3f | FL, FR, SL, SR, BL, BR, FC, LFE | {0, 6, 1, 2, 3, 4, 5, 7} | - -The Mapping Table in the table indicates the mapping table selected by -`opus_multistream_surround_encoder_create` (Opus 1.3.1). If the -encoder outputs a different mapping table in a future Opus encoder -release, the channel ordering will be incorrect, and the surround -encoder can not be used. We expect that the probability of the Opus -encoder authors making such changes is negligible. +> **Note** +> +> The channel audio location specification is similar to the location +> bitfield of the `Audio_Channel_Allocation` LTV structure in Bluetooth +> SIG [Assigned Numbers, Generic Audio] used in the LE Audio, and the +> bitmasks defined above are the same. +> +> The channel ordering differs from LE Audio, and is defined here to be +> compatible with the internal stream ordering in the reference Opus +> Multistream surround encoder Mapping Family 0 and 1 output. This +> allows making use of its surround masking and LFE handling +> capabilities. The stream ordering of the reference Opus surround +> encoder, although being unchanged since its addition in 2013, is an +> internal detail of the encoder. Implementations using the surround +> encoder need to check that the mapping table used by the encoder +> corresponds to the above channel ordering. +> +> For reference, we list the Audio Location bitfield values +> corresponding to the different channel counts in Opus Mapping Family 0 +> and 1 surround encoder output, and the expected mapping table: +> +> | Mapping Family | Channel Count | Audio Location Value | Stream Ordering | Mapping Table | +> |----------------|---------------|----------------------|---------------------------------|--------------------------| +> | 0 | 1 | 0x00000000 | mono | {0} | +> | 0 | 2 | 0x00000003 | FL, FR | {0, 1} | +> | 1 | 1 | 0x00000000 | mono | {0} | +> | 1 | 2 | 0x00000003 | FL, FR | {0, 1} | +> | 1 | 3 | 0x00000007 | FL, FR, FC | {0, 2, 1} | +> | 1 | 4 | 0x00000033 | FL, FR, BL, BR | {0, 1, 2, 3} | +> | 1 | 5 | 0x00000037 | FL, FR, BL, BR, FC | {0, 4, 1, 2, 3} | +> | 1 | 6 | 0x0000003f | FL, FR, BL, BR, FC, LFE | {0, 4, 1, 2, 3, 5} | +> | 1 | 7 | 0x00000d0f | FL, FR, SL, SR, FC, BC, LFE | {0, 4, 1, 2, 3, 5, 6} | +> | 1 | 8 | 0x00000c3f | FL, FR, SL, SR, BL, BR, FC, LFE | {0, 6, 1, 2, 3, 4, 5, 7} | +> +> The Mapping Table in the table indicates the mapping table selected by +> `opus_multistream_surround_encoder_create` (Opus 1.3.1). If the +> encoder outputs a different mapping table in a future Opus encoder +> release, the channel ordering will be incorrect, and the surround +> encoder can not be used. We expect that the probability of the Opus +> encoder authors making such changes is negligible. ## Limits Configuration @@ -236,6 +240,12 @@ Namely: If no return channel is supported or selected, the number of channels is set to 0 in CAP or SEL. +> **Note** +> +> This is a nonstandard extension to A2DP. The return direction audio +> data is simply sent back via the underlying L2CAP connection, which +> is bidirectional, in the same format as the forward direction audio. +> This is similar to what aptX-LL and FastStream do. # Packet Structure @@ -255,8 +265,9 @@ may be fragmented to several consecutive Bluetooth packets. The format of the Multistream data is the same as in the audio packets of [RFC7845], or, as produced/consumed by the Opus Multistream API. -(Note that we DO NOT follow [RFC7587], as we want fragmentation and -multichannel support.) +> **Note** +> +> We DO NOT follow [RFC7587], as we want fragmentation and multichannel support. ## RTP Header @@ -311,10 +322,13 @@ correction instead of PLC. # References -1. IETF RFC 3550: [RFC3550] -2. IETF RFC 7587: [RFC7587] -3. IETF RFC 7845: [RFC7845] +1. Bluetooth [AVDTP v1.3] +2. IETF [RFC3550] +3. IETF [RFC7587] +4. IETF [RFC7845] +5. Bluetooth [Assigned Numbers, Generic Audio] +[AVDTP v1.3]: https://www.bluetooth.com/specifications/specs/a-v-distribution-transport-protocol-1-3/ [RFC3550]: https://datatracker.ietf.org/doc/html/rfc3550 [RFC7587]: https://datatracker.ietf.org/doc/html/rfc7587 [RFC7845]: https://datatracker.ietf.org/doc/html/rfc7845 diff --git a/spa/plugins/bluez5/a2dp-sink.c b/spa/plugins/bluez5/a2dp-sink.c index fac3b57a7c5071974f213dae06d6e6b2e89eb1b3..9d712d81551bd5a5ff7085eb9e1ed17a0e7547e4 100644 --- a/spa/plugins/bluez5/a2dp-sink.c +++ b/spa/plugins/bluez5/a2dp-sink.c @@ -885,7 +885,7 @@ static void a2dp_on_timeout(struct spa_source *source) static int do_start(struct impl *this) { - int i, res, val, size; + int res, val, size; struct port *port; socklen_t len; uint8_t *conf; @@ -907,8 +907,8 @@ static int do_start(struct impl *this) conf = this->transport->configuration; size = this->transport->configuration_len; - for (i = 0; i < size; i++) - spa_log_debug(this->log, " %d: %02x", i, conf[i]); + spa_log_debug(this->log, "Transport configuration:"); + spa_log_hexdump(this->log, SPA_LOG_LEVEL_DEBUG, 2, conf, (size_t)size); this->codec_data = this->codec->init(this->codec, this->is_duplex ? A2DP_CODEC_FLAG_SINK : 0, diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index f43d4b57d2ff59049b98b8256ed357fafb29223a..93f8e72171a31e4cafb3886e156035f65ed463e9 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -491,7 +491,7 @@ static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBu uint8_t *pconf = (uint8_t *) config; DBusMessage *r; DBusError err; - int i, size, res; + int size, res; const struct a2dp_codec *codec; bool sink; @@ -506,8 +506,7 @@ static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBu return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } spa_log_info(monitor->log, "%p: %s select conf %d", monitor, path, size); - for (i = 0; i < size; i++) - spa_log_debug(monitor->log, " %d: %02x", i, cap[i]); + spa_log_hexdump(monitor->log, SPA_LOG_LEVEL_DEBUG, 2, cap, (size_t)size); codec = a2dp_endpoint_to_codec(monitor, path, &sink); if (codec != NULL) @@ -529,8 +528,7 @@ static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBu return DBUS_HANDLER_RESULT_NEED_MEMORY; goto exit_send; } - for (i = 0; i < size; i++) - spa_log_debug(monitor->log, " %d: %02x", i, pconf[i]); + spa_log_hexdump(monitor->log, SPA_LOG_LEVEL_DEBUG, 2, pconf, (size_t)size); if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; @@ -1734,7 +1732,7 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en else if (spa_streq(key, "Capabilities")) { DBusMessageIter iter; uint8_t *value; - int i, len; + int len; if (!check_iter_signature(&it[1], "ay")) goto next; @@ -1743,8 +1741,7 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en dbus_message_iter_get_fixed_array(&iter, &value, &len); spa_log_debug(monitor->log, "remote_endpoint %p: %s=%d", remote_endpoint, key, len); - for (i = 0; i < len; i++) - spa_log_debug(monitor->log, " %d: %02x", i, value[i]); + spa_log_hexdump(monitor->log, SPA_LOG_LEVEL_DEBUG, 2, value, (size_t)len); free(remote_endpoint->capabilities); remote_endpoint->capabilities_len = 0; @@ -2267,7 +2264,7 @@ static int transport_update_props(struct spa_bt_transport *transport, else if (spa_streq(key, "Configuration")) { DBusMessageIter iter; uint8_t *value; - int i, len; + int len; if (!check_iter_signature(&it[1], "ay")) goto next; @@ -2276,8 +2273,7 @@ static int transport_update_props(struct spa_bt_transport *transport, dbus_message_iter_get_fixed_array(&iter, &value, &len); spa_log_debug(monitor->log, "transport %p: %s=%d", transport, key, len); - for (i = 0; i < len; i++) - spa_log_debug(monitor->log, " %d: %02x", i, value[i]); + spa_log_hexdump(monitor->log, SPA_LOG_LEVEL_DEBUG, 2, value, (size_t)len); free(transport->configuration); transport->configuration_len = 0; diff --git a/spa/plugins/libcamera/libcamera-utils.cpp b/spa/plugins/libcamera/libcamera-utils.cpp index c070e1f60c6164c2b7ef5459eeb4ce4ed26d8d6f..99d0672541f1605ad05696e90c193beb4dee7ae4 100644 --- a/spa/plugins/libcamera/libcamera-utils.cpp +++ b/spa/plugins/libcamera/libcamera-utils.cpp @@ -417,7 +417,7 @@ static int spa_libcamera_set_format(struct impl *impl, struct port *port, port->streamConfig = impl->config->at(0); if ((res = allocBuffers(impl, port, port->streamConfig.bufferCount)) < 0) - return res; + goto error; port->have_format = true; diff --git a/spa/plugins/support/null-audio-sink.c b/spa/plugins/support/null-audio-sink.c index abee4bedd5f46c523e4e93f0523152acd031e0cc..4c000f3eb1d0eea5babd92ce70b1cf2699e0e3c1 100644 --- a/spa/plugins/support/null-audio-sink.c +++ b/spa/plugins/support/null-audio-sink.c @@ -71,7 +71,7 @@ static void reset_props(struct props *props) } #define DEFAULT_CHANNELS 2 -#define DEFAULT_RATE 44100 +#define DEFAULT_RATE 48000 #define MAX_BUFFERS 16 #define MAX_PORTS 1 diff --git a/spa/plugins/v4l2/v4l2-source.c b/spa/plugins/v4l2/v4l2-source.c index ac83032a8b515f1fa03fd59efcf8d781b49d578b..802ead7ee2dbfca254aaf93fe301f916e55e18c8 100644 --- a/spa/plugins/v4l2/v4l2-source.c +++ b/spa/plugins/v4l2/v4l2-source.c @@ -314,7 +314,7 @@ static int impl_node_send_command(void *object, const struct spa_command *comman switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_ParamBegin: - if ((res = spa_v4l2_open(&port->dev, NULL)) < 0) + if ((res = spa_v4l2_open(&port->dev, this->props.device)) < 0) return res; break; case SPA_NODE_COMMAND_ParamEnd: diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 22b8fb36837edb591a5fb85c80cd48aa45c9f6da..694e6af65abe48d9485b5be2bef7a2fffc537d1b 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -24,23 +24,32 @@ #include <spa/support/plugin.h> #include <spa/support/log.h> +#include <spa/support/cpu.h> #include <spa/node/node.h> #include <spa/node/io.h> #include <spa/node/utils.h> #include <spa/node/keys.h> -#include <spa/utils/result.h> #include <spa/utils/names.h> +#include <spa/utils/result.h> #include <spa/utils/string.h> #include <spa/buffer/alloc.h> #include <spa/pod/parser.h> #include <spa/pod/filter.h> +#include <spa/pod/dynamic.h> +#include <spa/param/param.h> +#include <spa/param/video/format-utils.h> +#include <spa/param/latency-utils.h> #include <spa/debug/format.h> #include <spa/debug/pod.h> +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT log_topic +static struct spa_log_topic *log_topic = &SPA_LOG_TOPIC(0, "spa.videoadapter"); + #define DEFAULT_ALIGN 16 -#define NAME "videoadapter" +#define MAX_PORTS 1 /** \cond */ @@ -49,19 +58,22 @@ struct impl { struct spa_node node; struct spa_log *log; + struct spa_cpu *cpu; + uint32_t max_align; enum spa_direction direction; struct spa_node *target; - struct spa_hook target_listener; struct spa_node *follower; struct spa_hook follower_listener; uint32_t follower_flags; + struct spa_video_info follower_current_format; + struct spa_video_info default_format; struct spa_handle *hnd_convert; struct spa_node *convert; - + struct spa_hook convert_listener; uint32_t convert_flags; uint32_t n_buffers; @@ -69,32 +81,71 @@ struct impl { struct spa_io_buffers io_buffers; struct spa_io_rate_match io_rate_match; + struct spa_io_position *io_position; uint64_t info_all; struct spa_node_info info; - struct spa_param_info params[5]; +#define IDX_EnumFormat 0 +#define IDX_PropInfo 1 +#define IDX_Props 2 +#define IDX_Format 3 +#define IDX_EnumPortConfig 4 +#define IDX_PortConfig 5 +#define IDX_Latency 6 +#define IDX_ProcessLatency 7 +#define N_NODE_PARAMS 8 + struct spa_param_info params[N_NODE_PARAMS]; + uint32_t convert_params_flags[N_NODE_PARAMS]; + uint32_t follower_params_flags[N_NODE_PARAMS]; struct spa_hook_list hooks; struct spa_callbacks callbacks; - unsigned int use_converter:1; + unsigned int add_listener:1; + unsigned int have_format:1; unsigned int started:1; - unsigned int active:1; unsigned int driver:1; - unsigned int driving:1; - unsigned int monitor:1; + unsigned int async:1; + unsigned int passthrough:1; + unsigned int follower_removing:1; }; /** \endcond */ +static int follower_enum_params(struct impl *this, + uint32_t id, + uint32_t idx, + struct spa_result_node_params *result, + const struct spa_pod *filter, + struct spa_pod_builder *builder) +{ + int res; + if (result->next < 0x100000) { + if (this->convert != NULL && + (res = spa_node_enum_params_sync(this->convert, + id, &result->next, filter, &result->param, builder)) == 1) + return res; + result->next = 0x100000; + } + if (result->next < 0x200000 && this->follower_params_flags[idx] & SPA_PARAM_INFO_READ) { + result->next &= 0xfffff; + if ((res = spa_node_enum_params_sync(this->follower, + id, &result->next, filter, &result->param, builder)) == 1) { + result->next |= 0x100000; + return res; + } + result->next = 0x200000; + } + return 0; +} + static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; - struct spa_pod *param; - struct spa_pod_builder b = { 0 }; - uint8_t buffer[1024]; + uint8_t buffer[4096]; + struct spa_pod_dynamic_builder b; struct spa_result_node_params result; uint32_t count = 0; int res; @@ -105,50 +156,65 @@ static int impl_node_enum_params(void *object, int seq, result.id = id; result.next = start; next: - result.index = result.next++; + result.index = result.next; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); + spa_log_debug(this->log, "%p: %d id:%u", this, seq, id); + + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); switch (id) { + case SPA_PARAM_EnumPortConfig: + case SPA_PARAM_PortConfig: + if (this->convert == NULL) + return 0; + res = spa_node_enum_params(this->convert, seq, id, start, num, filter); + return res; case SPA_PARAM_PropInfo: + res = follower_enum_params(this, + id, IDX_PropInfo, &result, filter, &b.b); + break; case SPA_PARAM_Props: - if ((res = spa_node_enum_params_sync(this->target, - id, &start, filter, ¶m, &b)) != 1) - return res; + res = follower_enum_params(this, + id, IDX_Props, &result, filter, &b.b); + break; + case SPA_PARAM_ProcessLatency: + res = follower_enum_params(this, + id, IDX_ProcessLatency, &result, filter, &b.b); break; - case SPA_PARAM_EnumFormat: case SPA_PARAM_Format: - if ((res = spa_node_port_enum_params_sync(this->follower, + case SPA_PARAM_Latency: + res = spa_node_port_enum_params_sync(this->follower, this->direction, 0, - id, &start, filter, ¶m, &b)) != 1) - return res; + id, &result.next, filter, &result.param, &b.b); break; - default: return -ENOENT; } - if (spa_pod_filter(&b, &result.param, param, filter) < 0) - goto next; + if (res == 1) { + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + count++; + } + spa_pod_dynamic_builder_clean(&b); - spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + if (res != 1) + return res; - if (++count != num) + if (count != num) goto next; return 0; } -#if 0 static int link_io(struct impl *this) { int res; - if (!this->use_converter) + if (this->convert == NULL) return 0; - spa_log_warn(this->log, NAME " %p: controls", this); + spa_log_debug(this->log, "%p: controls", this); spa_zero(this->io_rate_match); this->io_rate_match.rate = 1.0; @@ -157,14 +223,14 @@ static int link_io(struct impl *this) this->direction, 0, SPA_IO_RateMatch, &this->io_rate_match, sizeof(this->io_rate_match))) < 0) { - spa_log_warn(this->log, NAME " %p: set RateMatch on follower failed %d %s", this, + spa_log_debug(this->log, "%p: set RateMatch on follower disabled %d %s", this, res, spa_strerror(res)); } else if ((res = spa_node_port_set_io(this->convert, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_IO_RateMatch, &this->io_rate_match, sizeof(this->io_rate_match))) < 0) { - spa_log_warn(this->log, NAME " %p: set RateMatch on convert failed %d %s", this, + spa_log_warn(this->log, "%p: set RateMatch on convert failed %d %s", this, res, spa_strerror(res)); } @@ -174,7 +240,7 @@ static int link_io(struct impl *this) this->direction, 0, SPA_IO_Buffers, &this->io_buffers, sizeof(this->io_buffers))) < 0) { - spa_log_warn(this->log, NAME " %p: set Buffers on follower failed %d %s", this, + spa_log_warn(this->log, "%p: set Buffers on follower failed %d %s", this, res, spa_strerror(res)); return res; } @@ -182,58 +248,425 @@ static int link_io(struct impl *this) SPA_DIRECTION_REVERSE(this->direction), 0, SPA_IO_Buffers, &this->io_buffers, sizeof(this->io_buffers))) < 0) { - spa_log_warn(this->log, NAME " %p: set Buffers on convert failed %d %s", this, + spa_log_warn(this->log, "%p: set Buffers on convert failed %d %s", this, res, spa_strerror(res)); return res; } return 0; } -#endif static void emit_node_info(struct impl *this, bool full) { + uint32_t i; uint64_t old = full ? this->info.change_mask : 0; + + spa_log_debug(this->log, "%p: info full:%d change:%08"PRIx64, + this, full, this->info.change_mask); + if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { - struct spa_dict_item items[1]; - - this->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; - items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_DRIVER, this->driver ? "true" : "false"); - this->info.props = &SPA_DICT_INIT(items, 1); - + if (this->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { + for (i = 0; i < this->info.n_params; i++) { + if (this->params[i].user > 0) { + this->params[i].flags ^= SPA_PARAM_INFO_SERIAL; + this->params[i].user = 0; + spa_log_debug(this->log, "param %d flags:%08x", + i, this->params[i].flags); + } + } + } spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } +static int debug_params(struct impl *this, struct spa_node *node, + enum spa_direction direction, uint32_t port_id, uint32_t id, struct spa_pod *filter, + const char *debug, int err) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[4096]; + uint32_t state; + struct spa_pod *param; + int res, count = 0; + + spa_log_error(this->log, "params %s: %d:%d (%s) %s", + spa_debug_type_find_name(spa_type_param, id), + direction, port_id, debug, err ? spa_strerror(err) : "no matching params"); + if (err == -EBUSY) + return 0; + + if (filter) { + spa_log_error(this->log, "with this filter:"); + spa_debug_pod(2, NULL, filter); + } else { + spa_log_error(this->log, "there was no filter"); + } + + state = 0; + while (true) { + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + res = spa_node_port_enum_params_sync(node, + direction, port_id, + id, &state, + NULL, ¶m, &b); + if (res != 1) { + if (res < 0) + spa_log_error(this->log, " error: %s", spa_strerror(res)); + break; + } + spa_log_error(this->log, "unmatched %s %d:", debug, count); + spa_debug_pod(2, NULL, param); + count++; + } + if (count == 0) + spa_log_error(this->log, "could not get any %s", debug); + + return 0; +} + +static int negotiate_buffers(struct impl *this) +{ + uint8_t buffer[4096]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + uint32_t state; + struct spa_pod *param; + int res; + bool follower_alloc, conv_alloc; + uint32_t i, size, buffers, blocks, align, flags, stride = 0; + uint32_t *aligns; + struct spa_data *datas; + uint32_t follower_flags, conv_flags; + + spa_log_debug(this->log, "%p: %d", this, this->n_buffers); + + if (this->target == this->follower) + return 0; + + if (this->n_buffers > 0) + return 0; + + state = 0; + param = NULL; + if ((res = spa_node_port_enum_params_sync(this->follower, + this->direction, 0, + SPA_PARAM_Buffers, &state, + param, ¶m, &b)) < 0) { + if (res == -ENOENT) + param = NULL; + else { + debug_params(this, this->follower, this->direction, 0, + SPA_PARAM_Buffers, param, "follower buffers", res); + return res; + } + } + + state = 0; + if ((res = spa_node_port_enum_params_sync(this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_Buffers, &state, + param, ¶m, &b)) != 1) { + debug_params(this, this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_Buffers, param, "convert buffers", res); + return -ENOTSUP; + } + if (param == NULL) + return -ENOTSUP; + + spa_pod_fixate(param); + + follower_flags = this->follower_flags; + conv_flags = this->convert_flags; + + follower_alloc = SPA_FLAG_IS_SET(follower_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); + conv_alloc = SPA_FLAG_IS_SET(conv_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); + + flags = 0; + if (conv_alloc || follower_alloc) { + flags |= SPA_BUFFER_ALLOC_FLAG_NO_DATA; + if (conv_alloc) + follower_alloc = false; + } + + align = DEFAULT_ALIGN; + + if ((res = spa_pod_parse_object(param, + SPA_TYPE_OBJECT_ParamBuffers, NULL, + SPA_PARAM_BUFFERS_buffers, SPA_POD_Int(&buffers), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(&blocks), + SPA_PARAM_BUFFERS_size, SPA_POD_Int(&size), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(&stride), + SPA_PARAM_BUFFERS_align, SPA_POD_OPT_Int(&align))) < 0) + return res; + + spa_log_debug(this->log, "%p: buffers:%d, blocks:%d, size:%d, stride:%d align:%d %d:%d", + this, buffers, blocks, size, stride, align, follower_alloc, conv_alloc); + + align = SPA_MAX(align, this->max_align); + + datas = alloca(sizeof(struct spa_data) * blocks); + memset(datas, 0, sizeof(struct spa_data) * blocks); + aligns = alloca(sizeof(uint32_t) * blocks); + for (i = 0; i < blocks; i++) { + datas[i].type = SPA_DATA_MemPtr; + datas[i].flags = SPA_DATA_FLAG_READWRITE | SPA_DATA_FLAG_DYNAMIC; + datas[i].maxsize = size; + aligns[i] = align; + } + + free(this->buffers); + this->buffers = spa_buffer_alloc_array(buffers, flags, 0, NULL, blocks, datas, aligns); + if (this->buffers == NULL) + return -errno; + this->n_buffers = buffers; + + if ((res = spa_node_port_use_buffers(this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + conv_alloc ? SPA_NODE_BUFFERS_FLAG_ALLOC : 0, + this->buffers, this->n_buffers)) < 0) + return res; + + if ((res = spa_node_port_use_buffers(this->follower, + this->direction, 0, + follower_alloc ? SPA_NODE_BUFFERS_FLAG_ALLOC : 0, + this->buffers, this->n_buffers)) < 0) + return res; + + return 0; +} + +static int configure_format(struct impl *this, uint32_t flags, const struct spa_pod *format) +{ + int res; + + spa_log_debug(this->log, "%p: configure format:", this); + if (format && spa_log_level_enabled(this->log, SPA_LOG_LEVEL_DEBUG)) + spa_debug_format(0, NULL, format); + + if ((res = spa_node_port_set_param(this->follower, + this->direction, 0, + SPA_PARAM_Format, flags, + format)) < 0) + return res; + if (res > 0) { + uint8_t buffer[4096]; + struct spa_pod_builder b = { 0 }; + 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, + SPA_PARAM_Format, &state, + NULL, &fmt, &b)) != 1) + return -EIO; + + format = fmt; + } + + if (this->target != this->follower && this->convert) { + if ((res = spa_node_port_set_param(this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_Format, flags, + format)) < 0) + return res; + } + + this->have_format = format != NULL; + if (format == NULL) { + this->n_buffers = 0; + } else { + res = negotiate_buffers(this); + } + + return res; +} + +static int configure_convert(struct impl *this, uint32_t mode) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param; + + if (this->convert == NULL) + return 0; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + spa_log_debug(this->log, "%p: configure convert %p", this, this->target); + + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(this->direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(mode)); + + return spa_node_set_param(this->convert, SPA_PARAM_PortConfig, 0, param); +} + +extern const struct spa_handle_factory spa_videoconvert_factory; + +static const struct spa_node_events follower_node_events; + +static int reconfigure_mode(struct impl *this, bool passthrough, + enum spa_direction direction, struct spa_pod *format) +{ + int res = 0; + struct spa_hook l; + + spa_log_info(this->log, "%p: passthrough mode %d", this, passthrough); + + if (this->passthrough != passthrough) { + if (passthrough) { + /* remove converter split/merge ports */ + configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_none); + } else { + /* remove follower ports */ + this->follower_removing = true; + spa_zero(l); + spa_node_add_listener(this->follower, &l, &follower_node_events, this); + spa_hook_remove(&l); + this->follower_removing = false; + } + } + + /* set new target */ + this->target = passthrough ? this->follower : this->convert; + + if ((res = configure_format(this, SPA_NODE_PARAM_FLAG_NEAREST, format)) < 0) + return res; + + if (this->passthrough != passthrough) { + this->passthrough = passthrough; + if (passthrough) { + /* add follower ports */ + spa_zero(l); + spa_node_add_listener(this->follower, &l, &follower_node_events, this); + spa_hook_remove(&l); + } else { + /* add converter ports */ + configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_dsp); + link_io(this); + } + } + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; + this->info.flags &= ~SPA_NODE_FLAG_NEED_CONFIGURE; + this->params[IDX_Props].user++; + + emit_node_info(this, false); + + return 0; +} + +static int format_video_raw_parse_opt(const struct spa_pod *format, struct spa_video_info_raw *info) +{ + uint32_t media_type, media_subtype; + int res; + if ((res = spa_format_parse(format, &media_type, &media_subtype)) < 0) + return res; + if (media_type != SPA_MEDIA_TYPE_video || + media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -ENOTSUP; + + spa_zero(*info); + res = spa_pod_parse_object(format, + SPA_TYPE_OBJECT_Format, NULL, + SPA_FORMAT_VIDEO_format, SPA_POD_OPT_Id(&info->format), + SPA_FORMAT_VIDEO_size, SPA_POD_OPT_Int(&info->size)); + return res; +} + static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { - int res = 0; + int res = 0, res2 = 0; struct impl *this = object; + struct spa_video_info info = { 0 }; - spa_log_debug(this->log, NAME" %p: set param %d", this, id); + spa_log_debug(this->log, "%p: set param %d", this, id); switch (id) { - case SPA_PARAM_PortConfig: + case SPA_PARAM_Format: if (this->started) return -EIO; - if (this->target != this->follower) { - if ((res = spa_node_set_param(this->target, id, flags, param)) < 0) + if (param == NULL) + return -EINVAL; + + if ((res = spa_format_parse(param, &info.media_type, &info.media_subtype)) < 0) + return res; + if (info.media_type != SPA_MEDIA_TYPE_video || + info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -EINVAL; + if (spa_format_video_raw_parse(param, &info.info.raw) < 0) + return -EINVAL; + + this->follower_current_format = info; + break; + + case SPA_PARAM_PortConfig: + { + enum spa_direction dir; + enum spa_param_port_config_mode mode; + struct spa_pod *format = NULL; + + if (this->started) { + spa_log_error(this->log, "was started"); + return -EIO; + } + + if (spa_pod_parse_object(param, + SPA_TYPE_OBJECT_ParamPortConfig, NULL, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(&dir), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(&mode), + SPA_PARAM_PORT_CONFIG_format, SPA_POD_OPT_Pod(&format)) < 0) + return -EINVAL; + + if (format) { + struct spa_video_info info; + if (format_video_raw_parse_opt(format, &info.info.raw) >= 0) + this->default_format = info; + } + + switch (mode) { + case SPA_PARAM_PORT_CONFIG_MODE_none: + return -ENOTSUP; + case SPA_PARAM_PORT_CONFIG_MODE_passthrough: + if ((res = reconfigure_mode(this, true, dir, format)) < 0) return res; + break; + case SPA_PARAM_PORT_CONFIG_MODE_convert: + case SPA_PARAM_PORT_CONFIG_MODE_dsp: + if (this->convert == NULL) + return -ENOTSUP; + if ((res = reconfigure_mode(this, false, dir, NULL)) < 0) + return res; + break; + default: + return -EINVAL; } - break; - case SPA_PARAM_Props: + if (this->target != this->follower) { if ((res = spa_node_set_param(this->target, id, flags, param)) < 0) return res; - - this->info.change_mask = SPA_NODE_CHANGE_MASK_PARAMS; - this->params[2].flags ^= SPA_PARAM_INFO_SERIAL; - emit_node_info(this, false); } break; + } + + case SPA_PARAM_Props: + if (this->target != this->follower) + res = spa_node_set_param(this->target, id, flags, param); + res2 = spa_node_set_param(this->follower, id, flags, param); + if (res < 0 && res2 < 0) + return res; + res = 0; + break; + case SPA_PARAM_ProcessLatency: + res = spa_node_set_param(this->follower, id, flags, param); + break; default: res = -ENOTSUP; break; @@ -248,6 +681,14 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) spa_return_val_if_fail(this != NULL, -EINVAL); + switch (id) { + case SPA_IO_Position: + this->io_position = data; + break; + default: + break; + } + if (this->target) res = spa_node_set_io(this->target, id, data, size); @@ -257,105 +698,479 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) return res; } +static struct spa_pod *merge_objects(struct impl *this, struct spa_pod_builder *b, uint32_t id, + struct spa_pod_object *o1, struct spa_pod_object *o2) +{ + const struct spa_pod_prop *p1, *p2; + struct spa_pod_frame f; + struct spa_pod_builder_state state; + int res = 0; + + if (o2 == NULL || SPA_POD_TYPE(o1) != SPA_POD_TYPE(o2)) + return (struct spa_pod*)o1; + + spa_pod_builder_push_object(b, &f, o1->body.type, o1->body.id); + p2 = NULL; + SPA_POD_OBJECT_FOREACH(o1, p1) { + p2 = spa_pod_object_find_prop(o2, p2, p1->key); + if (p2 != NULL) { + spa_pod_builder_get_state(b, &state); + res = spa_pod_filter_prop(b, p1, p2); + if (res < 0) + spa_pod_builder_reset(b, &state); + } + if (p2 == NULL || res < 0) + spa_pod_builder_raw_padded(b, p1, SPA_POD_PROP_SIZE(p1)); + } + p1 = NULL; + SPA_POD_OBJECT_FOREACH(o2, p2) { + p1 = spa_pod_object_find_prop(o1, p1, p2->key); + if (p1 != NULL) + continue; + spa_pod_builder_raw_padded(b, p2, SPA_POD_PROP_SIZE(p2)); + } + return spa_pod_builder_pop(b, &f); +} + +static int negotiate_format(struct impl *this) +{ + uint32_t state; + struct spa_pod *format, *def; + uint8_t buffer[4096]; + struct spa_pod_builder b = { 0 }; + int res; + + if (this->have_format) + return 0; + + if (this->target == this->follower) + return 0; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + spa_log_debug(this->log, "%p: negiotiate", this); + + spa_node_send_command(this->follower, + &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamBegin)); + + state = 0; + format = NULL; + if ((res = spa_node_port_enum_params_sync(this->follower, + this->direction, 0, + SPA_PARAM_EnumFormat, &state, + format, &format, &b)) < 0) { + if (res == -ENOENT) + format = NULL; + else { + debug_params(this, this->follower, this->direction, 0, + SPA_PARAM_EnumFormat, format, "follower format", res); + goto done; + } + } + if (this->convert) { + state = 0; + if ((res = spa_node_port_enum_params_sync(this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_EnumFormat, &state, + format, &format, &b)) != 1) { + debug_params(this, this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_EnumFormat, format, "convert format", res); + res = -ENOTSUP; + goto done; + } + } + if (format == NULL) { + res = -ENOTSUP; + goto done; + } + + def = spa_format_video_raw_build(&b, + SPA_PARAM_Format, &this->default_format.info.raw); + + format = merge_objects(this, &b, SPA_PARAM_Format, + (struct spa_pod_object*)format, + (struct spa_pod_object*)def); + + spa_pod_fixate(format); + + res = configure_format(this, SPA_NODE_PARAM_FLAG_NEAREST, format); + +done: + spa_node_send_command(this->follower, + &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamEnd)); + + return res; +} + + static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; int res; - spa_return_val_if_fail(this != NULL, -EINVAL); - - switch (SPA_NODE_COMMAND_ID(command)) { - case SPA_NODE_COMMAND_Start: - this->started = true; - break; - case SPA_NODE_COMMAND_Pause: - this->started = false; - break; - default: - break; - } + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_log_debug(this->log, "%p: command %d", this, SPA_NODE_COMMAND_ID(command)); + + switch (SPA_NODE_COMMAND_ID(command)) { + case SPA_NODE_COMMAND_Start: + if ((res = negotiate_format(this)) < 0) + return res; + if ((res = negotiate_buffers(this)) < 0) + return res; + break; + case SPA_NODE_COMMAND_Suspend: + configure_format(this, 0, NULL); + SPA_FALLTHROUGH + case SPA_NODE_COMMAND_Flush: + this->io_buffers.status = SPA_STATUS_OK; + SPA_FALLTHROUGH + case SPA_NODE_COMMAND_Pause: + this->started = false; + break; + default: + break; + } + + if ((res = spa_node_send_command(this->target, command)) < 0) { + spa_log_error(this->log, "%p: can't send command %d: %s", + this, SPA_NODE_COMMAND_ID(command), + spa_strerror(res)); + return res; + } + + if (this->target != this->follower) { + if ((res = spa_node_send_command(this->follower, command)) < 0) { + spa_log_error(this->log, "%p: can't send command %d: %s", + this, SPA_NODE_COMMAND_ID(command), + spa_strerror(res)); + return res; + } + } + switch (SPA_NODE_COMMAND_ID(command)) { + case SPA_NODE_COMMAND_Start: + this->started = true; + break; + } + return res; +} + +static void convert_node_info(void *data, const struct spa_node_info *info) +{ + struct impl *this = data; + uint32_t i; + + spa_log_debug(this->log, "%p: info change:%08"PRIx64, this, + info->change_mask); + + if (info->change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { + for (i = 0; i < info->n_params; i++) { + uint32_t idx; + + switch (info->params[i].id) { + case SPA_PARAM_EnumPortConfig: + idx = IDX_EnumPortConfig; + break; + case SPA_PARAM_PortConfig: + idx = IDX_PortConfig; + break; + case SPA_PARAM_PropInfo: + idx = IDX_PropInfo; + break; + case SPA_PARAM_Props: + idx = IDX_Props; + break; + default: + continue; + } + if (!this->add_listener && + this->convert_params_flags[idx] == info->params[i].flags) + continue; + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + this->convert_params_flags[idx] = info->params[i].flags; + this->params[idx].flags = + (this->params[idx].flags & SPA_PARAM_INFO_SERIAL) | + (info->params[i].flags & SPA_PARAM_INFO_READWRITE); + + if (!this->add_listener) { + this->params[idx].user++; + spa_log_debug(this->log, "param %d changed", info->params[i].id); + } + } + } + emit_node_info(this, false); +} + +static void convert_port_info(void *data, + enum spa_direction direction, uint32_t port_id, + const struct spa_port_info *info) +{ + struct impl *this = data; + + if (direction != this->direction) { + if (port_id == 0) + return; + else + port_id--; + } + + spa_log_debug(this->log, "%p: port info %d:%d", this, + direction, port_id); + + if (this->target != this->follower) + spa_node_emit_port_info(&this->hooks, direction, port_id, info); +} + +static void convert_result(void *data, int seq, int res, uint32_t type, const void *result) +{ + struct impl *this = data; + + if (this->target == this->follower) + return; + + spa_log_trace(this->log, "%p: result %d %d", this, seq, res); + spa_node_emit_result(&this->hooks, seq, res, type, result); +} + +static const struct spa_node_events convert_node_events = { + SPA_VERSION_NODE_EVENTS, + .info = convert_node_info, + .port_info = convert_port_info, + .result = convert_result, +}; + +static void follower_info(void *data, const struct spa_node_info *info) +{ + struct impl *this = data; + uint32_t i; + + spa_log_debug(this->log, "%p: info change:%08"PRIx64, this, + info->change_mask); + + if (this->follower_removing) + return; + + this->async = (info->flags & SPA_NODE_FLAG_ASYNC) != 0; + + if (info->max_input_ports > 0) + this->direction = SPA_DIRECTION_INPUT; + else + this->direction = SPA_DIRECTION_OUTPUT; + + if (this->direction == SPA_DIRECTION_INPUT) { + this->info.flags |= SPA_NODE_FLAG_IN_PORT_CONFIG; + this->info.max_input_ports = MAX_PORTS; + } else { + this->info.flags |= SPA_NODE_FLAG_OUT_PORT_CONFIG; + this->info.max_output_ports = MAX_PORTS; + } + + spa_log_debug(this->log, "%p: follower info %s", this, + this->direction == SPA_DIRECTION_INPUT ? + "Input" : "Output"); + + if (info->change_mask & SPA_NODE_CHANGE_MASK_PROPS) { + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; + this->info.props = info->props; + } + if (info->change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { + for (i = 0; i < info->n_params; i++) { + uint32_t idx; + + switch (info->params[i].id) { + case SPA_PARAM_PropInfo: + idx = IDX_PropInfo; + break; + case SPA_PARAM_Props: + idx = IDX_Props; + break; + case SPA_PARAM_ProcessLatency: + idx = IDX_ProcessLatency; + break; + default: + continue; + } + if (!this->add_listener && + this->follower_params_flags[idx] == info->params[i].flags) + continue; + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + this->follower_params_flags[idx] = info->params[i].flags; + this->params[idx].flags = + (this->params[idx].flags & SPA_PARAM_INFO_SERIAL) | + (info->params[i].flags & SPA_PARAM_INFO_READWRITE); + + if (!this->add_listener) { + this->params[idx].user++; + spa_log_debug(this->log, "param %d changed", info->params[i].id); + } + } + } + emit_node_info(this, false); + + spa_zero(this->info.props); + this->info.change_mask &= ~SPA_NODE_CHANGE_MASK_PROPS; + +} + +static int recalc_latency(struct impl *this, enum spa_direction direction, uint32_t port_id) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param; + uint32_t index = 0; + struct spa_latency_info latency; + int res; + + spa_log_debug(this->log, "%p: ", this); - if ((res = spa_node_send_command(this->target, command)) < 0) { - spa_log_error(this->log, NAME " %p: can't start convert: %s", - this, spa_strerror(res)); - return res; - } + if (this->target == this->follower) + return 0; - if (this->target != this->follower) { - if ((res = spa_node_send_command(this->follower, command)) < 0) { - spa_log_error(this->log, NAME " %p: can't start follower: %s", - this, spa_strerror(res)); + while (true) { + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + if ((res = spa_node_port_enum_params_sync(this->follower, + direction, port_id, SPA_PARAM_Latency, + &index, NULL, ¶m, &b)) != 1) return res; - } + if ((res = spa_latency_parse(param, &latency)) < 0) + return res; + if (latency.direction == direction) + break; } - return res; + if ((res = spa_node_port_set_param(this->target, + SPA_DIRECTION_REVERSE(direction), 0, + SPA_PARAM_Latency, 0, param)) < 0) + return res; + + return 0; } -static void target_port_info(void *data, +static void follower_port_info(void *data, enum spa_direction direction, uint32_t port_id, const struct spa_port_info *info) { struct impl *this = data; + uint32_t i; + int res; - if (direction != this->direction) { - if (port_id == 0) - return; - else - port_id--; + if (this->follower_removing) { + spa_node_emit_port_info(&this->hooks, direction, port_id, NULL); + return; } - spa_log_trace(this->log, NAME" %p: port info %d:%d", this, - direction, port_id); + spa_log_debug(this->log, "%p: follower port info %s %p %08"PRIx64, this, + this->direction == SPA_DIRECTION_INPUT ? + "Input" : "Output", info, info->change_mask); + + if (info->change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { + for (i = 0; i < info->n_params; i++) { + uint32_t idx; + + switch (info->params[i].id) { + case SPA_PARAM_EnumFormat: + idx = IDX_EnumFormat; + break; + case SPA_PARAM_Format: + idx = IDX_Format; + break; + case SPA_PARAM_Latency: + idx = IDX_Latency; + break; + default: + continue; + } + if (!this->add_listener && + this->follower_params_flags[idx] == info->params[i].flags) + continue; + + this->follower_params_flags[idx] = info->params[i].flags; + this->params[idx].flags = + (this->params[idx].flags & SPA_PARAM_INFO_SERIAL) | + (info->params[i].flags & SPA_PARAM_INFO_READWRITE); + + if (idx == IDX_Latency) { + res = recalc_latency(this, direction, port_id); + spa_log_debug(this->log, "latency: %d (%s)", res, + spa_strerror(res)); + } + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + if (!this->add_listener) { + this->params[idx].user++; + spa_log_debug(this->log, "param %d changed", info->params[i].id); + } + } + } + emit_node_info(this, false); - spa_node_emit_port_info(&this->hooks, direction, port_id, info); + if (this->target == this->follower) + spa_node_emit_port_info(&this->hooks, direction, port_id, info); } -static void target_result(void *data, int seq, int res, uint32_t type, const void *result) +static void follower_result(void *data, int seq, int res, uint32_t type, const void *result) { struct impl *this = data; - spa_log_trace(this->log, NAME" %p: result %d %d", this, seq, res); + + if (this->target != this->follower) + return; + + spa_log_trace(this->log, "%p: result %d %d", this, seq, res); spa_node_emit_result(&this->hooks, seq, res, type, result); } -static const struct spa_node_events target_node_events = { - SPA_VERSION_NODE_EVENTS, - .port_info = target_port_info, - .result = target_result, -}; - -static void follower_info(void *data, const struct spa_node_info *info) +static void follower_event(void *data, const struct spa_event *event) { struct impl *this = data; - const char *str; - - if (info->max_input_ports > 0) - this->direction = SPA_DIRECTION_INPUT; - else - this->direction = SPA_DIRECTION_OUTPUT; - spa_log_debug(this->log, NAME" %p: follower info %s", this, - this->direction == SPA_DIRECTION_INPUT ? - "Input" : "Output"); + spa_log_trace(this->log, "%p: event %d", this, SPA_EVENT_TYPE(event)); - if (info->props) { - if ((str = spa_dict_lookup(info->props, SPA_KEY_NODE_DRIVER)) != NULL) - this->driver = spa_atob(str); + switch (SPA_NODE_EVENT_ID(event)) { + case SPA_NODE_EVENT_Error: + /* Forward errors */ + spa_node_emit_event(&this->hooks, event); + break; + default: + /* Ignore other events */ + break; } } static const struct spa_node_events follower_node_events = { SPA_VERSION_NODE_EVENTS, .info = follower_info, + .port_info = follower_port_info, + .result = follower_result, + .event = follower_event, }; static int follower_ready(void *data, int status) { struct impl *this = data; - spa_log_trace(this->log, NAME " %p: ready %d", this, status); + spa_log_trace_fp(this->log, "%p: ready %d", this, status); + + if (this->target != this->follower) { + this->driver = true; + + if (this->direction == SPA_DIRECTION_OUTPUT) { + int retry = 8; + while (retry--) { + status = spa_node_process(this->convert); + if (status & SPA_STATUS_HAVE_DATA) + break; + + if (status & SPA_STATUS_NEED_DATA) { + status = spa_node_process(this->follower); + if (!(status & SPA_STATUS_HAVE_DATA)) + break; + } + } - if (this->direction == SPA_DIRECTION_OUTPUT) - status = spa_node_process(this->convert); + } + } return spa_node_call_ready(&this->callbacks, status); } @@ -365,7 +1180,7 @@ static int follower_reuse_buffer(void *data, uint32_t port_id, uint32_t buffer_i int res; struct impl *this = data; - if (this->use_converter) + if (this->target != this->follower && this->convert) res = spa_node_port_reuse_buffer(this->convert, port_id, buffer_id); else res = spa_node_call_reuse_buffer(&this->callbacks, port_id, buffer_id); @@ -373,10 +1188,17 @@ static int follower_reuse_buffer(void *data, uint32_t port_id, uint32_t buffer_i return res; } +static int follower_xrun(void *data, uint64_t trigger, uint64_t delay, struct spa_pod *info) +{ + struct impl *this = data; + return spa_node_call_xrun(&this->callbacks, trigger, delay, info); +} + static const struct spa_node_callbacks follower_node_callbacks = { SPA_VERSION_NODE_CALLBACKS, .ready = follower_ready, .reuse_buffer = follower_reuse_buffer, + .xrun = follower_xrun, }; static int impl_node_add_listener(void *object, @@ -390,17 +1212,25 @@ static int impl_node_add_listener(void *object, spa_return_val_if_fail(this != NULL, -EINVAL); - spa_log_trace(this->log, NAME" %p: add listener %p", this, listener); + spa_log_trace(this->log, "%p: add listener %p", this, listener); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); - emit_node_info(this, true); + if (events->info || events->port_info) { + this->add_listener = true; - if (this->use_converter) { spa_zero(l); - spa_node_add_listener(this->convert, &l, &target_node_events, this); + spa_node_add_listener(this->follower, &l, &follower_node_events, this); spa_hook_remove(&l); - } + if (this->convert) { + spa_zero(l); + spa_node_add_listener(this->convert, &l, &convert_node_events, this); + spa_hook_remove(&l); + } + this->add_listener = false; + + emit_node_info(this, true); + } spa_hook_list_join(&this->hooks, &save); return 0; @@ -471,201 +1301,12 @@ impl_node_port_enum_params(void *object, int seq, if (direction != this->direction) port_id++; - spa_log_debug(this->log, NAME" %p: %d %u", this, seq, id); + spa_log_debug(this->log, "%p: %d %u", this, seq, id); return spa_node_port_enum_params(this->target, seq, direction, port_id, id, start, num, filter); } -static int debug_params(struct impl *this, struct spa_node *node, - enum spa_direction direction, uint32_t port_id, uint32_t id, struct spa_pod *filter, - const char *debug, int err) -{ - struct spa_pod_builder b = { 0 }; - uint8_t buffer[4096]; - uint32_t state; - struct spa_pod *param; - int res; - - spa_log_error(this->log, "params %s: %d:%d (%s) %s", - spa_debug_type_find_name(spa_type_param, id), - direction, port_id, debug, spa_strerror(err)); - - state = 0; - while (true) { - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - res = spa_node_port_enum_params_sync(node, - direction, port_id, - id, &state, - NULL, ¶m, &b); - if (res != 1) { - if (res < 0) - spa_log_error(this->log, " error: %s", spa_strerror(res)); - break; - } - spa_debug_pod(2, NULL, param); - } - - spa_log_error(this->log, "failed filter:"); - if (filter) - spa_debug_pod(2, NULL, filter); - - return 0; -} - - -static int negotiate_format(struct impl *this) -{ - uint32_t state; - struct spa_pod *format; - uint8_t buffer[4096]; - struct spa_pod_builder b = { 0 }; - int res; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - spa_log_debug(this->log, NAME "%p: negiotiate", this); - - state = 0; - format = NULL; - if ((res = spa_node_port_enum_params_sync(this->follower, - this->direction, 0, - SPA_PARAM_EnumFormat, &state, - format, &format, &b)) < 0) { - debug_params(this, this->follower, this->direction, 0, - SPA_PARAM_EnumFormat, format, "follower format", res); - return -ENOTSUP; - } - - state = 0; - if ((res = spa_node_port_enum_params_sync(this->convert, - SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_EnumFormat, &state, - format, &format, &b)) != 1) { - debug_params(this, this->convert, - SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_EnumFormat, format, "convert format", res); - return -ENOTSUP; - } - - spa_pod_fixate(format); - if (spa_log_level_enabled(this->log, SPA_LOG_LEVEL_DEBUG)) - spa_debug_format(0, NULL, format); - - if ((res = spa_node_port_set_param(this->convert, - SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_Format, 0, - format)) < 0) - return res; - - if ((res = spa_node_port_set_param(this->follower, - this->direction, 0, - SPA_PARAM_Format, 0, - format)) < 0) - return res; - - return res; -} - -static int negotiate_buffers(struct impl *this) -{ - uint8_t buffer[4096]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - uint32_t state; - struct spa_pod *param; - int res, i; - bool follower_alloc, conv_alloc; - int32_t size, buffers, blocks, align, flags; - uint32_t *aligns; - struct spa_data *datas; - uint32_t follower_flags, conv_flags; - - spa_log_debug(this->log, "%p: %d", this, this->n_buffers); - - if (this->n_buffers > 0) - return 0; - - state = 0; - param = NULL; - if ((res = spa_node_port_enum_params_sync(this->follower, - this->direction, 0, - SPA_PARAM_Buffers, &state, - param, ¶m, &b)) < 0) { - debug_params(this, this->follower, this->direction, 0, - SPA_PARAM_Buffers, param, "follower buffers", res); - return -ENOTSUP; - } - - state = 0; - if ((res = spa_node_port_enum_params_sync(this->convert, - SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_Buffers, &state, - param, ¶m, &b)) != 1) { - debug_params(this, this->convert, - SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_Buffers, param, "convert buffers", res); - return -ENOTSUP; - } - - spa_pod_fixate(param); - - follower_flags = this->follower_flags; - conv_flags = this->convert_flags; - - follower_alloc = SPA_FLAG_IS_SET(follower_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); - conv_alloc = SPA_FLAG_IS_SET(conv_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); - - flags = 0; - if (conv_alloc || follower_alloc) { - flags |= SPA_BUFFER_ALLOC_FLAG_NO_DATA; - if (conv_alloc) - follower_alloc = false; - } - - align = DEFAULT_ALIGN; - - if ((res = spa_pod_parse_object(param, - SPA_TYPE_OBJECT_ParamBuffers, NULL, - SPA_PARAM_BUFFERS_buffers, SPA_POD_Int(&buffers), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(&blocks), - SPA_PARAM_BUFFERS_size, SPA_POD_Int(&size), - SPA_PARAM_BUFFERS_align, SPA_POD_OPT_Int(&align))) < 0) - return res; - - spa_log_debug(this->log, "%p: buffers %d, blocks %d, size %d, align %d %d:%d", - this, buffers, blocks, size, align, follower_alloc, conv_alloc); - - datas = alloca(sizeof(struct spa_data) * blocks); - memset(datas, 0, sizeof(struct spa_data) * blocks); - aligns = alloca(sizeof(uint32_t) * blocks); - for (i = 0; i < blocks; i++) { - datas[i].type = SPA_DATA_MemPtr; - datas[i].flags = SPA_DATA_FLAG_DYNAMIC; - datas[i].maxsize = size; - aligns[i] = align; - } - - free(this->buffers); - this->buffers = spa_buffer_alloc_array(buffers, flags, 0, NULL, blocks, datas, aligns); - if (this->buffers == NULL) - return -errno; - this->n_buffers = buffers; - - if ((res = spa_node_port_use_buffers(this->convert, - SPA_DIRECTION_REVERSE(this->direction), 0, - conv_alloc ? SPA_NODE_BUFFERS_FLAG_ALLOC : 0, - this->buffers, this->n_buffers)) < 0) - return res; - - if ((res = spa_node_port_use_buffers(this->follower, - this->direction, 0, - follower_alloc ? SPA_NODE_BUFFERS_FLAG_ALLOC : 0, - this->buffers, this->n_buffers)) < 0) { - return res; - } - return 0; -} - static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, @@ -686,19 +1327,13 @@ impl_node_port_set_param(void *object, flags, param)) < 0) return res; - if (id == SPA_PARAM_Format && this->use_converter) { - if (param == NULL) { - if ((res = spa_node_port_set_param(this->target, - SPA_DIRECTION_REVERSE(direction), 0, - id, 0, NULL)) < 0) - return res; - this->n_buffers = 0; - } - else { - if (port_id == 0) - res = negotiate_format(this); - } + if ((id == SPA_PARAM_Latency) && + direction == this->direction) { + if ((res = spa_node_port_set_param(this->follower, direction, 0, id, + flags, param)) < 0) + return res; } + return res; } @@ -737,18 +1372,13 @@ impl_node_port_use_buffers(void *object, if (direction != this->direction) port_id++; + spa_log_debug(this->log, "%p: %d %d:%d", this, + n_buffers, direction, port_id); + if ((res = spa_node_port_use_buffers(this->target, direction, port_id, flags, buffers, n_buffers)) < 0) return res; - - spa_log_debug(this->log, NAME" %p: %d %d:%d", this, - n_buffers, direction, port_id); - - if (n_buffers > 0 && this->use_converter) { - if (port_id == 0) - res = negotiate_buffers(this); - } return res; } @@ -765,25 +1395,96 @@ impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) static int impl_node_process(void *object) { struct impl *this = object; - int status; + int status = 0, fstatus, retry = 8; - spa_log_trace_fp(this->log, "%p: process convert:%u", - this, this->use_converter); + spa_log_trace_fp(this->log, "%p: process convert:%p driver:%d", + this, this->convert, this->driver); - if (this->direction == SPA_DIRECTION_INPUT) { - if (this->use_converter) - status = spa_node_process(this->convert); + if (this->target == this->follower) { + if (this->io_position) + this->io_rate_match.size = this->io_position->clock.duration; + return spa_node_process(this->follower); } - status = spa_node_process(this->follower); - - if (this->monitor) - status |= SPA_STATUS_HAVE_DATA; + if (this->direction == SPA_DIRECTION_INPUT) { + /* an input node (sink). + * First we run the converter to process the input for the follower + * then if it produced data, we run the follower. */ + while (retry--) { + status = this->convert ? spa_node_process(this->convert) : 0; + /* schedule the follower when the converter needed + * a recycled buffer */ + if (status == -EPIPE || status == 0) + status = SPA_STATUS_HAVE_DATA; + else if (status < 0) + break; + + if (status & (SPA_STATUS_HAVE_DATA | SPA_STATUS_DRAINED)) { + /* as long as the converter produced something or + * is drained, process the follower. */ + fstatus = spa_node_process(this->follower); + if (fstatus < 0) { + status = fstatus; + break; + } + /* if the follower doesn't need more data or is + * drained we can stop */ + if ((fstatus & SPA_STATUS_NEED_DATA) == 0 || + (fstatus & SPA_STATUS_DRAINED)) + break; + } + /* the converter needs more data */ + if ((status & SPA_STATUS_NEED_DATA)) + break; + } + } else if (!this->driver) { + bool done = false; + while (retry--) { + /* output node (source). First run the converter to make + * sure we push out any queued data. Then when it needs + * more data, schedule the follower. */ + status = this->convert ? spa_node_process(this->convert) : 0; + if (status == 0) + status = SPA_STATUS_NEED_DATA; + else if (status < 0) + break; + + done = (status & (SPA_STATUS_HAVE_DATA | SPA_STATUS_DRAINED)); + + /* when not async, we can return the data when we are done. + * In async mode we might first need to wake up the follower + * to asynchronously provide more data for the next round. */ + if (!this->async && done) + break; + + if (status & SPA_STATUS_NEED_DATA) { + /* the converter needs more data, schedule the + * follower */ + fstatus = spa_node_process(this->follower); + if (fstatus < 0) { + status = fstatus; + break; + } + /* if the follower didn't produce more data or is + * not drained we can stop now */ + if ((fstatus & (SPA_STATUS_HAVE_DATA | SPA_STATUS_DRAINED)) == 0) + break; + } + /* converter produced something or is drained and we + * scheduled the follower above, we can stop now*/ + if (done) + break; + } + if (!done) + spa_node_call_xrun(&this->callbacks, 0, 0, NULL); - if (this->direction == SPA_DIRECTION_OUTPUT && !this->driving) { - if (this->use_converter) - status = spa_node_process(this->convert); + } else { + status = spa_node_process(this->follower); } + spa_log_trace_fp(this->log, "%p: process status:%d", this, status); + + this->driver = false; + return status; } @@ -834,6 +1535,9 @@ static int impl_clear(struct spa_handle *handle) spa_hook_remove(&this->follower_listener); spa_node_set_callbacks(this->follower, NULL, NULL); + if (this->hnd_convert) + spa_handle_clear(this->hnd_convert); + if (this->buffers) free(this->buffers); this->buffers = NULL; @@ -841,7 +1545,6 @@ static int impl_clear(struct spa_handle *handle) return 0; } -extern const struct spa_handle_factory spa_videoconvert_factory; static size_t impl_get_size(const struct spa_handle_factory *factory, @@ -879,23 +1582,27 @@ impl_init(const struct spa_handle_factory *factory, this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + spa_log_topic_init(this->log, log_topic); + + this->cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); - if (info == NULL || (str = spa_dict_lookup(info, "video.adapt.follower")) == NULL) + if (info == NULL || + (str = spa_dict_lookup(info, "video.adapt.follower")) == NULL) return -EINVAL; sscanf(str, "pointer:%p", &this->follower); if (this->follower == NULL) return -EINVAL; - spa_node_add_listener(this->follower, - &this->follower_listener, &follower_node_events, this); - spa_node_set_callbacks(this->follower, &follower_node_callbacks, this); + if (this->cpu) + this->max_align = spa_cpu_get_max_align(this->cpu); + + spa_hook_list_init(&this->hooks); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); - spa_hook_list_init(&this->hooks); #if 0 this->hnd_convert = SPA_PTROFF(this, sizeof(struct impl), struct spa_handle); @@ -905,31 +1612,36 @@ impl_init(const struct spa_handle_factory *factory, spa_handle_get_interface(this->hnd_convert, SPA_TYPE_INTERFACE_Node, &iface); this->convert = iface; +#endif this->target = this->convert; - spa_node_add_listener(this->convert, - &this->convert_listener, &target_node_events, this); - this->use_converter = true; - link_io(this); + this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | + SPA_NODE_CHANGE_MASK_PARAMS; + this->info = SPA_NODE_INFO_INIT(); + this->info.flags = SPA_NODE_FLAG_RT | + SPA_NODE_FLAG_NEED_CONFIGURE; + this->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); + this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); + this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); + this->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + this->params[IDX_EnumPortConfig] = SPA_PARAM_INFO(SPA_PARAM_EnumPortConfig, SPA_PARAM_INFO_READ); + this->params[IDX_PortConfig] = SPA_PARAM_INFO(SPA_PARAM_PortConfig, SPA_PARAM_INFO_READWRITE); + this->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); + this->params[IDX_ProcessLatency] = SPA_PARAM_INFO(SPA_PARAM_ProcessLatency, SPA_PARAM_INFO_READWRITE); + this->info.params = this->params; + this->info.n_params = N_NODE_PARAMS; -#else - this->target = this->follower; - spa_node_add_listener(this->target, - &this->target_listener, &target_node_events, this); + spa_node_add_listener(this->follower, + &this->follower_listener, &follower_node_events, this); + spa_node_set_callbacks(this->follower, &follower_node_callbacks, this); -#endif + if (this->convert) + spa_node_add_listener(this->convert, + &this->convert_listener, &convert_node_events, this); - this->info_all = SPA_NODE_CHANGE_MASK_PARAMS; - this->info = SPA_NODE_INFO_INIT(); - this->info.max_input_ports = 0; - this->info.max_output_ports = 0; - this->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); - this->params[1] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); - this->params[2] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); - this->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READ); - this->params[4] = SPA_PARAM_INFO(SPA_PARAM_PortConfig, SPA_PARAM_INFO_WRITE); - this->info.params = this->params; - this->info.n_params = 5; + reconfigure_mode(this, true, this->direction, NULL); + + link_io(this); return 0; } diff --git a/spa/plugins/volume/volume.c b/spa/plugins/volume/volume.c index 40556a33c6ffd1e4154cb6944d0501b38ba8a37a..fe6fcc4f4f9e09787baaacd4ab4d9df616cec8ed 100644 --- a/spa/plugins/volume/volume.c +++ b/spa/plugins/volume/volume.c @@ -39,6 +39,9 @@ #define NAME "volume" +#define DEFAULT_RATE 48000 +#define DEFAULT_CHANNELS 2 + #define DEFAULT_VOLUME 1.0 #define DEFAULT_MUTE false @@ -322,8 +325,10 @@ static int port_enum_formats(void *object, SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_S32), - SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(44100, 1, INT32_MAX), - SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(2, 1, INT32_MAX)); + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int( + DEFAULT_RATE, 1, INT32_MAX), + SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int( + DEFAULT_CHANNELS, 1, INT32_MAX)); break; default: return 0; diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index 5ac3cb6be336d48c8e8fe42cf7e952c7bb53824d..42caf780bcd01fd4a4a2c190f6db9811fcbb2446 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -402,7 +402,7 @@ static void capture_process(void *data) pw_stream_queue_buffer(impl->capture, buf); } -static void input_state_changed(void *data, enum pw_stream_state old, +static void capture_state_changed(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct impl *impl = data; @@ -423,6 +423,44 @@ static void input_state_changed(void *data, enum pw_stream_state old, } } +static void source_state_changed(void *data, enum pw_stream_state old, + enum pw_stream_state state, const char *error) +{ + struct impl *impl = data; + int res; + + switch (state) { + case PW_STREAM_STATE_PAUSED: + pw_stream_flush(impl->source, false); + pw_stream_flush(impl->capture, false); + + if (old == PW_STREAM_STATE_STREAMING) { + pw_log_debug("%p: deactivate %s", impl, impl->aec->name); + res = spa_audio_aec_deactivate(impl->aec); + if (res < 0 && res != -EOPNOTSUPP) { + pw_log_error("aec plugin %s deactivate failed: %s", impl->aec->name, spa_strerror(res)); + } + } + break; + case PW_STREAM_STATE_STREAMING: + pw_log_debug("%p: activate %s", impl, impl->aec->name); + res = spa_audio_aec_activate(impl->aec); + if (res < 0 && res != -EOPNOTSUPP) { + pw_log_error("aec plugin %s activate failed: %s", impl->aec->name, spa_strerror(res)); + } + break; + case PW_STREAM_STATE_UNCONNECTED: + pw_log_info("%p: input unconnected", impl); + pw_impl_module_schedule_destroy(impl->module); + break; + case PW_STREAM_STATE_ERROR: + pw_log_info("%p: input error: %s", impl, error); + break; + default: + break; + } +} + static void input_param_latency_changed(struct impl *impl, const struct spa_pod *param) { struct spa_latency_info latency; @@ -455,7 +493,7 @@ static void input_param_changed(void *data, uint32_t id, const struct spa_pod *p static const struct pw_stream_events capture_events = { PW_VERSION_STREAM_EVENTS, .destroy = capture_destroy, - .state_changed = input_state_changed, + .state_changed = capture_state_changed, .process = capture_process, .param_changed = input_param_changed }; @@ -470,7 +508,7 @@ static void source_destroy(void *d) static const struct pw_stream_events source_events = { PW_VERSION_STREAM_EVENTS, .destroy = source_destroy, - .state_changed = input_state_changed, + .state_changed = source_state_changed, .param_changed = input_param_changed }; @@ -976,7 +1014,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) handle = spa_plugin_loader_load(impl->loader, SPA_NAME_AEC, &info); if (handle == NULL) { - pw_log_error("AEC codec plugin %s not available library.name %s", SPA_NAME_AEC, path); + pw_log_error("aec plugin %s not available library.name %s", SPA_NAME_AEC, path); return -ENOENT; } @@ -986,14 +1024,16 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) } impl->aec = iface; impl->spa_handle = handle; - if (impl->aec->iface.version != SPA_VERSION_AUDIO_AEC) { - pw_log_error("codec plugin %s has incompatible ABI version (%d != %d)", + + if (impl->aec->iface.version > SPA_VERSION_AUDIO_AEC) { + pw_log_error("codec plugin %s has incompatible ABI version (%d > %d)", SPA_NAME_AEC, impl->aec->iface.version, SPA_VERSION_AUDIO_AEC); res = -ENOENT; goto error; } - pw_log_info("Using plugin AEC %s", impl->aec->name); + pw_log_info("Using plugin AEC %s with version %d", impl->aec->name, + impl->aec->iface.version); if ((str = pw_properties_get(props, "aec.args")) != NULL) aec_props = pw_properties_new_string(str); @@ -1005,7 +1045,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_properties_free(aec_props); if (res < 0) { - pw_log_error("codec plugin %s create failed: %s", impl->aec->name, + pw_log_error("aec plugin %s create failed: %s", impl->aec->name, spa_strerror(res)); goto error; } diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index d63567407269d719dd2348b888640fe1ac085617..bacedf6ef75f199973fb0bd4725cb3746367c217 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -682,11 +682,21 @@ static struct port *find_port(struct node *node, const char *name, int descripto str = strdupa(name); col = strchr(str, ':'); if (col != NULL) { + struct node *find; node_name = str; port_name = col + 1; *col = '\0'; - node = find_node(node->graph, node_name); - } else { + find = find_node(node->graph, node_name); + if (find == NULL) { + /* it's possible that the : is part of the port name, + * try again without splitting things up. */ + *col = ':'; + col = NULL; + } else { + node = find; + } + } + if (col == NULL) { node_name = node->name; port_name = str; } @@ -838,7 +848,7 @@ static int set_control_value(struct node *node, const char *name, float *value) port = find_port(node, name, FC_PORT_INPUT | FC_PORT_CONTROL); if (port == NULL) - return 0; + return -ENOENT; node = port->node; desc = node->desc; @@ -853,7 +863,7 @@ static int parse_params(struct graph *graph, const struct spa_pod *pod) { struct spa_pod_parser prs; struct spa_pod_frame f; - int changed = 0; + int res, changed = 0; struct node *def_node; def_node = spa_list_first(&graph->node_list, struct node, link); @@ -886,7 +896,8 @@ static int parse_params(struct graph *graph, const struct spa_pod *pod) struct spa_pod *pod; spa_pod_parser_get_pod(&prs, &pod); } - changed += set_control_value(def_node, name, val); + if ((res = set_control_value(def_node, name, val)) > 0) + changed += res; } return changed; } @@ -1338,15 +1349,17 @@ static int parse_control(struct node *node, struct spa_json *control) while (spa_json_get_string(control, key, sizeof(key)) > 0) { float fl; const char *val; - int len; + int res, len; if ((len = spa_json_next(control, &val)) < 0) break; - if (spa_json_parse_float(val, len, &fl) <= 0) + if (spa_json_parse_float(val, len, &fl) <= 0) { pw_log_warn("control '%s' expects a number, ignoring", key); - else - set_control_value(node, key, &fl); + } + else if ((res = set_control_value(node, key, &fl)) < 0) { + pw_log_warn("control '%s' can not be set: %s", key, spa_strerror(res)); + } } return 0; } diff --git a/src/modules/module-filter-chain/lv2_plugin.c b/src/modules/module-filter-chain/lv2_plugin.c index 9f655700792e284fac69b94b4b6a1ba0b3827c13..fc4f274bc180ad1b6115364f808e1a6c08a32976 100644 --- a/src/modules/module-filter-chain/lv2_plugin.c +++ b/src/modules/module-filter-chain/lv2_plugin.c @@ -35,11 +35,27 @@ #include <pipewire/array.h> #include <lilv/lilv.h> -#include <lv2/lv2plug.in/ns/ext/atom/atom.h> -#include <lv2/lv2plug.in/ns/ext/buf-size/buf-size.h> -#include <lv2/lv2plug.in/ns/ext/worker/worker.h> -#include <lv2/lv2plug.in/ns/ext/options/options.h> -#include <lv2/lv2plug.in/ns/ext/parameters/parameters.h> + +#if defined __has_include +# if __has_include (<lv2/atom/atom.h>) + + #include <lv2/atom/atom.h> + #include <lv2/buf-size/buf-size.h> + #include <lv2/worker/worker.h> + #include <lv2/options/options.h> + #include <lv2/parameters/parameters.h> + +# else + + #include <lv2/lv2plug.in/ns/ext/atom/atom.h> + #include <lv2/lv2plug.in/ns/ext/buf-size/buf-size.h> + #include <lv2/lv2plug.in/ns/ext/worker/worker.h> + #include <lv2/lv2plug.in/ns/ext/options/options.h> + #include <lv2/lv2plug.in/ns/ext/parameters/parameters.h> + +# endif + +#endif #include "plugin.h" diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index 88462a61eb6499f21d710b03eb2bb08c147e58a3..7cd07ded5bcbb991c4e21a3e0c197038824748c2 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -519,10 +519,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); - if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL) - pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, - "loopback-%u-%u", pid, id); - if ((str = pw_properties_get(props, "capture.props")) != NULL) pw_properties_update_string(impl->capture_props, str, strlen(str)); if ((str = pw_properties_get(props, "playback.props")) != NULL) @@ -549,6 +545,10 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (pw_properties_get(impl->playback_props, PW_KEY_NODE_NAME) == NULL) pw_properties_setf(impl->playback_props, PW_KEY_NODE_NAME, "output.%s", str); + if (pw_properties_get(impl->capture_props, PW_KEY_NODE_DESCRIPTION) == NULL) + pw_properties_set(impl->capture_props, PW_KEY_NODE_DESCRIPTION, str); + if (pw_properties_get(impl->playback_props, PW_KEY_NODE_DESCRIPTION) == NULL) + pw_properties_set(impl->playback_props, PW_KEY_NODE_DESCRIPTION, str); parse_audio_info(impl->capture_props, &impl->capture_info); parse_audio_info(impl->playback_props, &impl->playback_info); diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index ff7e1e9957075f3c808cde17310cd63a5515dab6..b403aba2140b893be7f3bf7ed9ff86032aa8a943 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -87,6 +87,9 @@ #define DEFAULT_POSITION "[ FL FR ]" #define MAX_FORMATS 32 +/* The max amount of data we send in one block when capturing. In PulseAudio this + * size is derived from the mempool PA_MEMPOOL_SLOT_SIZE */ +#define MAX_FRAGSIZE (64*1024) #define TEMPORARY_MOVE_TIMEOUT (SPA_NSEC_PER_SEC) @@ -442,7 +445,15 @@ static uint32_t frac_to_bytes_round_up(struct spa_fraction val, const struct sam return (uint32_t) u; } -static uint32_t fix_playback_buffer_attr(struct stream *s, struct buffer_attr *attr) +static void clamp_latency(struct stream *s, struct spa_fraction *lat) +{ + if (lat->num * s->min_quantum.denom / lat->denom < s->min_quantum.num) + lat->num = (s->min_quantum.num * lat->denom + + (s->min_quantum.denom -1)) / s->min_quantum.denom; +} + +static uint64_t fix_playback_buffer_attr(struct stream *s, struct buffer_attr *attr, + uint32_t rate, struct spa_fraction *lat) { uint32_t frame_size, max_prebuf, minreq, latency, max_latency; struct defs *defs = &s->impl->defs; @@ -521,11 +532,15 @@ static uint32_t fix_playback_buffer_attr(struct stream *s, struct buffer_attr *a attr->fragsize = 0; - pw_log_info("[%s] maxlength:%u tlength:%u minreq:%u/%u prebuf:%u latency:%u %u", + lat->num = latency / frame_size; + lat->denom = rate; + clamp_latency(s, lat); + + pw_log_info("[%s] maxlength:%u tlength:%u minreq:%u/%u prebuf:%u latency:%u/%u %u", s->client->name, attr->maxlength, attr->tlength, - attr->minreq, minreq, attr->prebuf, latency, frame_size); + attr->minreq, minreq, attr->prebuf, lat->num, lat->denom, frame_size); - return latency / frame_size; + return lat->num * SPA_USEC_PER_SEC / lat->denom; } static uint64_t set_playback_buffer_attr(struct stream *s, struct buffer_attr *attr) @@ -539,16 +554,10 @@ static uint64_t set_playback_buffer_attr(struct stream *s, struct buffer_attr *a char attr_prebuf[32]; char attr_minreq[32]; - lat.denom = s->ss.rate; - lat.num = fix_playback_buffer_attr(s, attr); + lat_usec = fix_playback_buffer_attr(s, attr, s->ss.rate, &lat); s->attr = *attr; - if (lat.num * s->min_quantum.denom / lat.denom < s->min_quantum.num) - lat.num = (s->min_quantum.num * lat.denom + - (s->min_quantum.denom -1)) / s->min_quantum.denom; - lat_usec = lat.num * SPA_USEC_PER_SEC / lat.denom; - snprintf(latency, sizeof(latency), "%u/%u", lat.num, lat.denom); snprintf(rate, sizeof(rate), "1/%u", lat.denom); snprintf(attr_maxlength, sizeof(attr_maxlength), "%u", s->attr.maxlength); @@ -643,7 +652,8 @@ static int reply_create_playback_stream(struct stream *stream, struct pw_manager return client_queue_message(client, reply); } -static uint32_t fix_record_buffer_attr(struct stream *s, struct buffer_attr *attr) +static uint64_t fix_record_buffer_attr(struct stream *s, struct buffer_attr *attr, + uint32_t rate, struct spa_fraction *lat) { uint32_t frame_size, minfrag, latency; @@ -652,8 +662,9 @@ static uint32_t fix_record_buffer_attr(struct stream *s, struct buffer_attr *att if (frame_size == 0) frame_size = 4; - pw_log_info("[%s] maxlength:%u fragsize:%u", - s->client->name, attr->maxlength, attr->fragsize); + pw_log_info("[%s] maxlength:%u fragsize:%u framesize:%u", + s->client->name, attr->maxlength, attr->fragsize, + frame_size); if (attr->maxlength == (uint32_t) -1 || attr->maxlength > MAXLENGTH) attr->maxlength = MAXLENGTH; @@ -668,25 +679,23 @@ static uint32_t fix_record_buffer_attr(struct stream *s, struct buffer_attr *att attr->fragsize = SPA_ROUND_UP(attr->fragsize, frame_size); attr->fragsize = SPA_MAX(attr->fragsize, minfrag); - /* pulseaudio configures the source to half of the fragsize. It also - * immediately sends chunks to clients. Configure a 2/3 of the fragsize - * as the latency. */ - latency = attr->fragsize * 2 / 3; - - if (s->adjust_latency) - attr->fragsize = SPA_ROUND_UP(latency, frame_size); - - attr->tlength = attr->prebuf = 0; + attr->tlength = attr->minreq = attr->prebuf = 0; /* make sure can queue at least to fragsize without overruns */ if (attr->maxlength < attr->fragsize * 4) attr->maxlength = attr->fragsize * 4; - pw_log_info("[%s] maxlength:%u fragsize:%u minfrag:%u latency:%u", + latency = attr->fragsize / frame_size; + + lat->num = latency; + lat->denom = rate; + clamp_latency(s, lat); + + pw_log_info("[%s] maxlength:%u fragsize:%u minfrag:%u latency:%u/%u", s->client->name, attr->maxlength, attr->fragsize, minfrag, - latency); + lat->num, lat->denom); - return latency / frame_size; + return lat->num * SPA_USEC_PER_SEC / lat->denom; } static uint64_t set_record_buffer_attr(struct stream *s, struct buffer_attr *attr) @@ -698,13 +707,9 @@ static uint64_t set_record_buffer_attr(struct stream *s, struct buffer_attr *att struct spa_fraction lat; uint64_t lat_usec; - lat.denom = s->ss.rate; - lat.num = fix_record_buffer_attr(s, &s->attr); + lat_usec = fix_record_buffer_attr(s, attr, s->ss.rate, &lat); - if (lat.num * s->min_quantum.denom / lat.denom < s->min_quantum.num) - lat.num = (s->min_quantum.num * lat.denom + - (s->min_quantum.denom -1)) / s->min_quantum.denom; - lat_usec = lat.num * SPA_USEC_PER_SEC / lat.denom; + s->attr = *attr; snprintf(latency, sizeof(latency), "%u/%u", lat.num, lat.denom); snprintf(rate, sizeof(rate), "1/%u", lat.denom); @@ -1336,7 +1341,8 @@ do_process_done(struct spa_loop *loop, pw_log_trace("avail:%d index:%u", avail, index); while ((uint32_t)avail >= stream->attr.fragsize) { - towrite = SPA_MIN((uint32_t)avail, stream->attr.fragsize); + towrite = SPA_MIN(avail, MAX_FRAGSIZE); + towrite = SPA_ROUND_DOWN(towrite, stream->frame_size); msg = message_alloc(impl, stream->channel, towrite); if (msg == NULL) @@ -1732,9 +1738,11 @@ static int do_create_playback_stream(struct client *client, uint32_t command, ui stream->underrun_for = -1; if (rate != 0) { + struct spa_fraction lat; + fix_playback_buffer_attr(stream, &attr, rate, &lat); pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", rate); pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", - fix_playback_buffer_attr(stream, &attr), rate); + lat.num, lat.denom); } if (no_remix) pw_properties_set(props, PW_KEY_STREAM_DONT_REMIX, "true"); @@ -1993,9 +2001,11 @@ static int do_create_record_stream(struct client *client, uint32_t command, uint no_move = false; if (rate != 0) { + struct spa_fraction lat; + fix_record_buffer_attr(stream, &attr, rate, &lat); pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", rate); pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", - fix_record_buffer_attr(stream, &attr), rate); + lat.num, lat.denom); } if (peak_detect) pw_properties_set(props, PW_KEY_STREAM_MONITOR, "true"); @@ -2717,11 +2727,17 @@ static int do_flush_trigger_prebuf_stream(struct client *client, uint32_t comman break; case COMMAND_TRIGGER_PLAYBACK_STREAM: case COMMAND_PREBUF_PLAYBACK_STREAM: + if (stream->type != STREAM_TYPE_PLAYBACK) + return -ENOENT; + if (command == COMMAND_TRIGGER_PLAYBACK_STREAM) + stream->in_prebuf = false; + else if (stream->attr.prebuf > 0) + stream->in_prebuf = true; + stream_send_request(stream); break; default: return -EINVAL; } - return reply_simple_ack(client, tag); } diff --git a/src/modules/module-protocol-pulse/server.c b/src/modules/module-protocol-pulse/server.c index b363a66bc846b7acd93a4c5ae5b520c9daf83842..af1913b9518033c35924f3229fcee8e018362743 100644 --- a/src/modules/module-protocol-pulse/server.c +++ b/src/modules/module-protocol-pulse/server.c @@ -166,6 +166,8 @@ static int handle_memblock(struct client *client, struct message *msg) index += diff; filled += diff; stream->write_index += diff; + if ((flags & FLAG_SEEKMASK) == SEEK_RELATIVE) + stream->requested -= diff; if (filled < 0) { /* underrun, reported on reader side */ @@ -182,9 +184,10 @@ static int handle_memblock(struct client *client, struct message *msg) msg->data, SPA_MIN(msg->length, MAXLENGTH)); index += msg->length; - stream->write_index += msg->length; spa_ringbuffer_write_update(&stream->ring, index); - stream->requested -= SPA_MIN(msg->length, stream->requested); + + stream->write_index += msg->length; + stream->requested -= msg->length; stream_send_request(stream); diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 83d279f2cc6e35f47596a62bcde9bdf6c39729f7..fe6288341bb3d4304d4e1b3d6e43c46a65ee2213 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -66,10 +66,63 @@ * * Creates a new Sink to stream to an Airplay device. * + * Normally this sink is automatically created with \ref page_module_raop_discover + * with the right parameters but it is possible to manually create a RAOP sink + * as well. + * * ## Module Options * + * Options specific to the behavior of this module + * + * - `raop.hostname`: The hostname of the remote end. + * - `raop.port`: The port of the remote end. + * - `raop.transport`: The data transport to use, one of "udp" or "tcp". Defaults + * to "udp". + * - `raop.encryption.type`: The encryption type to use. One of "none", "RSA" or + * "auth_setup". Default is "none". + * - `raop.audio.codec`: The audio codec to use. Needs to be "PCM". Defaults to "PCM". + * - `raop.password`: The password to use. + * - `stream.props = {}`: properties to be passed to the sink stream + * + * Options with well-known behavior. + * + * - \ref PW_KEY_REMOTE_NAME + * - \ref PW_KEY_AUDIO_FORMAT + * - \ref PW_KEY_AUDIO_RATE + * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_POSITION + * - \ref PW_KEY_NODE_NAME + * - \ref PW_KEY_NODE_DESCRIPTION + * - \ref PW_KEY_NODE_GROUP + * - \ref PW_KEY_NODE_LATENCY + * - \ref PW_KEY_NODE_VIRTUAL + * - \ref PW_KEY_MEDIA_CLASS + * * ## Example configuration * + *\code{.unparsed} + * context.modules = [ + * { name = libpipewire-module-raop-sink + * args = { + * # Set the remote address to tunnel to + * raop.hostname = "my-raop-device" + * raop.port = 8190 + * #raop.transport = "udp" + * raop.encryption = "RSA" + * #raop.audio.codec = "PCM" + * #raop.password = "****" + * #audio.format = "S16" + * #audio.rate = 44100 + * #audio.channels = 22 + * #audio.position = [ FL FR ] + * stream.props = { + * # extra sink properties + * } + * } + * } + * ] + *\endcode + * * ## See also * * \ref page_module_raop_discover @@ -106,7 +159,13 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define DEFAULT_LATENCY (DEFAULT_RATE*2) -#define MODULE_USAGE "[ node.latency=<latency as fraction> ] " \ +#define MODULE_USAGE "[ raop.hostname=<name of host> ] " \ + "[ raop.port=<remote port> ] " \ + "[ raop.transport=<transport, default:udp> ] " \ + "[ raop.encryption.type=<encryption, default:none> ] " \ + "[ raop.audio.codec=PCM ] " \ + "[ raop.password=<password for auth> ] " \ + "[ node.latency=<latency as fraction> ] " \ "[ node.name=<name of the nodes> ] " \ "[ node.description=<description of the nodes> ] " \ "[ audio.format=<format, default:"DEFAULT_FORMAT"> ] " \ @@ -1072,17 +1131,14 @@ static void rtsp_auth_setup_reply(void *data, int status, const struct spa_dict static int rtsp_do_auth_setup(struct impl *impl) { - int res; - - char output[] = + static const unsigned char content[33] = "\x01" "\x59\x02\xed\xe9\x0d\x4e\xf2\xbd\x4c\xb6\x8a\x63\x30\x03\x82\x07" "\xa9\x4d\xbd\x50\xd8\xaa\x46\x5b\x5d\x8c\x01\x2a\x0c\x7e\x1d\x4e"; - res = pw_rtsp_client_url_send(impl->rtsp, "/auth-setup", "POST", &impl->headers->dict, - "application/octet-stream", output, rtsp_auth_setup_reply, impl); - - return res; + return pw_rtsp_client_url_send(impl->rtsp, "/auth-setup", "POST", &impl->headers->dict, + "application/octet-stream", content, sizeof(content), + rtsp_auth_setup_reply, impl); } static const char *find_attr(char **tokens, const char *key) diff --git a/src/modules/module-raop/rtsp-client.c b/src/modules/module-raop/rtsp-client.c index 792b441908b8adaee955f0ed5783a4801f514af4..c731a0469e923e35143eedcfe4ffdbb31155ccdf 100644 --- a/src/modules/module-raop/rtsp-client.c +++ b/src/modules/module-raop/rtsp-client.c @@ -44,11 +44,18 @@ struct message { void *data; size_t len; size_t offset; - int cseq; + uint32_t cseq; void (*reply) (void *user_data, int status, const struct spa_dict *headers); void *user_data; }; +enum client_recv_state { + CLIENT_RECV_NONE, + CLIENT_RECV_STATUS, + CLIENT_RECV_HEADERS, + CLIENT_RECV_CONTENT, +}; + struct pw_rtsp_client { struct pw_loop *loop; struct pw_properties *props; @@ -67,15 +74,15 @@ struct pw_rtsp_client { struct spa_source *source; unsigned int connecting:1; unsigned int need_flush:1; - unsigned int wait_status:1; + enum client_recv_state recv_state; int status; char line_buf[1024]; size_t line_pos; struct pw_properties *headers; + size_t content_length; - char *session; - int cseq; + uint32_t cseq; struct spa_list messages; struct spa_list pending; @@ -102,6 +109,7 @@ struct pw_rtsp_client *pw_rtsp_client_new(struct pw_loop *main_loop, spa_list_init(&client->pending); spa_hook_list_init(&client->listener_list); client->headers = pw_properties_new(NULL, NULL); + client->recv_state = CLIENT_RECV_NONE; pw_log_info("new client %p", client); @@ -145,7 +153,7 @@ int pw_rtsp_client_get_local_ip(struct pw_rtsp_client *client, if (ip) inet_ntop(client->local_addr.sa.sa_family, &client->local_addr.in.sin_addr, ip, len); - } else if (client->local_addr.sa.sa_family == AF_INET6) { + } else if (client->local_addr.sa.sa_family == AF_INET6) { *version = 6; if (ip) inet_ntop(client->local_addr.sa.sa_family, @@ -160,7 +168,7 @@ static int handle_connect(struct pw_rtsp_client *client, int fd) { int res, ip_version; socklen_t len; - char local_ip[INET6_ADDRSTRLEN]; + char local_ip[INET6_ADDRSTRLEN]; len = sizeof(res); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &res, &len) < 0) { @@ -180,13 +188,19 @@ static int handle_connect(struct pw_rtsp_client *client, int fd) if (ip_version == 4) asprintf(&client->url, "rtsp://%s/%s", local_ip, client->session_id); - else + else asprintf(&client->url, "rtsp://[%s]/%s", local_ip, client->session_id); pw_log_info("connected local ip %s", local_ip); client->connecting = false; - client->wait_status = true; + + client->recv_state = CLIENT_RECV_STATUS; + pw_properties_clear(client->headers); + client->status = 0; + client->line_pos = 0; + client->content_length = 0; + pw_rtsp_client_emit_connected(client); return 0; @@ -226,7 +240,7 @@ static int read_line(struct pw_rtsp_client *client, char **buf) return 0; } -static struct message *find_pending(struct pw_rtsp_client *client, int cseq) +static struct message *find_pending(struct pw_rtsp_client *client, uint32_t cseq) { struct message *msg; spa_list_for_each(msg, &client->pending, link) { @@ -236,92 +250,145 @@ static struct message *find_pending(struct pw_rtsp_client *client, int cseq) return NULL; } -static int process_input(struct pw_rtsp_client *client) +static int process_status(struct pw_rtsp_client *client, char *buf) { - char *buf = NULL; - int res; + const char *state = NULL, *s; + size_t len; - if ((res = read_line(client, &buf)) <= 0) - return res; + pw_log_info("status: %s", buf); - pw_log_debug("%s", buf); + s = pw_split_walk(buf, " ", &len, &state); + if (!spa_strstartswith(s, "RTSP/")) + return -EPROTO; - if (client->wait_status) { - const char *state = NULL, *s; - size_t len; + s = pw_split_walk(buf, " ", &len, &state); + if (s == NULL) + return -EPROTO; - pw_log_info("status: %s", buf); + client->status = atoi(s); + if (client->status == 0) + return -EPROTO; - s = pw_split_walk(buf, " ", &len, &state); - if (!spa_strstartswith(s, "RTSP/")) - goto error; + s = pw_split_walk(buf, " ", &len, &state); + if (s == NULL) + return -EPROTO; - s = pw_split_walk(buf, " ", &len, &state); - if (s == NULL) - goto error; + pw_properties_clear(client->headers); + client->recv_state = CLIENT_RECV_HEADERS; - client->status = atoi(s); + return 0; +} - s = pw_split_walk(buf, " ", &len, &state); - if (s == NULL) - goto error; +static void dispatch_handler(struct pw_rtsp_client *client) +{ + uint32_t cseq; + if (pw_properties_fetch_uint32(client->headers, "CSeq", &cseq) < 0) + return; - client->wait_status = false; - pw_properties_clear(client->headers); - } else { - if (strlen(buf) == 0) { - int cseq; - struct message *msg; - const struct spa_dict_item *it; - const char *content_type; - unsigned int content_length; - - spa_dict_for_each(it, &client->headers->dict) - pw_log_info(" %s: %s", it->key, it->value); - - cseq = pw_properties_get_int32(client->headers, "CSeq", 0); - content_type = pw_properties_get(client->headers, "Content-Type"); - if (content_type != NULL && strcmp(content_type, "application/octet-stream") == 0) { - pw_log_info("binary response received"); - content_length = pw_properties_get_uint64(client->headers, "Content-Length", 0); - char content_buf[content_length]; - res = read(client->source->fd, content_buf, content_length); - pw_log_debug("read %d bytes", res); - if (res == 0) - return -EPIPE; - if (res < 0) { - res = -errno; - if (res != -EAGAIN && res != -EWOULDBLOCK) - return res; - return 0; - } - pw_properties_set(client->headers, "body", content_buf); - } - if ((msg = find_pending(client, cseq)) != NULL) { - msg->reply(msg->user_data, client->status, &client->headers->dict); - spa_list_remove(&msg->link); - free(msg); - } else { - pw_rtsp_client_emit_message(client, client->status, - &client->headers->dict); - } - client->wait_status = true; - } else { - char *key, *value; + pw_log_info("received reply to request with cseq:%" PRIu32, cseq); - key = buf; - value = strstr(buf, ":"); - if (value == NULL) - goto error; - *value++ = '\0'; - while (*value == ' ') - value++; - pw_properties_set(client->headers, key, value); + struct message *msg = find_pending(client, cseq); + if (msg) { + msg->reply(msg->user_data, client->status, &client->headers->dict); + spa_list_remove(&msg->link); + free(msg); + } + else { + pw_rtsp_client_emit_message(client, client->status, &client->headers->dict); + } +} + +static void process_received_message(struct pw_rtsp_client *client) +{ + client->recv_state = CLIENT_RECV_STATUS; + dispatch_handler(client); +} + +static int process_header(struct pw_rtsp_client *client, char *buf) +{ + if (strlen(buf) > 0) { + char *key = buf, *value; + + value = strstr(buf, ":"); + if (value == NULL) + return -EPROTO; + + *value++ = '\0'; + while (*value == ' ') + value++; + + pw_properties_set(client->headers, key, value); + } + else { + const struct spa_dict_item *it; + spa_dict_for_each(it, &client->headers->dict) + pw_log_info(" %s: %s", it->key, it->value); + + client->content_length = pw_properties_get_uint32(client->headers, "Content-Length", 0); + if (client->content_length > 0) + client->recv_state = CLIENT_RECV_CONTENT; + else + process_received_message(client); + } + + return 0; +} + +static int process_content(struct pw_rtsp_client *client) +{ + char buf[1024]; + + while (client->content_length > 0) { + const size_t max_recv = SPA_MIN(sizeof(buf), client->content_length); + + ssize_t res = read(client->source->fd, buf, max_recv); + if (res == 0) + return -EPIPE; + + if (res < 0) { + res = -errno; + if (res == -EAGAIN || res == -EWOULDBLOCK) + return 0; + + return res; } + + spa_assert((size_t) res <= client->content_length); + client->content_length -= res; } + + if (client->content_length == 0) + process_received_message(client); + return 0; -error: - return -EPROTO; +} + +static int process_input(struct pw_rtsp_client *client) +{ + if (client->recv_state == CLIENT_RECV_STATUS || client->recv_state == CLIENT_RECV_HEADERS) { + char *buf = NULL; + int res; + + if ((res = read_line(client, &buf)) <= 0) + return res; + + pw_log_debug("received line: %s", buf); + + switch (client->recv_state) { + case CLIENT_RECV_STATUS: + return process_status(client, buf); + case CLIENT_RECV_HEADERS: + return process_header(client, buf); + default: + spa_assert_not_reached(); + } + } + else if (client->recv_state == CLIENT_RECV_CONTENT) { + return process_content(client); + } + else { + spa_assert_not_reached(); + } } static int flush_output(struct pw_rtsp_client *client) @@ -384,19 +451,19 @@ on_source_io(void *data, int fd, uint32_t mask) if (mask & SPA_IO_IN) { if ((res = process_input(client)) < 0) goto error; - } + } if (mask & SPA_IO_OUT || client->need_flush) { if (client->connecting) { if ((res = handle_connect(client, fd)) < 0) goto error; } res = flush_output(client); - if (res >= 0) { + if (res >= 0) { pw_loop_update_io(client->loop, client->source, client->source->mask & ~SPA_IO_OUT); } else if (res != -EAGAIN) goto error; - } + } done: return; error: @@ -486,7 +553,7 @@ int pw_rtsp_client_disconnect(struct pw_rtsp_client *client) int pw_rtsp_client_url_send(struct pw_rtsp_client *client, const char *url, const char *cmd, const struct spa_dict *headers, - const char *content_type, const char *content, + const char *content_type, const void *content, size_t content_length, void (*reply) (void *user_data, int status, const struct spa_dict *headers), void *user_data) { @@ -494,7 +561,7 @@ int pw_rtsp_client_url_send(struct pw_rtsp_client *client, const char *url, size_t len; const struct spa_dict_item *it; struct message *msg; - int cseq; + uint32_t cseq; if ((f = open_memstream((char**)&msg, &len)) == NULL) return -errno; @@ -504,20 +571,20 @@ int pw_rtsp_client_url_send(struct pw_rtsp_client *client, const char *url, cseq = ++client->cseq; fprintf(f, "%s %s RTSP/1.0\r\n", cmd, url); - fprintf(f, "CSeq: %d\r\n", cseq); + fprintf(f, "CSeq: %" PRIu32 "\r\n", cseq); if (headers != NULL) { spa_dict_for_each(it, headers) fprintf(f, "%s: %s\r\n", it->key, it->value); } if (content_type != NULL && content != NULL) { - fprintf(f, "Content-Type: %s\r\nContent-Length: %d\r\n", - content_type, (int)strlen(content)); + fprintf(f, "Content-Type: %s\r\nContent-Length: %zu\r\n", + content_type, content_length); } fprintf(f, "\r\n"); if (content_type && content) - fprintf(f, "%s", content); + fwrite(content, 1, content_length, f); fclose(f); @@ -534,7 +601,7 @@ int pw_rtsp_client_url_send(struct pw_rtsp_client *client, const char *url, if (client->source && !(client->source->mask & SPA_IO_OUT)) { pw_loop_update_io(client->loop, client->source, client->source->mask | SPA_IO_OUT); - } + } return 0; } @@ -544,5 +611,9 @@ int pw_rtsp_client_send(struct pw_rtsp_client *client, void (*reply) (void *user_data, int status, const struct spa_dict *headers), void *user_data) { - return pw_rtsp_client_url_send(client, client->url, cmd, headers, content_type, content, reply, user_data); + const size_t content_length = content ? strlen(content) : 0; + + return pw_rtsp_client_url_send(client, client->url, cmd, headers, + content_type, content, content_length, + reply, user_data); } diff --git a/src/modules/module-raop/rtsp-client.h b/src/modules/module-raop/rtsp-client.h index 1ff13ee59c089d42233f6d4c6111c491a8d95e80..75b1ce6a15795d3166d3678758739a465187efbe 100644 --- a/src/modules/module-raop/rtsp-client.h +++ b/src/modules/module-raop/rtsp-client.h @@ -73,7 +73,7 @@ int pw_rtsp_client_get_local_ip(struct pw_rtsp_client *client, int pw_rtsp_client_url_send(struct pw_rtsp_client *client, const char *url, const char *cmd, const struct spa_dict *headers, - const char *content_type, const char *content, + const char *content_type, const void *content, size_t content_length, void (*reply) (void *user_data, int status, const struct spa_dict *headers), void *user_data); diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c index 0ee3463d29dc4c2701e78d5b04a7dead0858b0c3..5cfd766e40290363faee4d4208cebbee6c3fe8fb 100644 --- a/src/modules/module-rt.c +++ b/src/modules/module-rt.c @@ -976,7 +976,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (set_nice(impl, impl->nice_level, !can_use_rtkit) < 0) use_rtkit = can_use_rtkit; } - set_rlimit(impl); #ifdef HAVE_DBUS impl->use_rtkit = use_rtkit; @@ -1012,6 +1011,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) set_nice(impl, impl->nice_level, true); } #endif + set_rlimit(impl); impl->thread_utils.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_ThreadUtils, diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index 26f817d25908bfba23ea8b553a285e6631d0d514..b0293772927d2cc1affb4835a4338d3b6956f3ed 100644 --- a/src/pipewire/impl-link.c +++ b/src/pipewire/impl-link.c @@ -619,7 +619,7 @@ 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->onode->active) + if (impl->activated || !this->prepared || !impl->inode->added || !impl->onode->active) return 0; if (!impl->io_set) { diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index 55851a68ef40cd77fda5bd2d55ffd3405a7782a5..2f3ec571588ab0b649afc87c3b891405f587a744 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -167,6 +167,7 @@ do_node_remove(struct spa_loop *loop, spa_loop_remove_source(loop, &this->source); remove_node(this); } + this->added = false; return 0; } @@ -210,19 +211,26 @@ static int pause_node(struct pw_impl_node *this) return res; } -static void node_activate(struct pw_impl_node *this) +static void node_activate_outputs(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) { + spa_list_for_each(port, &this->output_ports, link) { struct pw_impl_link *link; - spa_list_for_each(link, &port->links, input_link) + spa_list_for_each(link, &port->links, output_link) pw_impl_link_activate(link); } - spa_list_for_each(port, &this->output_ports, 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, output_link) + spa_list_for_each(link, &port->links, input_link) pw_impl_link_activate(link); } } @@ -232,7 +240,9 @@ static int start_node(struct pw_impl_node *this) struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); int res = 0; - node_activate(this); + /* First activate the outputs so that when the node starts pushing, + * we can process the outputs */ + node_activate_outputs(this); if (impl->pending_state >= PW_NODE_STATE_RUNNING) return 0; @@ -336,6 +346,7 @@ do_node_add(struct spa_loop *loop, spa_loop_add_source(loop, &this->source); add_node(this, driver); } + this->added = true; return 0; } @@ -354,8 +365,11 @@ static void node_update_state(struct pw_impl_node *node, enum pw_node_state stat error = spa_aprintf("Start error: %s", spa_strerror(res)); } } - if (res >= 0) + if (res >= 0) { pw_loop_invoke(node->data_loop, do_node_add, 1, NULL, 0, true, node); + /* now activate the inputs */ + node_activate_inputs(node); + } break; default: break; @@ -1078,6 +1092,12 @@ 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 */ diff --git a/src/pipewire/private.h b/src/pipewire/private.h index 144e21bf1a84d7a0c3a4e9e0a44173ba8b74e1fa..50080079afc5f87be846bbb1fd5735408a31455a 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -704,6 +704,7 @@ struct pw_impl_node { unsigned int transport_sync:1; /**< supports transport sync */ unsigned int current_pending:1; /**< a quantum/rate update is pending */ unsigned int moved:1; /**< the node was moved drivers */ + unsigned int added:1; /**< the node was add to graph */ uint32_t port_user_data_size; /**< extra size for port user data */ diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 89c2794e29748bb264a7530b65282c10c0d2083e..ff0e0f84068769766ad7b7ea48d80519798432c4 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -125,12 +125,12 @@ struct stream { uint32_t port_change_mask_all; struct spa_port_info port_info; struct pw_properties *port_props; -#define IDX_EnumFormat 0 -#define IDX_Meta 1 -#define IDX_IO 2 -#define IDX_Format 3 -#define IDX_Buffers 4 -#define IDX_Latency 5 +#define PORT_EnumFormat 0 +#define PORT_Meta 1 +#define PORT_IO 2 +#define PORT_Format 3 +#define PORT_Buffers 4 +#define PORT_Latency 5 #define N_PORT_PARAMS 6 struct spa_param_info port_params[N_PORT_PARAMS]; @@ -138,9 +138,11 @@ struct stream { uint32_t change_mask_all; struct spa_node_info info; -#define IDX_PropInfo 0 -#define IDX_Props 1 -#define N_NODE_PARAMS 2 +#define NODE_PropInfo 0 +#define NODE_Props 1 +#define NODE_EnumFormat 2 +#define NODE_Format 3 +#define N_NODE_PARAMS 4 struct spa_param_info params[N_NODE_PARAMS]; uint32_t media_type; @@ -179,9 +181,13 @@ static int get_param_index(uint32_t id) { switch (id) { case SPA_PARAM_PropInfo: - return IDX_PropInfo; + return NODE_PropInfo; case SPA_PARAM_Props: - return IDX_Props; + return NODE_Props; + case SPA_PARAM_EnumFormat: + return NODE_EnumFormat; + case SPA_PARAM_Format: + return NODE_Format; default: return -1; } @@ -191,17 +197,17 @@ static int get_port_param_index(uint32_t id) { switch (id) { case SPA_PARAM_EnumFormat: - return IDX_EnumFormat; + return PORT_EnumFormat; case SPA_PARAM_Meta: - return IDX_Meta; + return PORT_Meta; case SPA_PARAM_IO: - return IDX_IO; + return PORT_IO; case SPA_PARAM_Format: - return IDX_Format; + return PORT_Format; case SPA_PARAM_Buffers: - return IDX_Buffers; + return PORT_Buffers; case SPA_PARAM_Latency: - return IDX_Latency; + return PORT_Latency; default: return -1; } @@ -267,7 +273,8 @@ static struct param *add_param(struct stream *impl, impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; impl->params[idx].flags |= SPA_PARAM_INFO_READ; impl->params[idx].user++; - } else if ((idx = get_port_param_index(id)) != -1) { + } + if ((idx = get_port_param_index(id)) != -1) { impl->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; impl->port_params[idx].flags |= SPA_PARAM_INFO_READ; impl->port_params[idx].user++; @@ -867,6 +874,7 @@ static int impl_port_set_param(void *object, if (stream->state == PW_STREAM_STATE_ERROR) return -EIO; + emit_node_info(impl, false); emit_port_info(impl, false); return 0; @@ -1835,8 +1843,10 @@ pw_stream_connect(struct pw_stream *stream, if (!impl->process_rt) impl->info.flags |= SPA_NODE_FLAG_ASYNC; impl->info.props = &stream->properties->dict; - impl->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, 0); - impl->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_WRITE); + impl->params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, 0); + impl->params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_WRITE); + impl->params[NODE_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, 0); + impl->params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); impl->info.params = impl->params; impl->info.n_params = N_NODE_PARAMS; impl->info.change_mask = impl->change_mask_all; @@ -1851,12 +1861,12 @@ pw_stream_connect(struct pw_stream *stream, impl->port_info.flags = 0; if (SPA_FLAG_IS_SET(flags, PW_STREAM_FLAG_ALLOC_BUFFERS)) impl->port_info.flags |= SPA_PORT_FLAG_CAN_ALLOC_BUFFERS; - impl->port_params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, 0); - impl->port_params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, 0); - impl->port_params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, 0); - impl->port_params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - impl->port_params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - impl->port_params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_WRITE); + impl->port_params[PORT_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, 0); + impl->port_params[PORT_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, 0); + impl->port_params[PORT_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, 0); + impl->port_params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + impl->port_params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + impl->port_params[PORT_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_WRITE); impl->port_info.props = &impl->port_props->dict; impl->port_info.params = impl->port_params; impl->port_info.n_params = N_PORT_PARAMS; diff --git a/src/tools/pw-top.c b/src/tools/pw-top.c index 3fb25972702dc455702075043d297ba6575ce24b..459dacb11fcf5e774d25b64485b8cfad7713cbaa 100644 --- a/src/tools/pw-top.c +++ b/src/tools/pw-top.c @@ -32,10 +32,14 @@ #include <spa/utils/string.h> #include <spa/pod/parser.h> #include <spa/debug/pod.h> +#include <spa/param/format-utils.h> +#include <spa/param/audio/format-utils.h> +#include <spa/param/video/format-utils.h> #include <pipewire/impl.h> #include <pipewire/extensions/profiler.h> +#define MAX_FORMAT 16 #define MAX_NAME 128 struct driver { @@ -59,13 +63,17 @@ struct measurement { struct node { struct spa_list link; uint32_t id; - char name[MAX_NAME]; + char name[MAX_NAME+1]; struct measurement measurement; struct driver info; struct node *driver; uint32_t errors; int32_t last_error_status; uint32_t generation; + char format[MAX_FORMAT+1]; + struct pw_proxy *proxy; + struct spa_hook proxy_listener; + struct spa_hook object_listener; }; struct data { @@ -131,6 +139,98 @@ static struct node *find_node(struct data *d, uint32_t id) return NULL; } +static void on_node_removed(void *data) +{ + struct node *n = data; + pw_proxy_destroy(n->proxy); +} + +static void on_node_destroy(void *data) +{ + struct node *n = data; + n->proxy = NULL; + spa_hook_remove(&n->proxy_listener); + spa_hook_remove(&n->object_listener); +} + +static const struct pw_proxy_events proxy_events = { + PW_VERSION_PROXY_EVENTS, + .removed = on_node_removed, + .destroy = on_node_destroy, +}; + +static void node_param(void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + const struct spa_pod *param) +{ + struct node *n = data; + + switch (id) { + case SPA_PARAM_Format: + { + uint32_t media_type, media_subtype; + + spa_format_parse(param, &media_type, &media_subtype); + + switch(media_type) { + case SPA_MEDIA_TYPE_audio: + switch(media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + { + struct spa_audio_info_raw info; + if (spa_format_audio_raw_parse(param, &info) >= 0) { + snprintf(n->format, sizeof(n->format), "%6.6s %d %d", + spa_debug_type_find_short_name(spa_type_audio_format, info.format), + info.channels, info.rate); + } + break; + } + case SPA_MEDIA_SUBTYPE_dsd: + { + struct spa_audio_info_dsd info; + if (spa_format_audio_dsd_parse(param, &info) >= 0) { + snprintf(n->format, sizeof(n->format), "DSD%d %d ", + 8 * info.rate / 44100, info.channels); + + } + break; + } + } + break; + case SPA_MEDIA_TYPE_video: + switch(media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + { + struct spa_video_info_raw info; + if (spa_format_video_raw_parse(param, &info) >= 0) { + snprintf(n->format, sizeof(n->format), "%6.6s %dx%d", + spa_debug_type_find_short_name(spa_type_video_format, info.format), + info.size.width, info.size.height); + } + break; + } + } + break; + case SPA_MEDIA_TYPE_application: + switch(media_subtype) { + case SPA_MEDIA_SUBTYPE_control: + snprintf(n->format, sizeof(n->format), "%s", "CONTROL"); + break; + } + break; + } + break; + } + default: + break; + } +} + +static const struct pw_node_events node_events = { + PW_VERSION_NODE, + .param = node_param, +}; + static struct node *add_node(struct data *d, uint32_t id, const char *name) { struct node *n; @@ -139,11 +239,23 @@ static struct node *add_node(struct data *d, uint32_t id, const char *name) return NULL; if (name) - strncpy(n->name, name, MAX_NAME-1); + strncpy(n->name, name, MAX_NAME); else snprintf(n->name, sizeof(n->name), "%u", id); n->id = id; n->driver = n; + n->proxy = pw_registry_bind(d->registry, id, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0); + if (n->proxy) { + uint32_t ids[1] = { SPA_PARAM_Format }; + + pw_proxy_add_listener(n->proxy, + &n->proxy_listener, &proxy_events, n); + pw_proxy_add_object_listener(n->proxy, + &n->object_listener, &node_events, n); + + pw_node_subscribe_params((struct pw_node*)n->proxy, + ids, 1); + } spa_list_append(&d->node_list, &n->link); d->n_nodes++; @@ -152,6 +264,8 @@ static struct node *add_node(struct data *d, uint32_t id, const char *name) static void remove_node(struct data *d, struct node *n) { + if (n->proxy) + pw_proxy_destroy(n->proxy); spa_list_remove(&n->link); d->n_nodes--; free(n); @@ -235,7 +349,7 @@ static const char *print_time(char *buf, size_t len, uint64_t val) else if (val == (uint64_t)-2) snprintf(buf, len, " +++ "); else if (val < 1000000llu) - snprintf(buf, len, "%5.1fµs", val/1000.f); + snprintf(buf, len, "%5.1fus", val/1000.f); else if (val < 1000000000llu) snprintf(buf, len, "%5.1fms", val/1000000.f); else @@ -290,7 +404,7 @@ static void print_node(struct data *d, struct driver *i, struct node *n, int y) else busy = -1; - mvwprintw(d->win, y, 0, "%s %4.1u %6.1u %6.1u %s %s %s %s %3.1u %s%s", + mvwprintw(d->win, y, 0, "%s %4.1u %6.1u %6.1u %s %s %s %s %3.1u %16.16s %s%s", n->measurement.status != 3 ? "!" : " ", n->id, frac.num, frac.denom, @@ -299,6 +413,7 @@ static void print_node(struct data *d, struct driver *i, struct node *n, int y) print_perc(buf3, 64, waiting, quantum), print_perc(buf4, 64, busy, quantum), i->xrun_count + n->errors, + n->measurement.status != 3 ? "" : n->format, n->driver == n ? "" : " + ", n->name); } @@ -310,7 +425,7 @@ static void do_refresh(struct data *d) wclear(d->win); wattron(d->win, A_REVERSE); - wprintw(d->win, "%-*.*s", COLS, COLS, "S ID QUANT RATE WAIT BUSY W/Q B/Q ERR NAME "); + wprintw(d->win, "%-*.*s", COLS, COLS, "S ID QUANT RATE WAIT BUSY W/Q B/Q ERR FORMAT NAME "); wattroff(d->win, A_REVERSE); wprintw(d->win, "\n"); @@ -327,6 +442,7 @@ static void do_refresh(struct data *d) f->driver = f; spa_zero(f->measurement); spa_zero(f->info); + spa_zero(f->format); f->errors = 0; f->last_error_status = 0; }