diff --git a/.cirrus.yml b/.cirrus.yml index f99e8dbcc29fd1180ce0da232a07a57258b0bc1b..d26c4431fa1773e1bedad5b37f40734d3d82b174 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,7 +1,7 @@ task: freebsd_instance: matrix: - - image_family: freebsd-13-0-snap + - image_family: freebsd-13-1-snap env: # /usr/ports/Mk/Uses/localbase.mk localbase:ldflags LOCALBASE: /usr/local diff --git a/INSTALL.md b/INSTALL.md index c66af68261e29eca21e0802e023c405ccbcb814e..8969a815b779ec62d96638578d1b25a305617de2 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,8 +1,8 @@ ## Building -PipeWire uses a build tool called *Meson* as a basis for its build +PipeWire uses a build tool called [*Meson*](https://mesonbuild.com) as a basis for its build process. It's a tool with some resemblance to Autotools and CMake. Meson -again generates build files for a lower level build tool called *Ninja*, +again generates build files for a lower level build tool called [*Ninja*](https://ninja-build.org/), working in about the same level of abstraction as more familiar GNU Make does. @@ -39,7 +39,7 @@ section. They are defined in `meson_options.txt`. Finally, invoke the build: ``` -$ ninja -C builddir +$ meson compile -C builddir ``` Just to avoid any confusion: `autogen.sh` is a script invoked by *Jhbuild*, @@ -68,10 +68,10 @@ make run ``` This will use the default config file to configure and start the daemon. -The default config will also start pipewire-media-session, a default -example media session and pipewire-pulse, a PulseAudio compatible server. +The default config will also start `pipewire-media-session`, a default +example media session and `pipewire-pulse`, a PulseAudio compatible server. -You can also enable more debugging with the PIPEWIRE_DEBUG environment +You can also enable more debugging with the `PIPEWIRE_DEBUG` environment variable like so: ``` @@ -92,14 +92,15 @@ systemctl --user stop pipewire.service \ ## Installing -PipeWire comes with quite a bit of libraries and tools, run -inside `builddir`: +PipeWire comes with quite a bit of libraries and tools, run: ``` -sudo meson install +meson install -C builddir ``` to install everything onto the system into the specified prefix. +Depending on the configured installation prefix, the above command +may need to be run with elevated privileges (e.g. with `sudo`). Some additional steps will have to be performed to integrate with the distribution as shown below. @@ -111,7 +112,7 @@ pipewire-pulse process running. PipeWire is usually started as a systemd unit using socket activation or as a service. Configuration of the PipeWire daemon can be found in -/usr/share/pipewire/pipewire.conf. Please refer to the comments in the +`/usr/share/pipewire/pipewire.conf`. Please refer to the comments in the config file for more information about the configuration options. The daemon is started with: @@ -150,14 +151,14 @@ There is also a config file installed in: ``` The plugin will be picked up by alsa when the following files -are in /etc/alsa/conf.d/ +are in `/etc/alsa/conf.d/`: ``` /etc/alsa/conf.d/50-pipewire.conf -> /usr/share/alsa/alsa.conf.d/50-pipewire.conf /etc/alsa/conf.d/99-pipewire-default.conf ``` -With this setup, aplay -l should list a pipewire: device that can be used as +With this setup, `aplay -l` should list a pipewire device that can be used as a regular alsa device for playback and record. ### JACK emulation @@ -180,7 +181,7 @@ These libraries are found here: ``` -The provided pw-jack script uses LD_LIBRARY_PATH to set the library +The provided `pw-jack` script uses `LD_LIBRARY_PATH` to set the library search path to these replacement libraries. This allows you to run jack apps on both the real JACK server or on PipeWire with the script. @@ -193,7 +194,7 @@ contents like: ``` Note that when JACK is replaced by PipeWire, the SPA JACK plugin (installed -in /usr/lib64/spa-0.2/jack/libspa-jack.so) is not useful anymore and +in `/usr/lib64/spa-0.2/jack/libspa-jack.so`) is not useful anymore and distributions should make them conflict. @@ -216,14 +217,21 @@ systemctl --user start pipewire-pulse.service pipewire-pulse.socket ``` You can also start additional PulseAudio servers listening on other -sockets with the -a option. See `pipewire-pulse -h` for more info. +sockets with the `-a` option. See `pipewire-pulse -h` for more info. ## Uninstalling -To uninstall, in the `builddir` directory run: +To uninstall, run: ``` -sudo ninja uninstall +ninja -C builddir uninstall ``` +Depending on the configured installation prefix, the above command +may need to be run with elevated privileges (e.g. with `sudo`). + +Note that at the time of writing uninstallation only works with the +same build directory that was used for installation. Meson stores the +list of installed files in the build directory, and this list is +necessary for uninstallation to work. diff --git a/NEWS b/NEWS index b7a4e1a79016c8a67a75eb872daf4014e7666dc9..eec8787760b81e07aeddfd058993d7261c2f528c 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,326 @@ +# 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 + +## PipeWire + - Support masking of conf.d/ files. (#2629) + - Add some more debug info to memfd. + - Improve data-loop invoke method. Also flush pending items. (#2631) + - Add a filter-chain systemd service file than can be used to start + custom filters placed in ~/.conf/pipewire/filter-chain.d/ (#2553) + - Improve triggered timestamps for remote nodes. + - Fix some potential cross compilation problems due to wrong + 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 + wpctl status, pw-dump, pw-link, pw-top and others. + - Clean up resource in pw-cat correctly on errors. (#2651) + +## Modules + - Fix compilation of AVB on big-endian. Enable AVB only on Linux. + - Use org.freedesktop.portal.Realtime when available. This does the + correct PID/TID mappings to make realtime also work from flatpaks. + - Fix compilation of ROC module when headers are missing. (#2513) + - Improve some error cleanup paths in protocol-native. Improve connect + and disconnect. + - Fix a potential crash in FFT unload in filter-chain. + - Implement PIPEWIRE_NOTIFICATION_FD for notification when the socket + is ready. + - Try to use rtkit if set_nice() fails. + - Fix rate adjustement logic in pulse-tunnel. This would cause + increasing delays and hickups when using tunnels. (#2548) + - Handle disconnect in pulse-tunnel. + +## Bluetooth + - 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. + +## SPA + - Tweak the resampler window function some more. (#2574) + - Improve format convert performance in some fallback cases. + - Fix rounding in format conversion on ARM NEON. + - Fix libcamera build error. (#2575) + - Fix some issues where the wrong samplerate was used. (#2614) + - Don't wait for more samples that can fit in the ringbuffer in ALSA. + - Improve buffer size handling in audioconvert, scale the buffers based + on the rate conversion and make things work with really large rate + conversions as well. + - Add more and better debug for ALSA devices. + - Improve channel mix: Filter FC and LFE when copying from a different + layout. Implement STEREO from FC. Avoid generating REAR from FC in PSD + mode. + - Fix rate match for sources. This fixes an error where follower sources + would generate many resync warnings. + - Improve ALSA format negotiation. If the ALSA node is not running and + there was a previously configured format, close and reopen the device + to enumerate and accept all possible formats again. (#2625). + +## ALSA + - The alsa plugin will now also save the volumes set with the control + API. This saves the volumes set with alsa-mixer, for example. + +## Pulse-server + - Flatpak apps with devices=all (Zoom) will now be granted Manager + permissions. + - Small tweaks to the amount of data sent to clients to work around an + issue in freerdp. + +## JACK + - Clean up the transport correctly when closing a client. (#2569) + - Match context properties in addition to node properties for the jack + client rules. (#2580) + - Make sure to return an error when disconnected from the server. (#2606) + - Fix thread cast problem in jack_client_thread_id(). + - Increase jack_client_name_size() length and make sure we have space for + the \0 byte. + - 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 +0.3.x releases. + +## Highlights + - A critical bug that could crash JACK apps was fixed. + - Some more regressions in audiomixer were fixed. This should fix crackling + and stuttering in some cases as well as some channel mapping regressions. + - A bug in the alsa plugin was fixed that could cause stuttering in VMs. + - Bluetooth sources should have improved latency and rate control. + - Many more bugfixes and improvements. + + +## Modules + - An experimental AVB module was added. It can expose PipeWire as an AVB + entity and initiate (broken) streaming between entities. + - module-loopback now handles the cases where the input and output channels + are different without crashing or producing silence. + - The filter-chain module now correctly calculates the output size without + crashing in some cases. It also skips invalid ports instead of crashing. + - Handle and report pthread errors better. + +## SPA + - The resampler qualities were tweaked a little. + - A bug that would sometimes cut off the last part of a buffer was fixed in + the alsa plugin. This could cause broken audio in VMs. (#2536) + - Access to the alsa mixer and devices is now checked more thoroughly. + (#2534) + - The spa-resample tool can now also handle large downsampling rates without + crashing. + - Audioconverter now uses rounding for float to int conversions, which + reduces distortions. Compilation of the c functions was separated and uses + its own optimization flags now. Unit tests were added. (#2543) + - Noise shaping was improved in audioconvert. A new Wannamaker 3 tap shaper + was added. + - Audioconvert now uses a pattern for generating keep alive noise. This + should have much less energy and be even more inaudible. (#2540) + - A channel mapping bug was fixed in audioconvert. Unit tests were added. + - The dsp audio mixer would sometimes not mix enough and cause dropouts. + (#2525) + +## JACK + - A critical bug in the mixer was fixed. It would cause most JACK apps to + segfault at startup. + +## Bluetooth + - A new rate control algorithm was implemented for the sources. + - The media role on HSP/HFP streams is now fixed. + +## Pulse Server + - Add the resampler delay to delay reporting as well. + + +# PipeWire 0.3.55 (2022-07-12) + +This is a quick bugfix release that is API and ABI compatible with previous +0.3.x releases. + +## Highlights + - Fix some more critical bugs in the new audioconvert and the queueing + in pw-stream that causes stuttering and hickups. + - HFP hardware volumes are now saved and restored. + - Format conversions and mixing was improved. + - Small bug fixes and improvements. + +## PipeWire + - The queueing in pw-stream was improved with support for buffer prefetch + in async mode. + - Add a pw-filter unit test. + +## tools + - pw-midiplay should now work again after improvements in pw-stream. + +## modules + - The RAOP module was improved to support auth_setup. + - The RAOP module should now handle timing packets better. + - Add some more filter-chain examples. + - The filter-chain now has a separate config file with the boilerplate + settings. The examples are now just config snippets that can be dropped + in .conf.d/ directories, such as the filter-chain.conf.d/ one. + - Start suggesting to use target.object instead of node.target in docs + and examples. + +## SPA + - Use the cosh window again for the resampler. It should now + give better resampler quality. (#2483) + - Rework the mixer functions. They were rewritten for higher precision and + better performance. Add unit tests and benchmarks. + - Improve format conversion for 32bits for avoid errors in clang because + of undefined behaviour at extreme ranges. + - Fix a bug in audioconvert where it would not consume the right + amount of samples when the resampler was disabled. This could cause + skipping and hickups. (#2519) + - Fix bug in audioconvert where it would try to convert the input samples + multiple times, causing strange artifacts when upmixing. + - Be more strict about valid JSON floats. + - device.vendor.id and device.product.id should now always show up in + 0xXXXX format and should not be converted to floats in pw-dump anymore. + - Add triangular dither, add unit tests for noise generation, add some + more optimizations. + +## Bluetooth + - HFP and A2DP now expose different routes and thus can have different + volumes. + - HW Volumes for HFP are now synced better. Volume changes from HW buttons + are now also saved. + +# PipeWire 0.3.54 (2022-07-07) + +This is a quick bugfix release that is API and ABI compatible with previous +0.3.x releases. + +## Highlights + - Some critical bugs in the new audioconvert were fixed. The old + adapter had internal buffering that was abused in some places. + - The bluetooth sources were rewritten using a ringbuffer to make them + more reliable to jitter and remove old audioconvert behaviour. + - Many improvements to the audio converter. + - Native DSD128 and up is now supported by pw-dsdplay. + + +## tools + - Support DSD128 to DSD512 as well by scaling the amount of samples + to read per time slice. + +## SPA + - Format conversion is now generated with macros to remove duplication + of code. + - 24bits conversions were rewritten to use the generic conversion + functions. + - Temporary buffers in audioconvert are now made large enough in all + cases. + - Fix draining in audioconvert. This fixes speaker-test. + - Fix the channel remapping. (#2502, #2490) + - Audio conversion constants were tweaked to handle the maximum ranges + and provide lossless conversion between 24bits and floats. + - Vector code and C code are aligned and the unit tests are activated + again. A new lossless conversion test was added. + - Fix an underrun case where the adapter would not ask for more data. + - Fix PROP_INFO for audioconvert. (#2488) + - Use the blackman window again for the resampler, the cosh window has + some bugs that can cause distortion in some cases. (#2483) + - Add more unit tests for audioconvert. Add end-to-end conversion tests. + - Don't leak memory in format converter. + +## pulse-server + - Card properties are now also added to sinks and sources, just like + in pulseaudio. + - Increase the maxlength size to at least 4 times the fragsize to avoid + xruns. + - Fix a race when setting default devices. + +## Bluetooth + - The source was rewritten to use a ringbuffer. This avoids regressions + caused by audioconvert. + +# PipeWire 0.3.53 (2022-06-30) + +This is a bugfix release that is API and ABI compatible with previous +0.3.x releases. + +## Highlights + - The 44.1KHz samplerate was removed again from the defaults, it caused + all kinds of problems with various hardware. + - The ALSA plugin should now be able to deal with unsupported samplerates + and fall back to the nearest supported one. + - The rlimits performance tuning wiki page was updated. Please check + you limits.conf file, the version on the wiki used to give all + processes a -19 nice level instead of just the pipewire daemon. + - The audioconvert plugin was rewritten to be more maintainable and + faster. It also gained support for control ports and dithering with + optional noise shaping. + - An impossible buffering situation is avoided in pulse-server that would + cause some applications (sunshine, ...) to stutter. + + +## PipeWire + - 44.1KHz was removed from the allowed rates again. It caused all kinds + of regressions due to driver bugs and timing issues on HDMI. + +## modules + - filter-chain now does some more error checking and reporting to + avoid some crashes. + - filter-chain now supports more channel layouts for input and output + that does not need to match the plugin layout. + - Format parsing is now more consistent in the modules. + +## Tools + - pw-cli can now also work without readline support. + - pw-cat can now also read multichannel ulaw/alaw/u8/s8. + +## SPA + - The audioconvert plugin was rewritten. This should make it more + maintainable. It also fixed some issues such as CPU spikes in some + cases and crashes in others. The old plugins were removed, for a + code reduction of some 6000 lines. + - The audioconvert plugin now supports control ports, which can be + enabled on nodes in the session manager. This makes it possible to + control audioconvert properties using timed events or midi. + - NoteOn 0-velocity MIDI events are no longer filtered out. This is + a valid event, nodes that can't deal with it should fix it up + themselves. The JACK layer still filters out these events by default + but this can now be configured with a per-client property. + - The running status on midi events is now disabled to match what + JACK does. + - The ALSA plugin will now deal with driver bugs when a driver announces + support for a samplerate but then refuses to use it later. + - The ALSA plugin has been optimized a little for sample IO. + - V4L2 now doesn't error when there are no controls. + - Error handling was improved in the audio converter. + - The audioconvert plugin now supports rectangular dithering and + noise shaping. + - The audioconvert plugin can now insert additional inaudible noise + that can be used to keep some amplifiers alive. (#705) + - The audioconvert format conversion was changed so that it now produces + the full 32 bits range in the C fallback conversion code as well. + - The resampler window function was changed to a cosh() window + function. (#2483) + - Vendor and device id are now in hex. + +## pulse-server + - Tweak the record buffer attributes some more and make sure we don't + end up in impossible buffering situations. Fixes an issue with + distorted sound in sunshine. (#2447) + - Fix a potential crash when updating the client property list. + - Some properties on cards were aligned with pulseaudio. + +## Wiki + - Change "priority" to "nice" in the example limits.conf file. It was + giving a -19 nice level to all processes, not just the pipewire + daemon. + # PipeWire 0.3.52 (2022-06-09) This is a bugfix release that is API and ABI compatible with previous @@ -112,9 +435,6 @@ This is a bugfix release that is API and ABI compatible with previous - Fixes to the source and fd use. - It is now possible to set client properties as well. (#1573) - -Older versions: - # PipeWire 0.3.51 (2022-04-28) This is a bugfix release that is API and ABI compatible with previous diff --git a/debian/changelog b/debian/changelog index aca051198ae2fcf47712655c760d39929a92a462..49184d6f3f6551a0a706f01f715d00bad4d10911 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,85 @@ +pipewire (0.3.57-1~bpo11+1) bullseye-backports; urgency=medium + + * Rebuild for bullseye-backports. + * Disable XFixes, minimum required version not available in Bullseye + * Disable libfreeaptx, not yet available in Bullseye + + -- Dylan Aïssi <daissi@debian.org> Wed, 07 Sep 2022 13:47:47 +0200 + +pipewire (0.3.57-1) unstable; urgency=medium + + * New upstream release + * Drop patches included in upstream release: + - aaa015d0: avb: fix compilation on big endian + - 1a5ec445: avb: fix compilation on big endian + - f857fd46: avb: fix compilation on big endian + * Don't install filter-chain.service for now. + * Update symbols file + + -- Dylan Aïssi <daissi@debian.org> Mon, 05 Sep 2022 09:57:50 +0200 + +pipewire (0.3.56-1) unstable; urgency=medium + + * New upstream release + * Drop patches included in upstream release: + - 40552a0e: jack: only mix when we have input to mix + * Cherry-pick upstream recommended patches: + - aaa015d0: avb: fix compilation on big endian + - 1a5ec445: avb: fix compilation on big endian + - f857fd46: avb: fix compilation on big endian + * Install the new avb module in libspa-0.2-modules + * Don't install new filter-chain example conf file + * Update symbols file + + -- Dylan Aïssi <daissi@debian.org> Tue, 19 Jul 2022 22:47:35 +0200 + +pipewire (0.3.55-2) unstable; urgency=medium + + * Source only upload for migration to testing + + -- Dylan Aïssi <daissi@debian.org> Mon, 18 Jul 2022 18:10:38 +0200 + +pipewire (0.3.55-1) unstable; urgency=medium + + * New upstream release + * Cherry-pick upstream recommended patch: + - 40552a0e: jack: only mix when we have input to mix + * Don't install new filter-chain example conf files + * Reintroduce pipewire-audio-client-libraries as a transitional package + to simplify the transition from Bullseye to Bookworm (Closes: #1014639) + * Mark pipewire-{alsa,jack,v4l2} as 'Multi-Arch: same' (Closes: #1014608) + + -- Dylan Aïssi <daissi@debian.org> Sun, 17 Jul 2022 10:42:21 +0200 + +pipewire (0.3.54-2) unstable; urgency=medium + + * Source only upload for migration to testing + + -- Dylan Aïssi <daissi@debian.org> Fri, 08 Jul 2022 10:07:26 +0200 + +pipewire (0.3.54-1) unstable; urgency=medium + + [ Sebastien Bacher ] + * Split the legacy pipewire-audio-client-libraries in alsa and jack + + [ Dylan Aïssi ] + * New upstream release + - Fix issue with microphone input on bluetooth headset (Closes: #1014458) + * pipewire-jack: remove suggests pulseaudio-utils and libspa-0.2-bluetooth + * Switch section from video to sound for pipewire-pulse, pipewire-alsa + and pipewire-jack + * Improve description of pipewire-alsa and pipewire-jack + * Move the V4L2 plugin into its own package + * Update copyright file + + -- Dylan Aïssi <daissi@debian.org> Thu, 07 Jul 2022 17:11:37 +0200 + +pipewire (0.3.53-1) unstable; urgency=medium + + * New upstream release + + -- Dylan Aïssi <daissi@debian.org> Fri, 01 Jul 2022 14:08:59 +0200 + pipewire (0.3.52-1) unstable; urgency=medium [ Dylan Aïssi ] diff --git a/debian/control b/debian/control index 639c3ca0e2d7269702984ae6be45a66d843b3f64..04abc683df47068c01e0c025d197faa91d00c525 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, @@ -32,7 +32,7 @@ Build-Depends: debhelper-compat (= 13), libusb-1.0-0-dev, libv4l-dev, libwebrtc-audio-processing-dev, - libxfixes-dev (>= 1:6.0.0), +# libxfixes-dev (>= 1:6.0.0), meson (>= 0.59.0), pkg-config, python3-docutils, @@ -218,7 +218,7 @@ Description: PipeWire multimedia server - programs the pipewire package instead. Package: pipewire-pulse -Section: video +Section: sound Architecture: linux-any Multi-Arch: foreign Replaces: pipewire-bin (<< 0.3.27-2) @@ -239,14 +239,36 @@ Description: PipeWire PulseAudio daemon . This package contains the PulseAudio replacement daemon. -Package: pipewire-audio-client-libraries +Package: pipewire-alsa +Section: sound Architecture: linux-any Multi-Arch: same -Depends: pipewire (= ${binary:Version}), ${misc:Depends}, ${shlibs:Depends} -Recommends: ${shlibs:Recommends} -Breaks: pipewire (<< 0.3.5) -Replaces: pipewire (<< 0.3.5) -Description: PipeWire multimedia server - audio client libraries +Replaces: pipewire-audio-client-libraries (<< 0.3.54-1~) +Breaks: pipewire-audio-client-libraries (<< 0.3.54-1~) +Depends: pipewire (= ${binary:Version}), + ${misc:Depends}, + ${shlibs:Depends} +Description: PipeWire ALSA plugin + PipeWire is a server and user space API to deal with multimedia + pipelines. This includes: + . + - Making available sources of video (such as from a capture devices or + application provided streams) and multiplexing this with clients. + - Accessing sources of video for consumption. + - Generating graphs for audio and video processing. + . + This package contains the ALSA plugin. + +Package: pipewire-jack +Section: sound +Architecture: linux-any +Multi-Arch: same +Replaces: pipewire-audio-client-libraries (<< 0.3.54-1~) +Breaks: pipewire-audio-client-libraries (<< 0.3.54-1~) +Depends: pipewire (= ${binary:Version}), + ${misc:Depends}, + ${shlibs:Depends} +Description: PipeWire JACK plugin PipeWire is a server and user space API to deal with multimedia pipelines. This includes: . @@ -255,10 +277,29 @@ Description: PipeWire multimedia server - audio client libraries - Accessing sources of video for consumption. - Generating graphs for audio and video processing. . - This package contains client libraries allowing programs designed for - the ALSA, JACK and PulseAudio APIs to use a PipeWire server for audio - playback and recording. They are not used by default, and are currently - considered to be experimental. + This package contains the JACK plugin. + +Package: pipewire-v4l2 +Section: video +Architecture: linux-any +Multi-Arch: same +Replaces: pipewire-bin (<< 0.3.54-1), + libpipewire-0.3-modules (<< 0.3.54-1) +Breaks: pipewire-bin (<< 0.3.54-1), + libpipewire-0.3-modules (<< 0.3.54-1) +Depends: pipewire (= ${binary:Version}), + ${misc:Depends}, + ${shlibs:Depends} +Description: PipeWire V4L2 plugin + PipeWire is a server and user space API to deal with multimedia + pipelines. This includes: + . + - Making available sources of video (such as from a capture devices or + application provided streams) and multiplexing this with clients. + - Accessing sources of video for consumption. + - Generating graphs for audio and video processing. + . + This package contains the V4L2 plugin. Package: pipewire-tests Architecture: linux-any @@ -329,3 +370,15 @@ 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 +Architecture: all +Depends: pipewire-alsa, + pipewire-jack +Description: transitional package for pipewire-alsa and pipewire-jack + PipeWire is a server and user space API to deal with multimedia + pipelines. + . + This is a transitional package for pipewire-alsa and pipewire-jack. + It can safely be removed. diff --git a/debian/copyright b/debian/copyright index 0bdf6775ab449974e14b6b1060e752c3acad4403..92d66f3d2ecca5d787fa542d96cb4a1a953df727 100644 --- a/debian/copyright +++ b/debian/copyright @@ -7,14 +7,45 @@ Files: * Copyright: 2009 Lennart Poettering 2010 David Henningsson 2013 Inigo Quilez - 2015-2020 Wim Taymans - 2016 Axis Communications - 2018-2020 Collabora Ltd. + 2015-2022 Wim Taymans + 2016-2021 Axis Communications + 2018-2022 Collabora Ltd. 2020 Konsulko Group 2020 Sergey Bugaev 2020 Georges Basile Stavracas Neto + 2021 jothepro + 2019-2021 Red Hat, Inc. + 2021 Arun Raghavan + 2013 The Chromium OS Authors. + 2010 Google Inc. + 2017 HiFi-LoFi + 2000-2002 Richard W.E. Furse, Paul Barton-Davis + 2021 Sanchayan Maity + 2021 Pauli Virtanen + 2021 Florian Hülsmann License: Expat +Files: include/* +Copyright: 2000-2017 Julian Seward. +License: BZIP2 + +Files: pipewire-jack/jack/* +Copyright: 2000-2013 Paul Davis + 2003-2004 Jack OQuin + 2002 Kai Vehmanen + 2011-2014 David Robillard + 2004 Ian Esten + 2004-2012 Grame + 2003 Rohan Drape + 2010 Torben Hohn + 2004 Rui Nuno Capela, Lee Revell +License: LGPL-2.1+ + +Files: pipewire-jack/jack/control.h +Copyright: 2008 Nedko Arnaudov + 2008 GRAME +License: GPL-2 + Files: spa/plugins/alsa/90-pipewire-alsa.rules spa/plugins/alsa/acp/* spa/plugins/alsa/mixer/paths/* @@ -30,24 +61,36 @@ Copyright: 1999 Tom Tromey 2011 Arun Raghavan 2011 Wolfson Microelectronics PLC 2012 Feng Wei, Freescale Ltd. - 2015-2020 Wim Taymans + 2015-2022 Wim Taymans License: LGPL-2+ and LGPL-2.1+ and Expat Files: spa/plugins/bluez5/* Copyright: 2004-2010 Marcel Holtmann 2006-2010 Nokia Corporation 2016-2017 Arkadiusz Bokowy - 2018 Wim Taymans - 2019 Collabora Ltd. + 2018-2022 Wim Taymans + 2018-2022 Collabora Ltd. + 2018 Pali Rohár + 2021-2022 Pauli Virtanen + 2013 Julien Pommier License: Expat and LGPL-2.1+ Files: src/modules/module-client-node/v0/* src/modules/module-protocol-native/v0/* src/modules/module-portal.c -Copyright: 2015-2017 Wim Taymans +Copyright: 2015-2022 Wim Taymans 2019 Red Hat Inc. License: LGPL-2+ +Files: src/modules/module-filter-chain/ladspa.h +Copyright: 2000-2002 Richard W.E. Furse, Paul Barton-Davis +License: LGPL-2.1+ + +Files: src/modules/module-filter-chain/pffft.* +Copyright: 2013 Julien Pommier + 2004 The University Corporation for Atmospheric Research +License: FFTPACK + License: Expat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -100,3 +143,77 @@ License: LGPL-2.1+ . On Debian systems, the complete text of the GNU Lesser General Public License can be found in "/usr/share/common-licenses/LGPL-2.1". + +License: FFTPACK + Redistribution and use of the Software in source and binary forms, + with or without modification, is permitted provided that the + following conditions are met: + . + - Neither the names of NCAR's Computational and Information Systems + Laboratory, the University Corporation for Atmospheric Research, + nor the names of its sponsors or contributors may be used to + endorse or promote products derived from this Software without + specific prior written permission. + . + - Redistributions of source code must retain the above copyright + notices, this list of conditions, and the disclaimer below. + . + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer below in the + documentation and/or other materials provided with the + distribution. + . + THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE + SOFTWARE. + +License: BZIP2 + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + . + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + . + 2. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + . + 3. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original software. + . + 4. The name of the author may not be used to endorse or promote + products derived from this software without specific prior written + permission. + . + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +License: GPL-2 + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + On Debian systems you can find the full text of the GNU General Public + License version 2 at /usr/share/common-licenses/GPL-2. diff --git a/debian/libpipewire-0.3-0.symbols b/debian/libpipewire-0.3-0.symbols index c3ba4300f8164bdfc6ce5c647a69c0c15ef2b60b..84324498643447c537e623198e0e4c397eefdc4a 100644 --- a/debian/libpipewire-0.3-0.symbols +++ b/debian/libpipewire-0.3-0.symbols @@ -9,6 +9,7 @@ libpipewire-0.3.so.0 libpipewire-0.3-0 #MINVER# pw_client_info_merge@Base 0.3.35 pw_client_info_update@Base 0.3.1 pw_conf_load_conf@Base 0.3.22 + pw_conf_load_conf_for_context@Base 0.3.57 pw_conf_load_state@Base 0.3.22 pw_conf_save_state@Base 0.3.22 pw_context_add_listener@Base 0.3.1 @@ -35,6 +36,7 @@ libpipewire-0.3.so.0 libpipewire-0.3-0 #MINVER# pw_context_find_spa_lib@Base 0.3.1 pw_context_for_each_global@Base 0.3.1 pw_context_get_conf_section@Base 0.3.22 + pw_context_get_data_loop@Base 0.3.56 pw_context_get_default_core@Base 0.3.1 pw_context_get_main_loop@Base 0.3.1 pw_context_get_object@Base 0.3.1 diff --git a/debian/libpipewire-0.3-modules.install b/debian/libpipewire-0.3-modules.install index 0b52b118e7bc943443ade3df559e56af66c78311..9e442992980a498ad999d9b699723bdb2d205659 100644 --- a/debian/libpipewire-0.3-modules.install +++ b/debian/libpipewire-0.3-modules.install @@ -1,2 +1 @@ usr/lib/*/pipewire-0.3/*.so -usr/lib/*/pipewire-0.3/v4l2/libpw-v4l2.so diff --git a/debian/libspa-0.2-modules.install b/debian/libspa-0.2-modules.install index ea7494f2bbd7b211d062c897a27029024a213d6e..7accb5b3c0cfc6ef6ba4db449553f847ac6c3855 100644 --- a/debian/libspa-0.2-modules.install +++ b/debian/libspa-0.2-modules.install @@ -3,6 +3,7 @@ usr/lib/*/spa-0.2/alsa usr/lib/*/spa-0.2/audioconvert usr/lib/*/spa-0.2/audiomixer usr/lib/*/spa-0.2/audiotestsrc +usr/lib/*/spa-0.2/avb usr/lib/*/spa-0.2/control usr/lib/*/spa-0.2/support usr/lib/*/spa-0.2/test diff --git a/debian/not-installed b/debian/not-installed index 31d9c16f10d8e5cf82eaeb480ddd421420f699d2..e197cdea21fc0c6613078d43931d8f18048a0bf6 100644 --- a/debian/not-installed +++ b/debian/not-installed @@ -1,9 +1,14 @@ +usr/lib/systemd/user/filter-chain.service +usr/share/pipewire/filter-chain.conf usr/share/pipewire/filter-chain/demonic.conf usr/share/pipewire/filter-chain/duplicate-FL.conf usr/share/pipewire/filter-chain/sink-convolver.conf usr/share/pipewire/filter-chain/sink-dolby-surround.conf usr/share/pipewire/filter-chain/sink-eq6.conf +usr/share/pipewire/filter-chain/sink-make-LFE.conf usr/share/pipewire/filter-chain/sink-matrix-spatialiser.conf +usr/share/pipewire/filter-chain/sink-mix-FL-FR.conf usr/share/pipewire/filter-chain/sink-virtual-surround-5.1-kemar.conf usr/share/pipewire/filter-chain/sink-virtual-surround-7.1-hesuvi.conf +usr/share/pipewire/filter-chain/source-duplicate-FL.conf usr/share/pipewire/filter-chain/source-rnnoise.conf diff --git a/debian/patches/Don-t-build_same_binary_twice.patch b/debian/patches/Don-t-build_same_binary_twice.patch index 4485b3c554b42788b950ac88862d589dc9d11774..5571943cbe8e7482479ab6fa26842411be4dc6d5 100644 --- a/debian/patches/Don-t-build_same_binary_twice.patch +++ b/debian/patches/Don-t-build_same_binary_twice.patch @@ -20,7 +20,7 @@ Signed-off-by: Dylan Aïssi <dylan.aissi@collabora.com> --- a/src/daemon/meson.build +++ b/src/daemon/meson.build -@@ -90,12 +90,11 @@ +@@ -93,12 +93,11 @@ dependencies : [ spa_dep, pipewire_dep, ], ) @@ -37,4 +37,4 @@ Signed-off-by: Dylan Aïssi <dylan.aissi@collabora.com> + join_paths(get_option('prefix'), get_option('bindir'), 'pipewire-pulse')) ) - ln = find_program('ln') + executable('pipewire-avb', diff --git a/debian/patches/series b/debian/patches/series index adf4f7f600dc2b456a1398b6fb6751066ab774c4..68ea67cb3cb189bac09334b1c603440c2c43ecd8 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,2 +1,3 @@ Don-t-automatically-start-pipewire-for-root-logins.patch Don-t-build_same_binary_twice.patch +# Recommended patch for 0.3.5X diff --git a/debian/pipewire-audio-client-libraries.install b/debian/pipewire-alsa.install similarity index 63% rename from debian/pipewire-audio-client-libraries.install rename to debian/pipewire-alsa.install index 917613102716241a2eb1565cc148794488105001..71834ccb424529703c1b619e789e529c98167bf6 100644 --- a/debian/pipewire-audio-client-libraries.install +++ b/debian/pipewire-alsa.install @@ -1,8 +1,4 @@ -debian/ld.so.conf.d/* usr/share/doc/pipewire/examples/ld.so.conf.d -usr/bin/pw-jack usr/lib/*/alsa-lib/libasound_module_ctl_pipewire.so usr/lib/*/alsa-lib/libasound_module_pcm_pipewire.so -usr/lib/*/pipewire-0.3/jack usr/share/alsa/alsa.conf.d/50-pipewire.conf usr/share/alsa/alsa.conf.d/99-pipewire-default.conf usr/share/doc/pipewire/examples/alsa.conf.d -usr/share/man/man1/pw-jack.* diff --git a/debian/pipewire-alsa.links b/debian/pipewire-alsa.links new file mode 100644 index 0000000000000000000000000000000000000000..570661740a1df49b4f0ab0301f8baad4f0354377 --- /dev/null +++ b/debian/pipewire-alsa.links @@ -0,0 +1 @@ +usr/share/alsa/alsa.conf.d/50-pipewire.conf etc/alsa/conf.d/50-pipewire.conf diff --git a/debian/pipewire-audio-client-libraries.links b/debian/pipewire-audio-client-libraries.links deleted file mode 100644 index a90c61d2190c87e80a0d1c1d515bb0a2629c277d..0000000000000000000000000000000000000000 --- a/debian/pipewire-audio-client-libraries.links +++ /dev/null @@ -1,3 +0,0 @@ -usr/share/alsa/alsa.conf.d/50-pipewire.conf etc/alsa/conf.d/50-pipewire.conf -usr/share/doc/pipewire-audio-client-libraries/README.Debian usr/share/doc/pipewire/examples/README.audio -usr/share/doc/pipewire/examples usr/share/doc/pipewire-audio-client-libraries/examples diff --git a/debian/pipewire-audio-client-libraries.shlibs.local b/debian/pipewire-audio-client-libraries.shlibs.local deleted file mode 100644 index e9c0857b5ae38ec8f550bc9ae50e868d6a978bc7..0000000000000000000000000000000000000000 --- a/debian/pipewire-audio-client-libraries.shlibs.local +++ /dev/null @@ -1,4 +0,0 @@ -libjack 0 pipewire-audio-client-libraries (= ${binary:Version}) -libjacknet 0 pipewire-audio-client-libraries (= ${binary:Version}) -libjackserver 0 pipewire-audio-client-libraries (= ${binary:Version}) -libpipewire-0.3 0 libpipewire-0.3-0 (= ${binary:Version}) diff --git a/debian/pipewire-bin.install b/debian/pipewire-bin.install index 9d7a9abdf7ae04d2da426efe75b9ff3bdea3cc35..e5808a3e4bc538a340cb230c9baedf1bead0237e 100644 --- a/debian/pipewire-bin.install +++ b/debian/pipewire-bin.install @@ -2,9 +2,11 @@ usr/share/pipewire/client-rt.conf usr/share/pipewire/client.conf usr/share/pipewire/jack.conf usr/share/pipewire/pipewire.conf +usr/share/pipewire/pipewire-avb.conf usr/share/pipewire/minimal.conf lib/udev/rules.d usr/bin/pipewire +usr/bin/pipewire-avb usr/bin/pw-cat usr/bin/pw-cli usr/bin/pw-dot @@ -22,7 +24,6 @@ usr/bin/pw-profiler usr/bin/pw-record usr/bin/pw-reserve usr/bin/pw-top -usr/bin/pw-v4l2 usr/bin/spa-* usr/share/alsa-card-profile usr/share/man/man1/pipewire.* diff --git a/debian/pipewire-jack.install b/debian/pipewire-jack.install new file mode 100644 index 0000000000000000000000000000000000000000..e5ef3a51c0b393291653d88e74980a7701afdaa7 --- /dev/null +++ b/debian/pipewire-jack.install @@ -0,0 +1,5 @@ +usr/bin/pw-jack +usr/lib/*/pipewire-0.3/jack +usr/share/man/man1/pw-jack.* +debian/ld.so.conf.d/* usr/share/doc/pipewire/examples/ld.so.conf.d + diff --git a/debian/pipewire-audio-client-libraries.lintian-overrides b/debian/pipewire-jack.lintian-overrides similarity index 100% rename from debian/pipewire-audio-client-libraries.lintian-overrides rename to debian/pipewire-jack.lintian-overrides diff --git a/debian/pipewire-jack.shlibs.local b/debian/pipewire-jack.shlibs.local new file mode 100644 index 0000000000000000000000000000000000000000..1bba4c397bd7076e562c08ea2c54fbb0b6d785b8 --- /dev/null +++ b/debian/pipewire-jack.shlibs.local @@ -0,0 +1,4 @@ +libjack 0 pipewire-jack (= ${binary:Version}) +libjacknet 0 pipewire-jack (= ${binary:Version}) +libjackserver 0 pipewire-jack (= ${binary:Version}) +libpipewire-0.3 0 libpipewire-0.3-0 (= ${binary:Version}) diff --git a/debian/pipewire-v4l2.install b/debian/pipewire-v4l2.install new file mode 100644 index 0000000000000000000000000000000000000000..08dbd90fd7f2ce721a1291d36efb24dbc536a819 --- /dev/null +++ b/debian/pipewire-v4l2.install @@ -0,0 +1,2 @@ +usr/bin/pw-v4l2 +usr/lib/*/pipewire-0.3/v4l2/libpw-v4l2.so diff --git a/debian/pipewire-audio-client-libraries.README.Debian b/debian/pipewire.README.Debian similarity index 100% rename from debian/pipewire-audio-client-libraries.README.Debian rename to debian/pipewire.README.Debian diff --git a/debian/rules b/debian/rules index d3910a5e5c9c552e2e3661b4dd61c421652118e4..bbf9fe65c35d10a62aaab32d5802cfd1a318c4bf 100755 --- a/debian/rules +++ b/debian/rules @@ -31,6 +31,7 @@ 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 \ @@ -45,6 +46,7 @@ override_dh_auto_configure: -Dvideotestsrc=enabled \ -Dvolume=enabled \ -Dvulkan=disabled \ + -Dx11-xfixes=disabled \ $(NULL) install -d debian/ld.so.conf.d echo "/usr/lib/$(DEB_HOST_MULTIARCH)/pipewire-0.3/jack/" > "debian/ld.so.conf.d/pipewire-jack-$(DEB_HOST_MULTIARCH).conf" @@ -77,10 +79,10 @@ override_dh_makeshlibs: override_dh_shlibdeps-arch: dh_shlibdeps \ - -ppipewire-audio-client-libraries \ + -ppipewire-jack \ -l/usr/lib/$(DEB_HOST_MULTIARCH)/pipewire-0.3 \ -- \ - -Ldebian/pipewire-audio-client-libraries.shlibs.local \ + -Ldebian/pipewire-jack.shlibs.local \ $(NULL) dh_shlibdeps \ --remaining-packages \ diff --git a/doc/pipewire-modules.dox b/doc/pipewire-modules.dox index c1eea1dacbb9d26fed42cec2d20cc36af59f83af..0b2f1f6980add4cbb34cfd7ac1bdc9d12cf23919 100644 --- a/doc/pipewire-modules.dox +++ b/doc/pipewire-modules.dox @@ -51,6 +51,7 @@ List of known modules: - \subpage page_module_access - \subpage page_module_adapter +- \subpage page_module_avb - \subpage page_module_client_device - \subpage page_module_client_node - \subpage page_module_echo_cancel diff --git a/doc/tutorial3.c b/doc/tutorial3.c index c7dd792cac62a93654eec9095481e9f3bb956663..17c0ee4457bb187fcdc85259c06e0b2d8b3af59f 100644 --- a/doc/tutorial3.c +++ b/doc/tutorial3.c @@ -7,32 +7,36 @@ #include <pipewire/pipewire.h> /* [roundtrip] */ -static int roundtrip(struct pw_core *core, struct pw_main_loop *loop) +struct roundtrip_data { + int pending; + struct pw_main_loop *loop; +}; + +static void on_core_done(void *data, uint32_t id, int seq) +{ + struct roundtrip_data *d = data; + + if (id == PW_ID_CORE && seq == d->pending) + pw_main_loop_quit(d->loop); +} + +static void roundtrip(struct pw_core *core, struct pw_main_loop *loop) { - struct spa_hook core_listener; - int pending, done = 0; - void core_event_done(void *object, uint32_t id, int seq) { - if (id == PW_ID_CORE && seq == pending) { - done = 1; - pw_main_loop_quit(loop); - } - } - const struct pw_core_events core_events = { - PW_VERSION_CORE_EVENTS, - .done = core_event_done, - }; - - spa_zero(core_listener); - pw_core_add_listener(core, &core_listener, - &core_events, NULL); - - pending = pw_core_sync(core, PW_ID_CORE, 0); - - while (!done) { - pw_main_loop_run(loop); - } - spa_hook_remove(&core_listener); - return 0; + static const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .done = on_core_done, + }; + + struct roundtrip_data d = { .loop = loop }; + struct spa_hook core_listener; + + pw_core_add_listener(core, &core_listener, &core_events, &d); + + d.pending = pw_core_sync(core, PW_ID_CORE, 0); + + pw_main_loop_run(loop); + + spa_hook_remove(&core_listener); } /* [roundtrip] */ @@ -50,37 +54,36 @@ static const struct pw_registry_events registry_events = { int main(int argc, char *argv[]) { - struct pw_main_loop *loop; - struct pw_context *context; - struct pw_core *core; - struct pw_registry *registry; - struct spa_hook registry_listener; + struct pw_main_loop *loop; + struct pw_context *context; + struct pw_core *core; + struct pw_registry *registry; + struct spa_hook registry_listener; - pw_init(&argc, &argv); + pw_init(&argc, &argv); - loop = pw_main_loop_new(NULL /* properties */); - context = pw_context_new(pw_main_loop_get_loop(loop), - NULL /* properties */, - 0 /* user_data size */); + loop = pw_main_loop_new(NULL /* properties */); + context = pw_context_new(pw_main_loop_get_loop(loop), + NULL /* properties */, + 0 /* user_data size */); - core = pw_context_connect(context, - NULL /* properties */, - 0 /* user_data size */); + core = pw_context_connect(context, + NULL /* properties */, + 0 /* user_data size */); - registry = pw_core_get_registry(core, PW_VERSION_REGISTRY, - 0 /* user_data size */); + registry = pw_core_get_registry(core, PW_VERSION_REGISTRY, + 0 /* user_data size */); - spa_zero(registry_listener); - pw_registry_add_listener(registry, ®istry_listener, - ®istry_events, NULL); + pw_registry_add_listener(registry, ®istry_listener, + ®istry_events, NULL); - roundtrip(core, loop); + roundtrip(core, loop); - pw_proxy_destroy((struct pw_proxy*)registry); - pw_core_disconnect(core); - pw_context_destroy(context); - pw_main_loop_destroy(loop); + pw_proxy_destroy((struct pw_proxy*)registry); + pw_core_disconnect(core); + pw_context_destroy(context); + pw_main_loop_destroy(loop); - return 0; + return 0; } /* [code] */ diff --git a/doc/tutorial3.dox b/doc/tutorial3.dox index 621819d22c0fba8456e15a2d01f10e87cad4b07a..776ea141e3d336a3b8abd669c119336f235569b5 100644 --- a/doc/tutorial3.dox +++ b/doc/tutorial3.dox @@ -15,10 +15,9 @@ Let's take the following small method first: Let's take a look at what this method does. \code{.c} - struct spa_hook core_listener; - spa_zero(core_listener); - pw_core_add_listener(core, &core_listener, - &core_events, NULL); + struct spa_hook core_listener; + + pw_core_add_listener(core, &core_listener, &core_events, &d); \endcode First of all we add a listener for the events of the core @@ -26,37 +25,31 @@ object. We are only interested in the `done` event in this tutorial. This is the event handler: \code{.c} - int pending, done = 0; - - void core_event_done(void *data, uint32_t id, int seq) { - if (id == PW_ID_CORE && seq == pending) { - done = 1; - pw_main_loop_quit(loop); - } - } - const struct pw_core_events core_events = { - PW_VERSION_CORE_EVENTS, - .done = core_event_done, - }; +static void on_core_done(void *data, uint32_t id, int seq) +{ + struct roundtrip_data *d = data; + + if (id == PW_ID_CORE && seq == d->pending) + pw_main_loop_quit(d->loop); +} \endcode -When the done event is received for an object with id `PW_ID_CORE` -and a certain sequence number `seq`, this function will set the done -variable to 1 and call `pw_main_loop_quit()`. +When the done event is received for an object with id `PW_ID_CORE` and +a certain sequence number `seq`, this function will call `pw_main_loop_quit()`. Next we do: \code{.c} - pending = pw_core_sync(core, PW_ID_CORE, 0); + d.pending = pw_core_sync(core, PW_ID_CORE, 0); \endcode This triggers the `sync` method on the core object with id `PW_ID_CORE` and sequence number 0. Because this is a method on a proxy object, it will be executed -asynchronously and the returns value will reflect this. PipeWire +asynchronously and the return value will reflect this. PipeWire uses the return values of the underlying SPA (Simple Plugin API) -helper objects (See also [error codes](spa-design.md#error-codes)). +helper objects (See also \ref page_spa_design ). Because all messages on the PipeWire server are handled sequentially, the sync method will be executed after all previous methods are @@ -68,9 +61,7 @@ We then run the mainloop to send the messages to the server and receive the events: \code{.c} - while (!done) { - pw_main_loop_run(loop); - } + pw_main_loop_run(loop); \endcode When we get the done event, we can compare it to the sync method @@ -79,7 +70,7 @@ more pending methods on the server. We can quit the mainloop and remove the listener: \code{.c} - spa_hook_remove(&core_listener); + spa_hook_remove(&core_listener); \endcode If we add this roundtrip method to our code and call it instead of the @@ -100,7 +91,7 @@ the objects we created. Let's destroy each of them in reverse order that we created them: \code{.c} - pw_proxy_destroy((struct pw_proxy*)registry); + pw_proxy_destroy((struct pw_proxy*)registry); \endcode The registry is a proxy and can be destroyed with the generic proxy destroy @@ -110,7 +101,7 @@ an error to destroy an object more than once. We can disconnect from the server with: \code{.c} - pw_core_disconnect(core); + pw_core_disconnect(core); \endcode This will also destroy the core proxy object and will remove the proxies @@ -119,8 +110,8 @@ that might have been created on this connection. We can finally destroy our context and mainloop to conclude this tutorial: \code{.c} - pw_context_destroy(context); - pw_main_loop_destroy(loop); + pw_context_destroy(context); + pw_main_loop_destroy(loop); \endcode \ref page_tutorial2 | \ref page_tutorial "Index" | \ref page_tutorial4 diff --git a/doc/tutorial4.c b/doc/tutorial4.c index 94fb6d3e223fd942962a3d4873acea544916bb5b..ff0cb277440fef9c0f85be805384ffe2b78ae8a5 100644 --- a/doc/tutorial4.c +++ b/doc/tutorial4.c @@ -96,7 +96,7 @@ int main(int argc, char *argv[]) pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, - argc > 1 ? (uint32_t)atoi(argv[1]) : PW_ID_ANY, + PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, diff --git a/doc/tutorial4.dox b/doc/tutorial4.dox index 92840212df305f98efa26260ce7553786c85023f..b8d170744f90dc61d997fb1e8f44c6331cf479a4 100644 --- a/doc/tutorial4.dox +++ b/doc/tutorial4.dox @@ -118,8 +118,8 @@ Now we're ready to connect the stream and run the main loop: pw_main_loop_run(data.loop); \endcode -To connect we specify that we have a `PW_DIRECTION_OUTPUT` stream. `PW_ID_ANY` -means that we are ok with connecting to any consumer. Next we set some flags: +To connect we specify that we have a `PW_DIRECTION_OUTPUT` stream. The third argument +is always `PW_ID_ANY`. Next we set some flags: - `PW_STREAM_FLAG_AUTOCONNECT`: Automatically connect this stream. This instructs the session manager to link us to some consumer. diff --git a/doc/tutorial5.c b/doc/tutorial5.c index 754fc025d73852b74a8e9a6f05b66f73a35d4127..e49da91168bf63522a80de1426176f97b1c57656 100644 --- a/doc/tutorial5.c +++ b/doc/tutorial5.c @@ -83,19 +83,23 @@ int main(int argc, char *argv[]) const struct spa_pod *params[1]; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + struct pw_properties *props; pw_init(&argc, &argv); data.loop = pw_main_loop_new(NULL); + props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Camera", + NULL); + if (argc > 1) + pw_properties_set(props, PW_KEY_TARGET_OBJECT, argv[1]); + data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "video-capture", - pw_properties_new( - PW_KEY_MEDIA_TYPE, "Video", - PW_KEY_MEDIA_CATEGORY, "Capture", - PW_KEY_MEDIA_ROLE, "Camera", - NULL), + props, &stream_events, &data); @@ -122,7 +126,7 @@ int main(int argc, char *argv[]) pw_stream_connect(data.stream, PW_DIRECTION_INPUT, - argc > 1 ? (uint32_t)atoi(argv[1]) : PW_ID_ANY, + PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS, params, 1); diff --git a/doc/tutorial5.dox b/doc/tutorial5.dox index b614fc531274013f3d4578e3dc1898f330bc7908..e73c1cf4463e976c0d645c19e4e124a8bae34bfd 100644 --- a/doc/tutorial5.dox +++ b/doc/tutorial5.dox @@ -23,18 +23,25 @@ We create a stream object with different properties to make it a Camera Video Capture stream. \code{.c} + props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Camera", + NULL); + if (argc > 1) + pw_properties_set(props, PW_KEY_TARGET_OBJECT, argv[1]); + data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "video-capture", - pw_properties_new( - PW_KEY_MEDIA_TYPE, "Video", - PW_KEY_MEDIA_CATEGORY, "Capture", - PW_KEY_MEDIA_ROLE, "Camera", - NULL), + props, &stream_events, &data); \endcode +We also optionally allow the user to pass the name of the target node where the session +manager is supposed to connect the node. The user may also give the value of the +unique target node serial (`PW_KEY_OBJECT_SERIAL`) as the value. + In addition to the `process` event, we are also going to listen to a new event, `param_changed`: @@ -122,7 +129,7 @@ Now we're ready to connect the stream and run the main loop: \code{.c} pw_stream_connect(data.stream, PW_DIRECTION_INPUT, - argc > 1 ? (uint32_t)atoi(argv[1]) : PW_ID_ANY, + PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS, params, 1); @@ -130,9 +137,8 @@ Now we're ready to connect the stream and run the main loop: pw_main_loop_run(data.loop); \endcode -To connect we specify that we have a `PW_DIRECTION_INPUT` stream. `PW_ID_ANY` -means that we are ok with connecting to any producer. We also allow the user -to pass an optional target id. +To connect we specify that we have a `PW_DIRECTION_INPUT` stream. The third +argument is always `PW_ID_ANY`. We're setting the `PW_STREAM_FLAG_AUTOCONNECT` flag to make an automatic connection to a suitable camera and `PW_STREAM_FLAG_MAP_BUFFERS` to let the diff --git a/man/pw-cli.1.rst.in b/man/pw-cli.1.rst.in index 98c5174c7c39efb1052d7de57e96f97051831215..ca0a2e80598359c69a033b7a6f2bbf8a76ea25fd 100644 --- a/man/pw-cli.1.rst.in +++ b/man/pw-cli.1.rst.in @@ -34,17 +34,20 @@ Use the 'help' command to list the available commands. GENERAL COMMANDS ================ -help - Show a quick help on the commands available. +help | h + Show a quick help on the commands available. It also lists the aliases + for many commands. -quit +quit | q Exit from **pw-cli** MODULE MANAGEMENT ================= -| Modules are loaded and unloaded in the local instance and can add -| functionality or objects to the local instance. +| Modules are loaded and unloaded in the local instance, thus the pw-cli +| binary itself and can add functionality or objects to the local +| instance. It is not possible in PipeWire to load modules in another +| instance. load-module *name* [*arguments...*] Load a module specified by its name and arguments. For most @@ -105,14 +108,22 @@ create-node *factory-name* [*properties...*] This command returns a *node variable*. -destroy-node *node-var* - Destroy a node. - export-node *node-id* [*remote-var*] Export a node from the local instance to the specified instance. When no instance is specified, the node will be exported to the current instance. +DEVICE MANAGEMENT +================= + +create-device *factory-name* [*properties...*] + Create a device from a factory in the current instance. + + Properties are key=value pairs separated by whitespace. + + This command returns a *device variable*. + + LINK MANAGEMENT =============== @@ -125,8 +136,44 @@ create-link *node-id* *port-id* *node-id* *port-id* [*properties...*] This command returns a *link variable*. -destroy-link *link-var* - Destroy a link. +GLOBALS MANAGEMENT +================== + +destroy *object-id* + Destroy a global object. + + +PARAMETER MANAGEMENT +==================== + +enum-params *object-id* *param-id* + Enumerate params of an object. + + *param-id* can also be given as the param short name. + +set-param *object-id* *param-id* *param-json* + Set param of an object. + + *param-id* can also be given as the param short name. + +PERMISSION MANAGEMENT +===================== + +permissions *client-id* *object-id* *permission* + Set permissions for a client. + + *object-id* can be *-1* to set the default permissions. + +get-permissions *client-id* + Get permissions of a client. + + +COMMAND MANAGEMENT +================== + +send-command *object-id* + Send a command to an object. + EXAMPLES ======== diff --git a/meson.build b/meson.build index 972a55771478f794c3299c85e3a746429736bc2a..2634b711757f544556703f2e5ac05f9da77fc0ca 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('pipewire', ['c' ], - version : '0.3.52', + version : '0.3.57', license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ], meson_version : '>= 0.59.0', default_options : [ 'warning_level=3', @@ -266,6 +266,7 @@ if not readline_dep.found() endif summary({'readline (for pw-cli)': readline_dep.found()}, bool_yn: true, section: 'Misc dependencies') +cdata.set('HAVE_READLINE', readline_dep.found()) ncurses_dep = dependency('ncursesw', required : false) sndfile_dep = dependency('sndfile', version : '>= 1.0.20', required : get_option('sndfile')) summary({'sndfile': sndfile_dep.found()}, bool_yn: true, section: 'pw-cat/pw-play/pw-dump/filter-chain') @@ -294,6 +295,11 @@ cdata.set('HAVE_LIBUSB', libusb_dep.found()) cap_lib = dependency('libcap', required : false) cdata.set('HAVE_LIBCAP', cap_lib.found()) +glib2_dep = dependency('glib-2.0', required : get_option('flatpak')) +summary({'GLib-2.0 (Flatpak support)': glib2_dep.found()}, bool_yn: true, section: 'Misc dependencies') +flatpak_support = glib2_dep.found() +cdata.set('HAVE_GLIB2', flatpak_support) + gst_option = get_option('gstreamer') gst_deps_def = { 'glib-2.0': {'version': '>=2.32.0'}, @@ -335,16 +341,16 @@ webrtc_dep = dependency('webrtc-audio-processing', summary({'WebRTC Echo Canceling': webrtc_dep.found()}, bool_yn: true, section: 'Misc dependencies') cdata.set('HAVE_WEBRTC', webrtc_dep.found()) -# On FreeBSD, epoll-shim library is required for eventfd() and timerfd() -epoll_shim_dep = (build_machine.system() == 'freebsd' +# On FreeBSD and MidnightBSD, epoll-shim library is required for eventfd() and timerfd() +epoll_shim_dep = (host_machine.system() == 'freebsd' or host_machine.system() == 'midnightbsd' ? dependency('epoll-shim', required: true) : dependency('', required: false)) -libinotify_dep = (build_machine.system() == 'freebsd' +libinotify_dep = (host_machine.system() == 'freebsd' or host_machine.system() == 'midnightbsd' ? dependency('libinotify', required: true) : dependency('', required: false)) -# On FreeBSD, libintl library is required for gettext +# On FreeBSD and MidnightBSD, libintl library is required for gettext libintl_dep = cc.find_library('intl', required: false) if not libintl_dep.found() libintl_dep = dependency('intl', required: false) @@ -355,8 +361,8 @@ need_alsa = get_option('pipewire-alsa').enabled() or 'media-session' in get_opti alsa_dep = dependency('alsa', version : '>=1.1.7', required: need_alsa) summary({'pipewire-alsa': alsa_dep.found()}, bool_yn: true) -if build_machine.system() == 'freebsd' -# On FreeBSD the OpenSSL library may come from base or a package. +if host_machine.system() == 'freebsd' or host_machine.system() == 'midnightbsd' +# On FreeBSD and MidnightBSD the OpenSSL library may come from base or a package. # Check for a package first and fallback to the base library if we can't find it via pkgconfig openssl_lib = dependency('openssl', required: false) if not openssl_lib.found() diff --git a/meson_options.txt b/meson_options.txt index 9e1466738b5e243bfdf9fd695b6fc7953fbbe8c6..e6a5623e02039d23cd0fa070f44939ada40fdf49 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -116,6 +116,10 @@ option('bluez5-codec-lc3plus', description: 'Enable LC3plus open source codec implementation', type: 'feature', value: 'auto') +option('bluez5-codec-opus', + description: 'Enable Opus open source codec implementation', + type: 'feature', + value: 'auto') option('control', description: 'Enable control spa plugin integration', type: 'feature', @@ -245,3 +249,11 @@ option('legacy-rtkit', description: 'Build legacy rtkit module', type: 'boolean', value: 'true') +option('avb', + description: 'Enable AVB code', + type: 'feature', + value: 'auto') +option('flatpak', + description: 'Enable Flatpak support', + type: 'feature', + value: 'enabled') diff --git a/pipewire-alsa/alsa-plugins/ctl_pipewire.c b/pipewire-alsa/alsa-plugins/ctl_pipewire.c index fd5dcc05a237c566ccb70910b1fdd03aba1a8a34..71f02f90ede795fb7a5aa5b47f79935de3589777 100644 --- a/pipewire-alsa/alsa-plugins/ctl_pipewire.c +++ b/pipewire-alsa/alsa-plugins/ctl_pipewire.c @@ -532,6 +532,7 @@ static int set_volume_mute(snd_ctl_pipewire_t *ctl, const char *name, struct vol spa_pod_builder_add(&b, SPA_PARAM_ROUTE_index, SPA_POD_Int(id), SPA_PARAM_ROUTE_device, SPA_POD_Int(device_id), + SPA_PARAM_ROUTE_save, SPA_POD_Bool(true), 0); spa_pod_builder_prop(&b, SPA_PARAM_ROUTE_props, 0); diff --git a/pipewire-alsa/alsa-plugins/pcm_pipewire.c b/pipewire-alsa/alsa-plugins/pcm_pipewire.c index 16940b1fcc04e6d4150a0f916d71aa011ad974c8..412b71fc26e6f8309cf437a48d5730d672c003e4 100644 --- a/pipewire-alsa/alsa-plugins/pcm_pipewire.c +++ b/pipewire-alsa/alsa-plugins/pcm_pipewire.c @@ -25,7 +25,7 @@ #define __USE_GNU #include <limits.h> -#ifndef __FreeBSD__ +#if !defined(__FreeBSD__) && !defined(__MidnightBSD__) #include <byteswap.h> #endif #include <sys/shm.h> @@ -726,6 +726,7 @@ static int snd_pcm_pipewire_sw_params(snd_pcm_ioplug_t * io, { snd_pcm_pipewire_t *pw = io->private_data; + pw_thread_loop_lock(pw->main_loop); if (pw->stream) { snd_pcm_uframes_t min_avail; snd_pcm_sw_params_get_avail_min( sw_params, &min_avail); @@ -746,6 +747,7 @@ static int snd_pcm_pipewire_sw_params(snd_pcm_ioplug_t * io, } else { pw_log_debug("%p: sw_params pre-prepare noop", pw); } + pw_thread_loop_unlock(pw->main_loop); return 0; } diff --git a/pipewire-jack/src/meson.build b/pipewire-jack/src/meson.build index bb1a7f0e972c5cae2220bafc9546666e2e08c746..20d1ccf8ea9e95b45c917cbfc997c17ec7c5afd1 100644 --- a/pipewire-jack/src/meson.build +++ b/pipewire-jack/src/meson.build @@ -78,7 +78,7 @@ if get_option('jack-devel') == true endif pkgconfig.generate(filebase : 'jack', - libraries : [pipewire_jack, pipewire_jackserver], + libraries : [pipewire_jack], name : 'jack', description : 'PipeWire JACK API', version : '1.9.17', diff --git a/pipewire-jack/src/metadata.c b/pipewire-jack/src/metadata.c index d50948ff46ab6cda67e786f0279a40f0e1b3262e..da3d75f41b8ea3b296984e60432d02e52c0336a9 100644 --- a/pipewire-jack/src/metadata.c +++ b/pipewire-jack/src/metadata.c @@ -210,7 +210,7 @@ static int update_property(struct client *c, pthread_mutex_unlock(&globals.lock); if (c->property_callback && changed > 0) { - pw_log_info("emit %lu %s", subject, key); + pw_log_info("emit %"PRIu64" %s", (uint64_t)subject, key); c->property_callback(subject, key, change, c->property_arg); } return changed; diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index c4ef10aaf999102baa68e48314f38d066eb37034..7100a41b088b5d0c84aca37d99503ac17120db1b 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -62,12 +62,12 @@ #define DEFAULT_RT_MAX 88 -#define JACK_CLIENT_NAME_SIZE 128 +#define JACK_CLIENT_NAME_SIZE 256 #define JACK_PORT_NAME_SIZE 256 #define JACK_PORT_TYPE_SIZE 32 #define MONITOR_EXT " Monitor" -#define MAX_MIDI_MIX 1024 +#define MAX_MIX 1024 #define MAX_BUFFER_FRAMES 8192 #define MAX_ALIGN 16 @@ -106,9 +106,9 @@ static bool mlock_warned = false; #define OBJECT_CHUNK 8 #define RECYCLE_THRESHOLD 128 -typedef void (*mix2_func) (float *dst, float *src1, float *src2, int n_samples); +typedef void (*mix_func) (float *dst, float *src[], uint32_t n_src, bool aligned, uint32_t n_samples); -static mix2_func mix2; +static mix_func mix_function; struct object { struct spa_list link; @@ -400,6 +400,7 @@ struct client { unsigned int default_as_system:1; int self_connect_mode; int rt_max; + unsigned int fix_midi_events:1; jack_position_t jack_position; jack_transport_state_t jack_state; @@ -735,38 +736,40 @@ static struct buffer *dequeue_buffer(struct client *c, struct mix *mix) #if defined (__SSE__) #include <xmmintrin.h> -static void mix2_sse(float *dst, float *src1, float *src2, int n_samples) +static void mix_sse(float *dst, float *src[], uint32_t n_src, bool aligned, uint32_t n_samples) { - int n, unrolled; - __m128 in[2]; + uint32_t i, n, unrolled; + __m128 in[1]; - if (SPA_IS_ALIGNED(src1, 16) && - SPA_IS_ALIGNED(src2, 16) && - SPA_IS_ALIGNED(dst, 16)) - unrolled = n_samples / 4; + if (SPA_IS_ALIGNED(dst, 16) && aligned) + unrolled = n_samples & ~3; else unrolled = 0; - for (n = 0; unrolled--; n += 4) { - in[0] = _mm_load_ps(&src1[n]), - in[1] = _mm_load_ps(&src2[n]), - in[0] = _mm_add_ps(in[0], in[1]); + for (n = 0; n < unrolled; n += 4) { + in[0] = _mm_load_ps(&src[0][n]); + for (i = 1; i < n_src; i++) + in[0] = _mm_add_ps(in[0], _mm_load_ps(&src[i][n])); _mm_store_ps(&dst[n], in[0]); } for (; n < n_samples; n++) { - in[0] = _mm_load_ss(&src1[n]), - in[1] = _mm_load_ss(&src2[n]), - in[0] = _mm_add_ss(in[0], in[1]); + in[0] = _mm_load_ss(&src[0][n]); + for (i = 1; i < n_src; i++) + in[0] = _mm_add_ss(in[0], _mm_load_ss(&src[i][n])); _mm_store_ss(&dst[n], in[0]); } } #endif -static void mix2_c(float *dst, float *src1, float *src2, int n_samples) +static void mix_c(float *dst, float *src[], uint32_t n_src, bool aligned, uint32_t n_samples) { - int i; - for (i = 0; i < n_samples; i++) - dst[i] = src1[i] + src2[i]; + uint32_t n, i; + for (n = 0; n < n_samples; n++) { + float t = src[0][n]; + for (i = 1; i < n_src; i++) + t += src[i][n]; + dst[n] = t; + } } SPA_EXPORT @@ -797,7 +800,7 @@ void jack_get_version(int *major_ptr, int *minor_ptr, int *micro_ptr, int *proto } else { \ if (c->active) \ (expr); \ - pw_log_debug("skip " #callback \ + pw_log_debug("skip " #callback \ " cb:%p active:%d", c->callback, \ c->active); \ } \ @@ -814,6 +817,9 @@ void jack_get_version(int *major_ptr, int *minor_ptr, int *micro_ptr, int *proto res = c->callback(__VA_ARGS__); \ c->rt_locked = false; \ pthread_mutex_unlock(&c->rt_lock); \ + } else { \ + pw_log_debug("skip " #callback \ + " cb:%p", c->callback); \ } \ } \ res; \ @@ -868,6 +874,8 @@ static int do_sync(struct client *client) pw_log_warn("sync requested from callback"); return 0; } + if (client->last_res == -EPIPE) + return -EPIPE; client->last_res = 0; client->pending_sync = pw_proxy_sync((struct pw_proxy*)client->core, client->pending_sync); @@ -985,10 +993,20 @@ static size_t convert_from_midi(void *midi, void *buffer, size_t size) return b.state.offset; } -static void convert_to_midi(struct spa_pod_sequence **seq, uint32_t n_seq, void *midi) +static inline void fix_midi_event(uint8_t *data, size_t size) +{ + /* fixup NoteOn with vel 0 */ + if (size > 2 && (data[0] & 0xF0) == 0x90 && data[2] == 0x00) { + data[0] = 0x80 + (data[0] & 0x0F); + data[2] = 0x40; + } +} + +static void convert_to_midi(struct spa_pod_sequence **seq, uint32_t n_seq, void *midi, bool fix) { struct spa_pod_control *c[n_seq]; uint32_t i; + int res; for (i = 0; i < n_seq; i++) c[i] = spa_pod_control_first(&seq[i]->body); @@ -996,6 +1014,8 @@ static void convert_to_midi(struct spa_pod_sequence **seq, uint32_t n_seq, void while (true) { struct spa_pod_control *next = NULL; uint32_t next_index = 0; + uint8_t *data; + size_t size; for (i = 0; i < n_seq; i++) { if (!spa_pod_control_is_inside(&seq[i]->body, @@ -1010,12 +1030,17 @@ static void convert_to_midi(struct spa_pod_sequence **seq, uint32_t n_seq, void if (SPA_UNLIKELY(next == NULL)) break; + data = SPA_POD_BODY(&next->value); + size = SPA_POD_BODY_SIZE(&next->value); + switch(next->type) { case SPA_CONTROL_Midi: - jack_midi_event_write(midi, - next->offset, - SPA_POD_BODY(&next->value), - SPA_POD_BODY_SIZE(&next->value)); + if (fix) + fix_midi_event(data, size); + + if ((res = jack_midi_event_write(midi, next->offset, data, size)) < 0) + pw_log_warn("midi %p: can't write event: %s", midi, + spa_strerror(res)); break; } c[next_index] = spa_pod_control_next(c[next_index]); @@ -3259,17 +3284,17 @@ jack_client_t * jack_client_open (const char *client_name, "jack.properties", client->props); pw_context_conf_section_match_rules(client->context.context, "jack.rules", - &client->props->dict, execute_match, client); + &client->context.context->properties->dict, execute_match, client); support = pw_context_get_support(client->context.context, &n_support); - mix2 = mix2_c; + mix_function = mix_c; cpu_iface = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); if (cpu_iface) { #if defined (__SSE__) uint32_t flags = spa_cpu_get_flags(cpu_iface); if (flags & SPA_CPU_FLAG_SSE) - mix2 = mix2_sse; + mix_function = mix_sse; #endif } client->context.old_thread_utils = @@ -3336,6 +3361,8 @@ jack_client_t * jack_client_open (const char *client_name, } if (pw_properties_get(client->props, PW_KEY_NODE_NAME) == NULL) pw_properties_set(client->props, PW_KEY_NODE_NAME, client_name); + if (pw_properties_get(client->props, PW_KEY_NODE_GROUP) == NULL) + pw_properties_setf(client->props, PW_KEY_NODE_GROUP, "jack-%d", getpid()); if (pw_properties_get(client->props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_set(client->props, PW_KEY_NODE_DESCRIPTION, client_name); if (pw_properties_get(client->props, PW_KEY_MEDIA_TYPE) == NULL) @@ -3383,6 +3410,7 @@ jack_client_t * jack_client_open (const char *client_name, client->filter_name = pw_properties_get_bool(client->props, "jack.filter-name", false); client->locked_process = pw_properties_get_bool(client->props, "jack.locked-process", true); client->default_as_system = pw_properties_get_bool(client->props, "jack.default-as-system", false); + client->fix_midi_events = pw_properties_get_bool(client->props, "jack.fix-midi-events", true); client->self_connect_mode = SELF_CONNECT_ALLOW; if ((str = pw_properties_get(client->props, "jack.self-connect-mode")) != NULL) { @@ -3471,6 +3499,8 @@ int jack_client_close (jack_client_t *client) res = jack_deactivate(client); + clean_transport(c); + if (c->context.loop) pw_thread_loop_stop(c->context.loop); @@ -3556,8 +3586,9 @@ char *jack_get_internal_client_name (jack_client_t *client, SPA_EXPORT int jack_client_name_size (void) { - pw_log_trace("%d", JACK_CLIENT_NAME_SIZE); - return JACK_CLIENT_NAME_SIZE; + /* The JACK API specifies that this value includes the final NULL character. */ + pw_log_trace("%d", JACK_CLIENT_NAME_SIZE+1); + return JACK_CLIENT_NAME_SIZE+1; } SPA_EXPORT @@ -3744,12 +3775,12 @@ jack_native_thread_t jack_client_thread_id (jack_client_t *client) struct client *c = (struct client *) client; void *thr; - spa_return_val_if_fail(c != NULL, -EINVAL); + spa_return_val_if_fail(c != NULL, (pthread_t){0}); thr = pw_data_loop_get_thread(c->loop); if (thr == NULL) return pthread_self(); - return *(pthread_t*)thr; + return (pthread_t) thr; } SPA_EXPORT @@ -4397,13 +4428,14 @@ static void *get_buffer_input_float(struct port *p, jack_nframes_t frames) { struct mix *mix; struct buffer *b; - int layer = 0; void *ptr = NULL; + float *mix_ptr[MAX_MIX], *np; + uint32_t n_ptr = 0; + bool ptr_aligned = true; spa_list_for_each(mix, &p->mix, port_link) { struct spa_data *d; uint32_t offset, size; - void *np; pw_log_trace_fp("%p: port %s mix %d.%d get buffer %d", p->client, p->object->port.name, p->port_id, mix->id, frames); @@ -4417,14 +4449,20 @@ static void *get_buffer_input_float(struct port *p, jack_nframes_t frames) if (size / sizeof(float) < frames) continue; - np = SPA_PTROFF(d->data, offset, void); - if (layer++ == 0) { - ptr = np; - } else { - mix2(p->emptyptr, ptr, np, frames); - ptr = p->emptyptr; - p->zeroed = false; - } + np = SPA_PTROFF(d->data, offset, float); + if (!SPA_IS_ALIGNED(np, 16)) + ptr_aligned = false; + + mix_ptr[n_ptr++] = np; + if (n_ptr == MAX_MIX) + break; + } + if (n_ptr == 1) { + ptr = mix_ptr[0]; + } else if (n_ptr > 1) { + ptr = p->emptyptr; + mix_function(ptr, mix_ptr, n_ptr, ptr_aligned, frames); + p->zeroed = false; } if (ptr == NULL) ptr = init_buffer(p); @@ -4435,7 +4473,7 @@ static void *get_buffer_input_midi(struct port *p, jack_nframes_t frames) { struct mix *mix; void *ptr = p->emptyptr; - struct spa_pod_sequence *seq[MAX_MIDI_MIX]; + struct spa_pod_sequence *seq[MAX_MIX]; uint32_t n_seq = 0; jack_midi_clear_buffer(ptr); @@ -4459,10 +4497,10 @@ static void *get_buffer_input_midi(struct port *p, jack_nframes_t frames) continue; seq[n_seq++] = pod; - if (n_seq == MAX_MIDI_MIX) + if (n_seq == MAX_MIX) break; } - convert_to_midi(seq, n_seq, ptr); + convert_to_midi(seq, n_seq, ptr, p->client->fix_midi_events); return ptr; } diff --git a/pipewire-v4l2/src/pipewire-v4l2.c b/pipewire-v4l2/src/pipewire-v4l2.c index 47e1721c24fc04588b7c1a96d1aa23cf20f3a578..d2e13dbf741e56163abc6dff0803b85c1b5f1827 100644 --- a/pipewire-v4l2/src/pipewire-v4l2.c +++ b/pipewire-v4l2/src/pipewire-v4l2.c @@ -1756,7 +1756,7 @@ static int v4l2_ioctl(int fd, unsigned long int request, void *arg) if ((file = find_file(fd)) == NULL) return globals.old_fops.ioctl(fd, request, arg); -#ifdef __FreeBSD__ +#if defined(__FreeBSD__) || defined(__MidnightBSD__) if (arg == NULL && (request & IOC_DIRMASK != IOC_VOID)) { #else if (arg == NULL && (_IOC_DIR(request) & (_IOC_WRITE | _IOC_READ))) { diff --git a/pipewire-v4l2/src/v4l2-func.c b/pipewire-v4l2/src/v4l2-func.c index 96e3ce76916836cfd9f38f18adb55b5a955833dd..c28e8342ca75dda0b2580949b5ced1544de5572c 100644 --- a/pipewire-v4l2/src/v4l2-func.c +++ b/pipewire-v4l2/src/v4l2-func.c @@ -22,6 +22,16 @@ * DEALINGS IN THE SOFTWARE. */ + +/* + * We need to export open* etc., but _FORTIFY_SOURCE defines conflicting + * always_inline versions. Disable _FORTIFY_SOURCE for this file, so we + * can define our overrides. + */ +#ifdef _FORTIFY_SOURCE +#undef _FORTIFY_SOURCE +#endif + #include <stdio.h> #include <errno.h> #include <fcntl.h> diff --git a/po/LINGUAS b/po/LINGUAS index 981c7282ef7666b04d17d9c5021daa25980e8942..2532833e615678f968840d7d21edf5efe276c281 100644 --- a/po/LINGUAS +++ b/po/LINGUAS @@ -21,6 +21,7 @@ hu id it ja +ka kk kn ko diff --git a/po/ca.po b/po/ca.po index b5c3c81e668a64c726849be4dbeed28668c61b65..6a9dc9f8fc60eff0342bd1c87e15886a1842c5fc 100644 --- a/po/ca.po +++ b/po/ca.po @@ -5,6 +5,7 @@ # Xavier Conde Rueda <xavi.conde@gmail.com>, 2008. # Agustà Grau <fletxa@gmail.com>, 2009. # Judith Pintó Subirada <judithp@gmail.com> +# Jordi Mas i Herǹandez, <jmas@softcatala.org>, 2022 # # This file is translated according to the glossary and style guide of # Softcatalà . If you plan to modify this file, please read first the page @@ -26,11 +27,10 @@ msgid "" msgstr "" "Project-Id-Version: pipewire\n" -"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" -"issues/new\n" +"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" -"PO-Revision-Date: 2012-01-30 09:52+0000\n" -"Last-Translator: Josep Torné Llavall <josep.torne@gmail.com>\n" +"PO-Revision-Date: 2022-09-01 19:24+0000\n" +"Last-Translator: Jordi Mas i Herǹandez, <jmas@softcatala.org>,\n" "Language-Team: Catalan <fedora@softcatala.net>\n" "Language: ca\n" "MIME-Version: 1.0\n" @@ -45,14 +45,18 @@ msgid "" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" +"%s [opcions]\n" +" -h, --help Mostra aquesta ajuda\n" +" --version Mostra la versió\n" +" -c, --config Carrega la configuració (predeterminada %s)\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" -msgstr "" +msgstr "Sistema multimèdia PipeWire" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" -msgstr "" +msgstr "Inicia el sistema multimèdia PipeWire" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 @@ -66,7 +70,7 @@ msgstr "Mòdem" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" -msgstr "" +msgstr "Dispositiu desconegut" #: src/tools/pw-cat.c:991 #, c-format @@ -77,9 +81,13 @@ msgid "" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" +"%s [opcions] <fitxer>\n" +" -h, --help Mostra aquesta ajuda\n" +" --version Mostra la versió\n" +" -v, --verbose Habilita les operacions detallades\n" #: src/tools/pw-cat.c:998 -#, c-format +#, c-format, fuzzy msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" @@ -90,42 +98,58 @@ msgid "" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" -" the rate is the one of the source " -"file\n" +" the rate is the one of the source file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" +"-R, --remote Nom del dimoni remot\n" +" --media-type Estableix el tipus de mitjà (per defecte %s)\n" +" --media-category Estableix la categoria dels mitjans (per defecte %s)\n" +" --media-role Estableix el rol del mitjà (per defecte %s)\n" +" --target Estableix l'objectiu del node (per defecte %s)\n" +" 0 vol dir que no enllaça\n" +" --latency Estableix latència del node (per defecte %s)\n" +" Xunit (unitat = s, ms, us, ns)\n" +" o mostres directes (256)\n" +" la taxa és la del fitxer d'origen\n" +" --list-targets Llista d'objectius disponibles per a --target" #: src/tools/pw-cat.c:1016 -#, c-format +#, c-format, fuzzy msgid "" -" --rate Sample rate (req. for rec) (default " -"%u)\n" -" --channels Number of channels (req. for rec) " -"(default %u)\n" +" --rate Sample rate (req. for rec) (default %u)\n" +" --channels Number of channels (req. for rec) (default %u)\n" " --channel-map Channel map\n" -" one of: \"stereo\", " -"\"surround-51\",... or\n" -" comma separated list of channel " -"names: eg. \"FL,FR\"\n" -" --format Sample format %s (req. for rec) " -"(default %s)\n" +" one of: \"stereo\", \"surround-51\",... or\n" +" comma separated list of channel names: eg. \"FL,FR\"\n" +" --format Sample format %s (req. for rec) (default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" -" -q --quality Resampler quality (0 - 15) (default " -"%d)\n" +" -q --quality Resampler quality (0 - 15) (default %d)\n" "\n" msgstr "" +"--rate Freqüència de mostreig (req. per rec) (predeterminat %u)\n" +" --channels Nombre de canals (req. per rec) (predeterminat %u)\n" +" --channel-map Mapa de canals\n" +" un dels següents: \"estèreo\", \"surround-51\",... o\n" +" Llista separada per comes dels noms dels canals: per exemple. \"FL,FR\"\n" +" --format Format de mostra %s (req. per a rec) (predeterminat %s)\n" +" --volume Volum de flux 0-1.0 (predeterminat %.3f)\n" +" -q --qualitat Remostrador de qualitat (0 - 15) (per defecte %d)" #: src/tools/pw-cat.c:1033 +#, fuzzy msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" +"-p, --playback Mode de reproducció\n" +" -r, --record mode d'enregistrament\n" +" -m, --midi Mode MIDI" #: src/tools/pw-cli.c:2932 -#, c-format +#, c-format, fuzzy msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" @@ -134,10 +158,15 @@ msgid "" " -r, --remote Remote daemon name\n" "\n" msgstr "" +"%s ]opcions] ]ordre]\n" +" -h, --help Mostra aquesta ajuda\n" +" --version Mostra la versió\n" +" -d, --daemon Inicia com a dimoni (fals predeterminat)\n" +" -r, --remote Nom del dimoni remot" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" -msgstr "" +msgstr "Pro Audio" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 @@ -252,25 +281,23 @@ msgstr "Entrada analògica" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" -msgstr "" +msgstr "Micròfon de l'acoblador" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 msgid "Headset Microphone" -msgstr "" +msgstr "Micròfon d'auriculars" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "Sortida analògica" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 -#, fuzzy msgid "Headphones 2" -msgstr "Auriculars" +msgstr "Auriculars 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 -#, fuzzy msgid "Headphones Mono Output" -msgstr "Sortida mono analògica" +msgstr "Sortida mono dels auriculars" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" @@ -297,49 +324,41 @@ msgid "Digital Input (S/PDIF)" msgstr "Entrada digital (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 -#, fuzzy msgid "Multichannel Input" -msgstr "Multicanal" +msgstr "Entrada multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 -#, fuzzy msgid "Multichannel Output" -msgstr "Multicanal" +msgstr "Sortida multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 -#, fuzzy msgid "Game Output" -msgstr "Sortida %s" +msgstr "Sortida del joc" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 -#, fuzzy msgid "Chat Output" -msgstr "Sortida %s" +msgstr "Sortida del xat" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 -#, fuzzy msgid "Chat Input" -msgstr "Entrada %s" +msgstr "Entrada del xat" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 -#, fuzzy msgid "Virtual Surround 7.1" -msgstr "Envoltant analògic 7.1" +msgstr "Envoltant virtual 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Mono analògic" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 -#, fuzzy msgid "Analog Mono (Left)" -msgstr "Mono analògic" +msgstr "Mono analògic (esquerra)" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 -#, fuzzy msgid "Analog Mono (Right)" -msgstr "Mono analògic" +msgstr "Mono analògic (dreta)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" @@ -364,13 +383,12 @@ msgstr "Estèreo" #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" -msgstr "Auricular" +msgstr "Auriculars" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 -#, fuzzy msgid "Speakerphone" -msgstr "Altaveu" +msgstr "Altaveu del telèfon" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 @@ -379,23 +397,23 @@ msgstr "Multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" -msgstr "So envoltant analògic 2.1" +msgstr "Envoltant analògic 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" -msgstr "So envoltant analògic 3.0" +msgstr "Envoltant analògic 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" -msgstr "So envoltant analògic 4.1" +msgstr "Envoltant analògic 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" -msgstr "Envoltant analògic 4.0 " +msgstr "Envoltant analògic 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" -msgstr "Envoltant analògic 4.1 " +msgstr "Envoltant analògic 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" @@ -407,15 +425,15 @@ msgstr "Envoltant analògic 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" -msgstr "So envoltant analògic 6.0" +msgstr "Envoltant analògic 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" -msgstr "So envoltant analògic 6.1" +msgstr "Envoltant analògic 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" -msgstr "So envoltant analògic 7.0" +msgstr "Envoltant analògic 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" @@ -431,11 +449,11 @@ msgstr "Envoltant digital 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" -msgstr "Envolvent digital 5.1 (IEC958/AC3)" +msgstr "Envoltant digital 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" -msgstr "So envoltant digital 5.1 (IEC958/DTS)" +msgstr "Envoltant digital 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" @@ -443,15 +461,15 @@ msgstr "Estèreo digital (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" -msgstr "So envoltant digital 5.1 (HDMI)" +msgstr "Envoltant digital 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" -msgstr "" +msgstr "Xat" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" -msgstr "" +msgstr "Joc" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" @@ -466,18 +484,16 @@ msgid "Digital Stereo Duplex (IEC958)" msgstr "Dúplex estèreo digital (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 -#, fuzzy msgid "Multichannel Duplex" -msgstr "Multicanal" +msgstr "Dúplex Multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 -#, fuzzy msgid "Stereo Duplex" -msgstr "Dúplex estèreo analògic" +msgstr "Dúplex estèreo" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" -msgstr "" +msgstr "Xat mono + 7.1 envoltant" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format @@ -492,115 +508,87 @@ msgstr "Entrada %s" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" -"snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " -"ms).\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " -"to the ALSA developers." +"snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgid_plural "" -"snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " -"ms).\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " -"to the ALSA developers." +"snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgstr[0] "" -"snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu bytes (%lu " -"ms).\n" -"Probablement es tracta d'un error del controlador de l'ALSA '%s'. Informeu " -"d'aquest incident als desenvolupadors de l'ALSA." +"snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu bytes (%lu ms).\n" +"Probablement es tracta d'un error del controlador de l'ALSA '%s'. Informeu d'aquest incident als desenvolupadors de l'ALSA." msgstr[1] "" -"snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu bytes (%lu " -"ms).\n" -"Probablement es tracta d'un error del controlador de l'ALSA '%s'. Informeu " -"d'aquest incident als desenvolupadors de l'ALSA." +"snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu bytes (%lu ms).\n" +"Probablement es tracta d'un error del controlador de l'ALSA '%s'. Informeu d'aquest incident als desenvolupadors de l'ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" -"snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" -"%lu ms).\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " -"to the ALSA developers." +"snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgid_plural "" -"snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" -"%lu ms).\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " -"to the ALSA developers." +"snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgstr[0] "" -"snd_pcm_delay() ha retornat un valor excepcionalment gran: %li bytes (%s%lu " -"ms).\n" -"Probablement es tracta d'un error del controlador de l'ALSA '%s'. Informeu " -"d'aquest incident als desenvolupadors de l'ALSA." +"snd_pcm_delay() ha retornat un valor excepcionalment gran: %li bytes (%s%lu ms).\n" +"Probablement es tracta d'un error del controlador de l'ALSA '%s'. Informeu d'aquest incident als desenvolupadors de l'ALSA." msgstr[1] "" -"snd_pcm_delay() ha retornat un valor excepcionalment gran: %li bytes (%s%lu " -"ms).\n" -"Probablement es tracta d'un error del controlador de l'ALSA '%s'. Informeu " -"d'aquest incident als desenvolupadors de l'ALSA." +"snd_pcm_delay() ha retornat un valor excepcionalment gran: %li bytes (%s%lu ms).\n" +"Probablement es tracta d'un error del controlador de l'ALSA '%s'. Informeu d'aquest incident als desenvolupadors de l'ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" -"snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " -"%lu.\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " -"to the ALSA developers." +"snd_pcm_avail_delay() returned strange values: delay %lu is less than avail %lu.\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgstr "" -"snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu bytes (%lu " -"ms).\n" -"Probablement es tracta d'un error del controlador d'ALSA «%s». Informeu " -"d'aquest problema als desenvolupadors d'ALSA." +"snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu bytes (%lu ms).\n" +"Probablement es tracta d'un error del controlador d'ALSA «%s». Informeu d'aquest problema als desenvolupadors d'ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" -"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " -"(%lu ms).\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " -"to the ALSA developers." +"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte (%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgid_plural "" -"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " -"(%lu ms).\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " -"to the ALSA developers." +"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes (%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgstr[0] "" -"snd_pcm_mmap_begin() ha retornat un valor excepcionalment gran: %lu bytes " -"(%lu ms).\n" -"Probablement es tracta d'un error del controlador de l'ALSA '%s'. Informeu " -"d'aquest incident als desenvolupadors de l'ALSA." +"snd_pcm_mmap_begin() ha retornat un valor excepcionalment gran: %lu bytes (%lu ms).\n" +"Probablement es tracta d'un error del controlador de l'ALSA '%s'. Informeu d'aquest incident als desenvolupadors de l'ALSA." msgstr[1] "" -"snd_pcm_mmap_begin() ha retornat un valor excepcionalment gran: %lu bytes " -"(%lu ms).\n" -"Probablement es tracta d'un error del controlador de l'ALSA '%s'. Informeu " -"d'aquest incident als desenvolupadors de l'ALSA." +"snd_pcm_mmap_begin() ha retornat un valor excepcionalment gran: %lu bytes (%lu ms).\n" +"Probablement es tracta d'un error del controlador de l'ALSA '%s'. Informeu d'aquest incident als desenvolupadors de l'ALSA." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" -msgstr "" +msgstr "Passarel·la d'à udio (A2DP Source & HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" -msgstr "" +msgstr "Reproducció d'alta fidelitat (Sink A2DP, còdec %s)" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" -msgstr "" +msgstr "Dúplex d'alta fidelitat (A2DP Source/Sink, còdec %s)" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" -msgstr "" +msgstr "Reproducció d'alta fidelitat (A2DP Sink)" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" -msgstr "" +msgstr "Dúplex d'alta fidelitat (A2DP Source/Sink)" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" -msgstr "" +msgstr "Unitat d'ariculars pel cap (HSP/HFP, còdec %s)" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" -msgstr "" +msgstr "Unitat d'ariculars pel cap (HSP/HFP)" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" @@ -612,7 +600,7 @@ msgstr "Auricular" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" -msgstr "" +msgstr "Portable" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" @@ -620,13 +608,12 @@ msgstr "Cotxe" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" -msgstr "" +msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "Telèfon" #: spa/plugins/bluez5/bluez5-device.c:1181 -#, fuzzy msgid "Bluetooth" -msgstr "Entrada bluetooth" +msgstr "Bluetooth" diff --git a/po/gl.po b/po/gl.po index 5ceddce8e9f680963d1b6aea2480778436683651..8be384f17eb1df145ea03a26d9533bb9ef0b130e 100644 --- a/po/gl.po +++ b/po/gl.po @@ -4,25 +4,31 @@ # Translators: # bassball93 <bassball93@gmail.com>, 2011. # mbouzada <mbouzada@gmail.com>, 2011. -# Fran Dieguez <frandieguez@gnome.org>, 2012, 2019. # Marcos Lans <marcoslansgarza@gmail.com>, 2018. +# Fran Dieguez <frandieguez@gnome.org>, 2012-2022. +# msgid "" msgstr "" "Project-Id-Version: PipeWire\n" -"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" -"issues/new\n" -"POT-Creation-Date: 2021-04-18 16:54+0800\n" -"PO-Revision-Date: 2019-02-20 01:36+0200\n" +"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" +"issues\n" +"POT-Creation-Date: 2022-07-10 03:27+0000\n" +"PO-Revision-Date: 2022-08-23 09:47+0200\n" "Last-Translator: Fran Dieguez <frandieguez@gnome.org>\n" -"Language-Team: Galician\n" +"Language-Team: Galician <Proxecto Trasno <proxecto@trasno.gal>>\n" "Language: gl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Virtaal 0.7.1\n" - -#: src/daemon/pipewire.c:43 +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"X-Generator: Gtranslator 40.0\n" +"X-DL-Team: gl\n" +"X-DL-Module: PipeWire\n" +"X-DL-Branch: master\n" +"X-DL-Domain: po\n" +"X-DL-State: Translating\n" + +#: src/daemon/pipewire.c:46 #, c-format msgid "" "%s [options]\n" @@ -30,6 +36,11 @@ msgid "" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" +"%s [opcións]\n" +" -h, --help Mostra esta axuda\n" +" --version Mostrar versión\n" +" -c, --config Cargar configuración (Predeterminado " +"%s)\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" @@ -39,31 +50,52 @@ msgstr "Sistema multimedia PipeWire" msgid "Start the PipeWire Media System" msgstr "Iniciar o Sistema multimedia PipeWire" -#: src/examples/media-session/alsa-monitor.c:526 -#: spa/plugins/alsa/acp/compat.c:187 -msgid "Built-in Audio" -msgstr "Audio interno" +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:180 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:180 +#, c-format +msgid "Tunnel to %s/%s" +msgstr "Túnel a %s/%s" -#: src/examples/media-session/alsa-monitor.c:530 -#: spa/plugins/alsa/acp/compat.c:192 -msgid "Modem" -msgstr "Módem" +#: src/modules/module-fallback-sink.c:51 +#| msgid "Game Output" +msgid "Dummy Output" +msgstr "SaÃda de proba" -#: src/examples/media-session/alsa-monitor.c:539 +#: src/modules/module-pulse-tunnel.c:648 +#, c-format +msgid "Tunnel for %s@%s" +msgstr "Túnel para %s@%s" + +#: src/modules/module-zeroconf-discover.c:332 msgid "Unknown device" -msgstr "" +msgstr "Dispositivo descoñecido" + +#: src/modules/module-zeroconf-discover.c:344 +#, c-format +msgid "%s on %s@%s" +msgstr "%s en %s@%s" + +#: src/modules/module-zeroconf-discover.c:348 +#, c-format +msgid "%s on %s" +msgstr "%s en %s" -#: src/tools/pw-cat.c:991 +#: src/tools/pw-cat.c:784 #, c-format msgid "" -"%s [options] <file>\n" +"%s [options] [<file>|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" +"%s [opcións] [<ficheiro>|-]\n" +" -h, --help Mostrar esta axuda\n" +" --version Mostrar versión\n" +" -v, --verbose Activar operacións verbosas\n" +"\n" -#: src/tools/pw-cat.c:998 +#: src/tools/pw-cat.c:791 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -77,11 +109,29 @@ msgid "" " or direct samples (256)\n" " the rate is the one of the source " "file\n" -" --list-targets List available targets for --target\n" +" -P --properties Set node properties\n" "\n" msgstr "" +" -R, --remote Nome do daemon remoto\n" +" --media-type Estabelecer o tipo de medio (por " +"omisión %s)\n" +" --media-category Estabelecer a categorÃa multimedia " +"(por omisión %s)\n" +" --media-role Estabelecer o rol multimedia (por " +"omisión %s)\n" +" --target Estabelecer o nodo obxectivo (por " +"omisión %s)\n" +" 0 significa non ligar\n" +" --latency Estabelecer a latencia do nodo (por " +"omisión %s)\n" +" Xunit (unidade = s, ms, us, ns)\n" +" ou mostras directas samples (256)\n" +" a taxa é un dos ficheiros de " +"orixe\n" +" -P --properties Estabelecer as propiedades do nodo\n" +"\n" -#: src/tools/pw-cat.c:1016 +#: src/tools/pw-cat.c:809 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -100,16 +150,38 @@ msgid "" "%d)\n" "\n" msgstr "" +" --rate Taxa de mostreo (solicitudes por " +"segundo) (por omisión %u)\n" +" --channels Número de canles (solicitudes por " +"segundo) (por omisión %u)\n" +" --channel-map Mapa de canles\n" +" un de: \"stereo\", " +"\"surround-51\",... or\n" +" lista separada por comas dos " +"nomes das canles: p.ex. \"FL,FR\"\n" +" --format Formato de mostras %s (solicitudes " +"por segundo) (por omisión %s)\n" +" --volume Volume do fluxo 0-1.0 (por omisión " +"%.3f)\n" +" -q --quality Calidade do remostreador (0 - 15) " +"(por omisión %d)\n" +"\n" -#: src/tools/pw-cat.c:1033 +#: src/tools/pw-cat.c:826 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" +" -d, --dsd DSD mode\n" "\n" msgstr "" +" -p, --playback Modo de reprodución\n" +" -r, --record Modo de grabación\n" +" -m, --midi Modo MIDI\n" +" -d, --dsd Modo DSD\n" +"\n" -#: src/tools/pw-cli.c:2932 +#: src/tools/pw-cli.c:3165 #, c-format msgid "" "%s [options] [command]\n" @@ -119,355 +191,352 @@ msgid "" " -r, --remote Remote daemon name\n" "\n" msgstr "" +"%s [opcións] [orde]\n" +" -h, --help Mostrar esta axuda\n" +" --version Mostrar versión\n" +" -d, --daemon Iniciar como demonio (Por omisión " +"falso)\n" +" -r, --remote Modo de demonio remoto\n" +"\n" -#: spa/plugins/alsa/acp/acp.c:290 +#: spa/plugins/alsa/acp/acp.c:321 msgid "Pro Audio" -msgstr "" +msgstr "Pro Audio" -#: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 -#: spa/plugins/bluez5/bluez5-device.c:1000 +#: spa/plugins/alsa/acp/acp.c:446 spa/plugins/alsa/acp/alsa-mixer.c:4648 +#: spa/plugins/bluez5/bluez5-device.c:1185 msgid "Off" msgstr "Apagado" -#: spa/plugins/alsa/acp/channelmap.h:466 -msgid "(invalid)" -msgstr "(incorrecto)" - -#: spa/plugins/alsa/acp/alsa-mixer.c:2709 +#: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Entrada" -#: spa/plugins/alsa/acp/alsa-mixer.c:2710 +#: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Entrada de estación acoplada (Docking Station)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2711 +#: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Micrófono da estación acoplada (Docking Station)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2712 +#: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Entrada de estación acoplada (Docking Station)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2713 -#: spa/plugins/alsa/acp/alsa-mixer.c:2804 +#: spa/plugins/alsa/acp/alsa-mixer.c:2656 +#: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Liña de entrada" -#: spa/plugins/alsa/acp/alsa-mixer.c:2714 -#: spa/plugins/alsa/acp/alsa-mixer.c:2798 -#: spa/plugins/bluez5/bluez5-device.c:1145 +#: spa/plugins/alsa/acp/alsa-mixer.c:2657 +#: spa/plugins/alsa/acp/alsa-mixer.c:2741 +#: spa/plugins/bluez5/bluez5-device.c:1357 msgid "Microphone" msgstr "Micrófono" -#: spa/plugins/alsa/acp/alsa-mixer.c:2715 -#: spa/plugins/alsa/acp/alsa-mixer.c:2799 +#: spa/plugins/alsa/acp/alsa-mixer.c:2658 +#: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Micrófono frontal" -#: spa/plugins/alsa/acp/alsa-mixer.c:2716 -#: spa/plugins/alsa/acp/alsa-mixer.c:2800 +#: spa/plugins/alsa/acp/alsa-mixer.c:2659 +#: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Micrófono traseiro" -#: spa/plugins/alsa/acp/alsa-mixer.c:2717 +#: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Micrófono externo" -#: spa/plugins/alsa/acp/alsa-mixer.c:2718 -#: spa/plugins/alsa/acp/alsa-mixer.c:2802 +#: spa/plugins/alsa/acp/alsa-mixer.c:2661 +#: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Micrófono interno" -#: spa/plugins/alsa/acp/alsa-mixer.c:2719 -#: spa/plugins/alsa/acp/alsa-mixer.c:2805 +#: spa/plugins/alsa/acp/alsa-mixer.c:2662 +#: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Radio" -#: spa/plugins/alsa/acp/alsa-mixer.c:2720 -#: spa/plugins/alsa/acp/alsa-mixer.c:2806 +#: spa/plugins/alsa/acp/alsa-mixer.c:2663 +#: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "VÃdeo" -#: spa/plugins/alsa/acp/alsa-mixer.c:2721 +#: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Control automático de ganancia" -#: spa/plugins/alsa/acp/alsa-mixer.c:2722 +#: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Sen control automático de ganancia" -#: spa/plugins/alsa/acp/alsa-mixer.c:2723 +#: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Enfatizador" -#: spa/plugins/alsa/acp/alsa-mixer.c:2724 +#: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Sen enfatizador" -#: spa/plugins/alsa/acp/alsa-mixer.c:2725 +#: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Amplificador" -#: spa/plugins/alsa/acp/alsa-mixer.c:2726 +#: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Sen amplificador" -#: spa/plugins/alsa/acp/alsa-mixer.c:2727 +#: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Enfatizador baixo" -#: spa/plugins/alsa/acp/alsa-mixer.c:2728 +#: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Sen enfatizador baixo" -#: spa/plugins/alsa/acp/alsa-mixer.c:2729 -#: spa/plugins/bluez5/bluez5-device.c:1150 +#: spa/plugins/alsa/acp/alsa-mixer.c:2672 +#: spa/plugins/bluez5/bluez5-device.c:1363 msgid "Speaker" msgstr "Altofalante" -#: spa/plugins/alsa/acp/alsa-mixer.c:2730 -#: spa/plugins/alsa/acp/alsa-mixer.c:2808 +#: spa/plugins/alsa/acp/alsa-mixer.c:2673 +#: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "Auriculares" -#: spa/plugins/alsa/acp/alsa-mixer.c:2797 +#: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Entrada analóxica" -#: spa/plugins/alsa/acp/alsa-mixer.c:2801 +#: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Micrófono do acople" -#: spa/plugins/alsa/acp/alsa-mixer.c:2803 +#: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Micrófono con auricular" -#: spa/plugins/alsa/acp/alsa-mixer.c:2807 +#: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "SaÃda analóxica" -#: spa/plugins/alsa/acp/alsa-mixer.c:2809 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" -msgstr "Auriculares" +msgstr "Auriculares 2" -#: spa/plugins/alsa/acp/alsa-mixer.c:2810 +#: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "SaÃda monoaural para auriculares" -#: spa/plugins/alsa/acp/alsa-mixer.c:2811 +#: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Liña de saÃda" -#: spa/plugins/alsa/acp/alsa-mixer.c:2812 +#: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "SaÃda monoaural analóxica" -#: spa/plugins/alsa/acp/alsa-mixer.c:2813 +#: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Altofalantes" -#: spa/plugins/alsa/acp/alsa-mixer.c:2814 +#: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" -#: spa/plugins/alsa/acp/alsa-mixer.c:2815 +#: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "SaÃda dixital (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2816 +#: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Entrada dixital (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2817 +#: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Entrada multicanle" -#: spa/plugins/alsa/acp/alsa-mixer.c:2818 +#: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "SaÃda multicanle" -#: spa/plugins/alsa/acp/alsa-mixer.c:2819 +#: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "SaÃda do xogo" -#: spa/plugins/alsa/acp/alsa-mixer.c:2820 -#: spa/plugins/alsa/acp/alsa-mixer.c:2821 +#: spa/plugins/alsa/acp/alsa-mixer.c:2763 +#: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "SaÃda do chat" -#: spa/plugins/alsa/acp/alsa-mixer.c:2822 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" -msgstr "SaÃda do chat" +msgstr "Entrada de chat" -#: spa/plugins/alsa/acp/alsa-mixer.c:2823 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" -msgstr "Sumideiro envolvente virtual" +msgstr "Envolvente virtual 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4527 +#: spa/plugins/alsa/acp/alsa-mixer.c:4471 msgid "Analog Mono" msgstr "Monoaural analóxico" -#: spa/plugins/alsa/acp/alsa-mixer.c:4528 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Analog Mono (Left)" -msgstr "Monoaural analóxico" +msgstr "Monoaural analóxico (Esquerda)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4529 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Mono (Right)" -msgstr "Monoaural analóxico" +msgstr "Monoaural analóxico (Dereita)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. -#: spa/plugins/alsa/acp/alsa-mixer.c:4530 -#: spa/plugins/alsa/acp/alsa-mixer.c:4538 -#: spa/plugins/alsa/acp/alsa-mixer.c:4539 +#: spa/plugins/alsa/acp/alsa-mixer.c:4474 +#: spa/plugins/alsa/acp/alsa-mixer.c:4482 +#: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Stereo" msgstr "Estéreo analóxico" -#: spa/plugins/alsa/acp/alsa-mixer.c:4531 +#: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Mono" msgstr "Mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4532 +#: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Stereo" msgstr "Estéreo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4540 -#: spa/plugins/alsa/acp/alsa-mixer.c:4698 -#: spa/plugins/bluez5/bluez5-device.c:1135 +#: spa/plugins/alsa/acp/alsa-mixer.c:4484 +#: spa/plugins/alsa/acp/alsa-mixer.c:4642 +#: spa/plugins/bluez5/bluez5-device.c:1345 msgid "Headset" msgstr "Auriculares con micro" -#: spa/plugins/alsa/acp/alsa-mixer.c:4541 -#: spa/plugins/alsa/acp/alsa-mixer.c:4699 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:4485 +#: spa/plugins/alsa/acp/alsa-mixer.c:4643 msgid "Speakerphone" msgstr "Altofalante" -#: spa/plugins/alsa/acp/alsa-mixer.c:4542 -#: spa/plugins/alsa/acp/alsa-mixer.c:4543 +#: spa/plugins/alsa/acp/alsa-mixer.c:4486 +#: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Multichannel" msgstr "Multicanle" -#: spa/plugins/alsa/acp/alsa-mixer.c:4544 +#: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Analog Surround 2.1" msgstr "Envolvente analóxico 2.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4545 +#: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Analog Surround 3.0" msgstr "Envolvente analóxico 3.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4546 +#: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Analog Surround 3.1" msgstr "Envolvente analóxico 3.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4547 +#: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Analog Surround 4.0" msgstr "Envolvente analóxico 4.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4548 +#: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Analog Surround 4.1" msgstr "Envolvente analóxico 4.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4549 +#: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Analog Surround 5.0" msgstr "Envolvente analóxico 5.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4550 +#: spa/plugins/alsa/acp/alsa-mixer.c:4494 msgid "Analog Surround 5.1" msgstr "Envolvente analóxico 5.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4551 +#: spa/plugins/alsa/acp/alsa-mixer.c:4495 msgid "Analog Surround 6.0" msgstr "Envolvente analóxico 6.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4552 +#: spa/plugins/alsa/acp/alsa-mixer.c:4496 msgid "Analog Surround 6.1" msgstr "Envolvente analóxico 6.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4553 +#: spa/plugins/alsa/acp/alsa-mixer.c:4497 msgid "Analog Surround 7.0" msgstr "Envolvente analóxico 7.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4554 +#: spa/plugins/alsa/acp/alsa-mixer.c:4498 msgid "Analog Surround 7.1" msgstr "Envolvente analóxico 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4555 +#: spa/plugins/alsa/acp/alsa-mixer.c:4499 msgid "Digital Stereo (IEC958)" msgstr "Estéreo dixital (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4556 +#: spa/plugins/alsa/acp/alsa-mixer.c:4500 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Envolvente dixital 4.0 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4557 +#: spa/plugins/alsa/acp/alsa-mixer.c:4501 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Envolvente dixital 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4558 +#: spa/plugins/alsa/acp/alsa-mixer.c:4502 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Envolvente dixital 5.1 (IEC958/ACDTS)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4559 +#: spa/plugins/alsa/acp/alsa-mixer.c:4503 msgid "Digital Stereo (HDMI)" msgstr "Estéreo dixital (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4560 +#: spa/plugins/alsa/acp/alsa-mixer.c:4504 msgid "Digital Surround 5.1 (HDMI)" msgstr "Envolvente dixital 5.1 (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4561 +#: spa/plugins/alsa/acp/alsa-mixer.c:4505 msgid "Chat" -msgstr "" +msgstr "Chat" -#: spa/plugins/alsa/acp/alsa-mixer.c:4562 +#: spa/plugins/alsa/acp/alsa-mixer.c:4506 msgid "Game" -msgstr "" +msgstr "Xogo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4696 +#: spa/plugins/alsa/acp/alsa-mixer.c:4640 msgid "Analog Mono Duplex" msgstr "Monoaural analóxico dúplex" -#: spa/plugins/alsa/acp/alsa-mixer.c:4697 +#: spa/plugins/alsa/acp/alsa-mixer.c:4641 msgid "Analog Stereo Duplex" msgstr "Estéreo analóxico dúplex" -#: spa/plugins/alsa/acp/alsa-mixer.c:4700 +#: spa/plugins/alsa/acp/alsa-mixer.c:4644 msgid "Digital Stereo Duplex (IEC958)" msgstr "Estéreo dixital dúplex (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4701 +#: spa/plugins/alsa/acp/alsa-mixer.c:4645 msgid "Multichannel Duplex" msgstr "Dúplex multicanle" -#: spa/plugins/alsa/acp/alsa-mixer.c:4702 +#: spa/plugins/alsa/acp/alsa-mixer.c:4646 msgid "Stereo Duplex" msgstr "Dúplex estéreo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4703 +#: spa/plugins/alsa/acp/alsa-mixer.c:4647 msgid "Mono Chat + 7.1 Surround" -msgstr "" +msgstr "Chat mono + envolvente 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4806 +#: spa/plugins/alsa/acp/alsa-mixer.c:4754 #, c-format msgid "%s Output" msgstr "SaÃda %s" -#: spa/plugins/alsa/acp/alsa-mixer.c:4813 +#: spa/plugins/alsa/acp/alsa-mixer.c:4761 #, c-format msgid "%s Input" msgstr "Entrada %s" -#: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 +#: spa/plugins/alsa/acp/alsa-util.c:1173 spa/plugins/alsa/acp/alsa-util.c:1267 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -490,16 +559,16 @@ msgstr[1] "" "O máis probábel é que sexa un erro do controlador ALSA «%s». Informe disto " "aos desenvolvedores de ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1241 +#: spa/plugins/alsa/acp/alsa-util.c:1239 #, c-format msgid "" -"snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" -"%lu ms).\n" +"snd_pcm_delay() returned a value that is exceptionally large: %li byte " +"(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" -"snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" -"%lu ms).\n" +"snd_pcm_delay() returned a value that is exceptionally large: %li bytes " +"(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" @@ -513,7 +582,7 @@ msgstr[1] "" "O máis probábel é que sexa un erro do controlador ALSA «%s». Informe disto " "aos desenvolvedores de ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1288 +#: spa/plugins/alsa/acp/alsa-util.c:1286 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -526,7 +595,7 @@ msgstr "" "O máis probábel é que sexa un erro do controlador ALSA «%s». Informe disto " "aos desenvolvedores de ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1331 +#: spa/plugins/alsa/acp/alsa-util.c:1329 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " @@ -549,62 +618,90 @@ msgstr[1] "" "O máis probábel é que sexa un erro do controlador ALSA «%s». Informe disto " "aos desenvolvedores de ALSA." -#: spa/plugins/bluez5/bluez5-device.c:1010 +#: spa/plugins/alsa/acp/channelmap.h:457 +msgid "(invalid)" +msgstr "(incorrecto)" + +#: spa/plugins/alsa/acp/compat.c:189 +msgid "Built-in Audio" +msgstr "Audio interno" + +#: spa/plugins/alsa/acp/compat.c:194 +msgid "Modem" +msgstr "Módem" + +#: spa/plugins/bluez5/bluez5-device.c:1196 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" -msgstr "" +msgstr "Porta de enlace de son (Orixe A2DP e HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1033 +#: spa/plugins/bluez5/bluez5-device.c:1221 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" -msgstr "" +msgstr "Reprodución de alta fidelidade (Sumideiro A2DP, códec %s)" -#: spa/plugins/bluez5/bluez5-device.c:1035 +#: spa/plugins/bluez5/bluez5-device.c:1224 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" -msgstr "" +msgstr "Dúplex de alta fidelidade (Orixe/sumideiro A2DP, códec %s)" -#: spa/plugins/bluez5/bluez5-device.c:1041 +#: spa/plugins/bluez5/bluez5-device.c:1232 msgid "High Fidelity Playback (A2DP Sink)" -msgstr "" +msgstr "Reprodución de alta fidelidade (Sumideiro A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:1043 +#: spa/plugins/bluez5/bluez5-device.c:1234 msgid "High Fidelity Duplex (A2DP Source/Sink)" -msgstr "" +msgstr "Dúplex de alta fidelidade (Orixe/sumideiro A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:1070 +#: spa/plugins/bluez5/bluez5-device.c:1262 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" -msgstr "" +msgstr "Unidade de auriculares de cabeza (HSP/HFP, códec %s)" -#: spa/plugins/bluez5/bluez5-device.c:1074 +#: spa/plugins/bluez5/bluez5-device.c:1267 msgid "Headset Head Unit (HSP/HFP)" -msgstr "" - -#: spa/plugins/bluez5/bluez5-device.c:1140 +msgstr "Unidade de auriculares de cabeza (HSP/HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:1346 +#: spa/plugins/bluez5/bluez5-device.c:1351 +#: spa/plugins/bluez5/bluez5-device.c:1358 +#: spa/plugins/bluez5/bluez5-device.c:1364 +#: spa/plugins/bluez5/bluez5-device.c:1370 +#: spa/plugins/bluez5/bluez5-device.c:1376 +#: spa/plugins/bluez5/bluez5-device.c:1382 +#: spa/plugins/bluez5/bluez5-device.c:1388 +#: spa/plugins/bluez5/bluez5-device.c:1394 msgid "Handsfree" msgstr "Sen mans" -#: spa/plugins/bluez5/bluez5-device.c:1155 +#: spa/plugins/bluez5/bluez5-device.c:1352 +#| msgid "Handsfree" +msgid "Handsfree (HFP)" +msgstr "Sen mans (HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:1369 msgid "Headphone" msgstr "Auriculares" -#: spa/plugins/bluez5/bluez5-device.c:1160 +#: spa/plugins/bluez5/bluez5-device.c:1375 msgid "Portable" msgstr "Portátil" -#: spa/plugins/bluez5/bluez5-device.c:1165 +#: spa/plugins/bluez5/bluez5-device.c:1381 msgid "Car" msgstr "Automóbil" -#: spa/plugins/bluez5/bluez5-device.c:1170 +#: spa/plugins/bluez5/bluez5-device.c:1387 msgid "HiFi" -msgstr "Hifi" +msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:1175 +#: spa/plugins/bluez5/bluez5-device.c:1393 msgid "Phone" msgstr "Teléfono" -#: spa/plugins/bluez5/bluez5-device.c:1181 -#, fuzzy +#: spa/plugins/bluez5/bluez5-device.c:1400 msgid "Bluetooth" -msgstr "Entrada de Bluetooth" +msgstr "Bluetooth" + +#: spa/plugins/bluez5/bluez5-device.c:1401 +msgid "Bluetooth (HFP)" +msgstr "Bluetooth (HFP)" diff --git a/po/hr.po b/po/hr.po index 64927d56d5188f2296cbed2c7d2df459fbc98952..e8c8c02119c0e65560f6d0f8ee121c4080d50b75 100644 --- a/po/hr.po +++ b/po/hr.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-03 12:56+0200\n" -"PO-Revision-Date: 2022-04-03 12:57+0200\n" +"POT-Creation-Date: 2022-06-30 12:50+0200\n" +"PO-Revision-Date: 2022-06-30 13:14+0200\n" "Last-Translator: gogo <trebelnik2@gmail.com>\n" "Language-Team: Croatian <https://translate.fedoraproject.org/projects/" "pipewire/pipewire/hr/>\n" @@ -34,8 +34,8 @@ msgstr "" " --version Prikaži inaÄicu\n" " -c, --config UÄitaj podeÅ¡avanje (Zadano %s)\n" -#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:190 -#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:190 +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:180 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:180 #, c-format msgid "Tunnel to %s/%s" msgstr "Tunel do %s/%s" @@ -44,41 +44,41 @@ msgstr "Tunel do %s/%s" msgid "Dummy Output" msgstr "Lažni izlaz" -#: src/modules/module-pulse-tunnel.c:545 +#: src/modules/module-pulse-tunnel.c:648 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunel za %s@%s" -#: src/modules/module-zeroconf-discover.c:313 +#: src/modules/module-zeroconf-discover.c:332 msgid "Unknown device" msgstr "Nepoznat ureÄ‘aj" -#: src/modules/module-zeroconf-discover.c:325 +#: src/modules/module-zeroconf-discover.c:344 #, c-format msgid "%s on %s@%s" msgstr "%s na %s@%s" -#: src/modules/module-zeroconf-discover.c:329 +#: src/modules/module-zeroconf-discover.c:348 #, c-format msgid "%s on %s" msgstr "%s na %s" -#: src/tools/pw-cat.c:1087 +#: src/tools/pw-cat.c:784 #, c-format msgid "" -"%s [options] <file>\n" +"%s [options] [<file>|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" -"%s [mogućnosti] <datoteka>\n" +"%s [mogućnosti] [<datoteka>|-]\n" " -h, --help Prikaži ovu pomoć\n" " --version Prikaži inaÄicu\n" " -v, --verbose Omogući opÅ¡irnije radnje\n" "\n" -#: src/tools/pw-cat.c:1094 +#: src/tools/pw-cat.c:791 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -92,7 +92,7 @@ msgid "" " or direct samples (256)\n" " the rate is the one of the source " "file\n" -" --list-targets List available targets for --target\n" +" -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Naziv udaljenog pozadinskog " @@ -111,11 +111,10 @@ msgstr "" " ili izravne uzorke (256)\n" " frekvencija je jednaka izvornoj " "datoteci\n" -" --list-targets Prikaži dostupna odrediÅ¡ta za --" -"target\n" +" -P --properties Postavi svojstva Ävora\n" "\n" -#: src/tools/pw-cat.c:1112 +#: src/tools/pw-cat.c:809 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -151,7 +150,7 @@ msgstr "" "15) (zadano je %d)\n" "\n" -#: src/tools/pw-cat.c:1129 +#: src/tools/pw-cat.c:826 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" @@ -165,7 +164,7 @@ msgstr "" " -d, --dsd DSD naÄin\n" "\n" -#: src/tools/pw-cli.c:3051 +#: src/tools/pw-cli.c:3165 #, c-format msgid "" "%s [options] [command]\n" @@ -188,8 +187,8 @@ msgstr "" msgid "Pro Audio" msgstr "Pro Audio" -#: spa/plugins/alsa/acp/acp.c:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 -#: spa/plugins/bluez5/bluez5-device.c:1159 +#: spa/plugins/alsa/acp/acp.c:446 spa/plugins/alsa/acp/alsa-mixer.c:4648 +#: spa/plugins/bluez5/bluez5-device.c:1161 msgid "Off" msgstr "IskljuÄeno" @@ -216,7 +215,7 @@ msgstr "Ulaz" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:1328 +#: spa/plugins/bluez5/bluez5-device.c:1330 msgid "Microphone" msgstr "Mikrofon" @@ -282,7 +281,7 @@ msgid "No Bass Boost" msgstr "Bez pojaÄanja basa" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:1333 +#: spa/plugins/bluez5/bluez5-device.c:1335 msgid "Speaker" msgstr "ZvuÄnik" @@ -397,7 +396,7 @@ msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 -#: spa/plugins/bluez5/bluez5-device.c:1318 +#: spa/plugins/bluez5/bluez5-device.c:1320 msgid "Headset" msgstr "SluÅ¡alice s mikrofonom" @@ -521,8 +520,7 @@ msgstr "%s izlaz" msgid "%s Input" msgstr "%s ulaz" -#: spa/plugins/alsa/acp/alsa-util.c:1173 -#: spa/plugins/alsa/acp/alsa-util.c:1267 +#: spa/plugins/alsa/acp/alsa-util.c:1173 spa/plugins/alsa/acp/alsa-util.c:1267 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -619,7 +617,7 @@ msgstr[2] "" "Najvjerojatnije je ovo greÅ¡ka ALSA upravljaÄkog programa '%s'. Prijavite " "problem ALSA razvijateljima." -#: spa/plugins/alsa/acp/channelmap.h:464 +#: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(neispravno)" @@ -631,62 +629,62 @@ msgstr "UgraÄ‘eni zvuk" msgid "Modem" msgstr "Modem" -#: spa/plugins/bluez5/bluez5-device.c:1170 +#: spa/plugins/bluez5/bluez5-device.c:1172 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "ZvuÄni pristupnik (A2DP izvor i HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1195 +#: spa/plugins/bluez5/bluez5-device.c:1197 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Reprodukcija visoke autentiÄnosti (A2DP slivnik, kôdek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1198 +#: spa/plugins/bluez5/bluez5-device.c:1200 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Telefonija visoke autentiÄnosti (A2DP slivnik, kôdek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1206 +#: spa/plugins/bluez5/bluez5-device.c:1208 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Reprodukcija visoke autentiÄnosti (A2DP slivnik)" -#: spa/plugins/bluez5/bluez5-device.c:1208 +#: spa/plugins/bluez5/bluez5-device.c:1210 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Telefonija visoke autentiÄnosti (A2DP izvor/slivnik)" -#: spa/plugins/bluez5/bluez5-device.c:1236 +#: spa/plugins/bluez5/bluez5-device.c:1238 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Jedinica sluÅ¡alice s mikrofonom (HSP/HFP, kôdek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1241 +#: spa/plugins/bluez5/bluez5-device.c:1243 msgid "Headset Head Unit (HSP/HFP)" msgstr "Jedinica sluÅ¡alice s mikrofonom (HSP/HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1323 +#: spa/plugins/bluez5/bluez5-device.c:1325 msgid "Handsfree" msgstr "Bez-ruku" -#: spa/plugins/bluez5/bluez5-device.c:1338 +#: spa/plugins/bluez5/bluez5-device.c:1340 msgid "Headphone" msgstr "SluÅ¡alice" -#: spa/plugins/bluez5/bluez5-device.c:1343 +#: spa/plugins/bluez5/bluez5-device.c:1345 msgid "Portable" msgstr "Prijenosnik" -#: spa/plugins/bluez5/bluez5-device.c:1348 +#: spa/plugins/bluez5/bluez5-device.c:1350 msgid "Car" msgstr "Automobil" -#: spa/plugins/bluez5/bluez5-device.c:1353 +#: spa/plugins/bluez5/bluez5-device.c:1355 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:1358 +#: spa/plugins/bluez5/bluez5-device.c:1360 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:1364 +#: spa/plugins/bluez5/bluez5-device.c:1366 msgid "Bluetooth" msgstr "Bluetooth" diff --git a/po/ka.po b/po/ka.po new file mode 100644 index 0000000000000000000000000000000000000000..1e78ffdfea21d246f9cd1d99e0391df811962d14 --- /dev/null +++ b/po/ka.po @@ -0,0 +1,661 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the pipewire package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: pipewire\n" +"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" +"issues/new\n" +"POT-Creation-Date: 2022-06-30 12:50+0200\n" +"PO-Revision-Date: 2022-07-25 13:11+0200\n" +"Last-Translator: Temuri Doghonadze <temuri.doghonadze@gmail.com>\n" +"Language-Team: Georgian <(nothing)>\n" +"Language: ka\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.1.1\n" + +#: src/daemon/pipewire.c:46 +#, c-format +msgid "" +"%s [options]\n" +" -h, --help Show this help\n" +" --version Show version\n" +" -c, --config Load config (Default %s)\n" +msgstr "" +"%s [პáƒáƒ áƒáƒ›áƒ”ტრები]\n" +" -h, --help áƒáƒ› დáƒáƒ®áƒ›áƒáƒ ების ჩვენებáƒ\n" +" --version ვერსიის ჩვენებáƒ\n" +" -c, --config ჩáƒáƒ¢áƒ•ირთვის კáƒáƒœáƒ¤áƒ˜áƒ’ურáƒáƒªáƒ˜áƒ (ნáƒáƒ’ულისხმები %s)\n" + +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:180 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:180 +#, c-format +msgid "Tunnel to %s/%s" +msgstr "გვირáƒáƒ‘ი %s/%s -მდე" + +#: src/modules/module-fallback-sink.c:51 +msgid "Dummy Output" +msgstr "ნულáƒáƒ•áƒáƒœáƒ˜ გáƒáƒ›áƒáƒ§áƒ•áƒáƒœáƒ" + +#: src/modules/module-pulse-tunnel.c:648 +#, c-format +msgid "Tunnel for %s@%s" +msgstr "გვირáƒáƒ‘ი %s@%s-სთვის" + +#: src/modules/module-zeroconf-discover.c:332 +msgid "Unknown device" +msgstr "უცნáƒáƒ‘ი მáƒáƒ¬áƒ§áƒáƒ‘ილáƒáƒ‘áƒ" + +#: src/modules/module-zeroconf-discover.c:344 +#, c-format +msgid "%s on %s@%s" +msgstr "%s %s@%s -ზე" + +#: src/modules/module-zeroconf-discover.c:348 +#, c-format +msgid "%s on %s" +msgstr "%s %s-ზე" + +#: src/tools/pw-cat.c:784 +#, c-format +msgid "" +"%s [options] [<file>|-]\n" +" -h, --help Show this help\n" +" --version Show version\n" +" -v, --verbose Enable verbose operations\n" +"\n" +msgstr "" +"%s [პáƒáƒ áƒáƒ›áƒ”ტრები] [<ფáƒáƒ˜áƒšáƒ˜>|-]\n" +" -h, --help áƒáƒ› დáƒáƒ®áƒ›áƒáƒ ების ჩვენებáƒ\n" +" --version ვერსიის ჩვენებáƒ\n" +" -v, --verbose დáƒáƒ›áƒáƒ¢áƒ”ბითი შეტყáƒáƒ‘ინებების გáƒáƒ›áƒáƒ¢áƒáƒœáƒ\n" +"\n" + +#: src/tools/pw-cat.c:791 +#, c-format +msgid "" +" -R, --remote Remote daemon name\n" +" --media-type Set media type (default %s)\n" +" --media-category Set media category (default %s)\n" +" --media-role Set media role (default %s)\n" +" --target Set node target (default %s)\n" +" 0 means don't link\n" +" --latency Set node latency (default %s)\n" +" Xunit (unit = s, ms, us, ns)\n" +" or direct samples (256)\n" +" the rate is the one of the source " +"file\n" +" -P --properties Set node properties\n" +"\n" +msgstr "" +" -R, --remote დáƒáƒ¨áƒáƒ ებული დემáƒáƒœáƒ˜áƒ¡ სáƒáƒ®áƒ”ლი\n" +" --media-type მედიის ტიპის დáƒáƒ§áƒ”ნებრ(ნáƒáƒ’ულისხმები %s)\n" +" --media-category მედირკáƒáƒ¢áƒ”გáƒáƒ იის დáƒáƒ§áƒ”ნებრ(ნáƒáƒ’ულისხმები %s)\n" +" --media-role მედიის რáƒáƒšáƒ˜áƒ¡ დáƒáƒ§áƒ”ნებრ(ნáƒáƒ’ულისხმები %s)\n" +" --target კვáƒáƒœáƒ«áƒ˜áƒ¡ სáƒáƒ›áƒ˜áƒ–ნის დáƒáƒ§áƒ”ნებრ(ნáƒáƒ’ულისხმები %s)\n" +" 0 ნიშნáƒáƒ•ს áƒáƒ მიბმáƒ\n" +" --latency კვáƒáƒœáƒ«áƒ˜áƒ¡ შეყáƒáƒ•ნების დáƒáƒ§áƒ”ნებრ(ნáƒáƒ’ულისხმები %s)\n" +" Xunit (ერთეული = s, ms, us, ns)\n" +" áƒáƒœ პირდáƒáƒžáƒ˜áƒ ი ნიმუშები (256)\n" +" მáƒáƒ©áƒ•ენებელი áƒáƒ ის ერთ-ერთი წყáƒáƒ áƒáƒ¡ " +"ფáƒáƒ˜áƒšáƒ˜\n" +" -P --properties კვáƒáƒœáƒ«áƒ˜áƒ¡ თვისებების დáƒáƒ§áƒ”ნებáƒ\n" + +#: src/tools/pw-cat.c:809 +#, c-format +msgid "" +" --rate Sample rate (req. for rec) (default " +"%u)\n" +" --channels Number of channels (req. for rec) " +"(default %u)\n" +" --channel-map Channel map\n" +" one of: \"stereo\", " +"\"surround-51\",... or\n" +" comma separated list of channel " +"names: eg. \"FL,FR\"\n" +" --format Sample format %s (req. for rec) " +"(default %s)\n" +" --volume Stream volume 0-1.0 (default %.3f)\n" +" -q --quality Resampler quality (0 - 15) (default " +"%d)\n" +"\n" +msgstr "" +" --rate სემპლის_სიჩქáƒáƒ ე (მáƒáƒ—ხáƒáƒ•ნილებრrec.) (ნáƒáƒ’ულისხმები %u)\n" +" --channels áƒáƒ ხების რáƒáƒáƒ“ენáƒáƒ‘რ(მáƒáƒ—ხáƒáƒ•ნილი ჩáƒáƒœáƒáƒ¬áƒ”რისთვის) (ნáƒáƒ’ულისხმები " +"%u)\n" +" --channel-map áƒáƒ ხის რუკáƒ\n" +" ერთ-ერთი: \"stereo\", " +"\"surround-51\",... áƒáƒœ\n" +" მძიმით გáƒáƒ›áƒáƒ§áƒáƒ¤áƒ˜áƒšáƒ˜ áƒáƒ ხის " +"სáƒáƒ®áƒ”ლების სიáƒ: მáƒáƒ’. \"FL, FR\"\n" +" --format ნიმუშის ფáƒáƒ მáƒáƒ¢áƒ˜ %s (მáƒáƒ—ხáƒáƒ•ნილებრrec.) " +"(ნáƒáƒ’ულისხმები %s)\n" +" --volume ნáƒáƒ™áƒáƒ“ის მáƒáƒªáƒ£áƒšáƒáƒ‘რ0-1.0 (ნáƒáƒ’ულისხმები %.3f)\n" +" -q --quality Resampler ხáƒáƒ ისხი (0 - 15) " +"(ნáƒáƒ’ულისხმები %d)\n" + +#: src/tools/pw-cat.c:826 +msgid "" +" -p, --playback Playback mode\n" +" -r, --record Recording mode\n" +" -m, --midi Midi mode\n" +" -d, --dsd DSD mode\n" +"\n" +msgstr "" +" -p, --playback დáƒáƒ™áƒ•რის რეჟიმი\n" +" -r, -- record ჩáƒáƒ¬áƒ”რის რეჟიმი\n" +" -m, --midi Midi რეჟიმი\n" +" -d, --dsd DSD რეჟიმი\n" +"\n" + +#: src/tools/pw-cli.c:3165 +#, c-format +msgid "" +"%s [options] [command]\n" +" -h, --help Show this help\n" +" --version Show version\n" +" -d, --daemon Start as daemon (Default false)\n" +" -r, --remote Remote daemon name\n" +"\n" +msgstr "" +"%s [პáƒáƒ áƒáƒ›áƒ”ტრები] [ბრძáƒáƒœáƒ”ბáƒ]\n" +" -h, --help áƒáƒ› დáƒáƒ®áƒ›áƒáƒ ების ჩვენებáƒ\n" +" --version ვერსიის ჩვენებáƒ\n" +" -d, --daemon დáƒáƒ¬áƒ§áƒ”ბრრáƒáƒ’áƒáƒ ც დემáƒáƒœáƒ˜ (ნáƒáƒ’ულისხმები " +"false)\n" +" -r, --remote დáƒáƒ¨áƒáƒ ებული დემáƒáƒœáƒ˜áƒ¡ სáƒáƒ®áƒ”ლი\n" + +#: spa/plugins/alsa/acp/acp.c:321 +msgid "Pro Audio" +msgstr "Pro Audio" + +#: spa/plugins/alsa/acp/acp.c:446 spa/plugins/alsa/acp/alsa-mixer.c:4648 +#: spa/plugins/bluez5/bluez5-device.c:1161 +msgid "Off" +msgstr "გáƒáƒ›áƒáƒ თული" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2652 +msgid "Input" +msgstr "შეყვáƒáƒœáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2653 +msgid "Docking Station Input" +msgstr "Docking Station-ის შეყვáƒáƒœáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2654 +msgid "Docking Station Microphone" +msgstr "Docking Station-ის მიკრáƒáƒ¤áƒáƒœáƒ˜" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2655 +msgid "Docking Station Line In" +msgstr "Docking Station Line In" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2656 +#: spa/plugins/alsa/acp/alsa-mixer.c:2747 +msgid "Line In" +msgstr "Line In" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2657 +#: spa/plugins/alsa/acp/alsa-mixer.c:2741 +#: spa/plugins/bluez5/bluez5-device.c:1330 +msgid "Microphone" +msgstr "მიკრáƒáƒ¤áƒáƒœáƒ˜" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2658 +#: spa/plugins/alsa/acp/alsa-mixer.c:2742 +msgid "Front Microphone" +msgstr "წინრმიკრáƒáƒ¤áƒáƒœáƒ˜" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2659 +#: spa/plugins/alsa/acp/alsa-mixer.c:2743 +msgid "Rear Microphone" +msgstr "უკáƒáƒœáƒ მიკფáƒáƒ¤áƒáƒœáƒ˜" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2660 +msgid "External Microphone" +msgstr "გáƒáƒ ე მიკრáƒáƒ¤áƒáƒœáƒ˜" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2661 +#: spa/plugins/alsa/acp/alsa-mixer.c:2745 +msgid "Internal Microphone" +msgstr "შიდრმიკრáƒáƒ¤áƒáƒœáƒ˜" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2662 +#: spa/plugins/alsa/acp/alsa-mixer.c:2748 +msgid "Radio" +msgstr "რáƒáƒ“იáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2663 +#: spa/plugins/alsa/acp/alsa-mixer.c:2749 +msgid "Video" +msgstr "ვიდეáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2664 +msgid "Automatic Gain Control" +msgstr "ხმის მáƒáƒ›áƒáƒ¢áƒ”ბის áƒáƒ•ტáƒáƒ›áƒáƒ¢áƒ£áƒ ი კáƒáƒœáƒ¢áƒ áƒáƒšáƒ˜" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2665 +msgid "No Automatic Gain Control" +msgstr "ხმის მáƒáƒ›áƒáƒ¢áƒ”ბის áƒáƒ•ტáƒáƒ›áƒáƒ¢áƒ£áƒ ი კáƒáƒœáƒ¢áƒ áƒáƒšáƒ˜áƒ¡ გáƒáƒ›áƒáƒ თვáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2666 +msgid "Boost" +msgstr "გáƒáƒ«áƒšáƒ˜áƒ”რებáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2667 +msgid "No Boost" +msgstr "გáƒáƒ«áƒšáƒ˜áƒ”რების გáƒáƒ ეშე" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2668 +msgid "Amplifier" +msgstr "გáƒáƒ›áƒáƒ«áƒšáƒ˜áƒ”რებელი" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2669 +msgid "No Amplifier" +msgstr "გáƒáƒ›áƒáƒ«áƒšáƒ˜áƒ”რებლის გáƒáƒ ეშე" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2670 +msgid "Bass Boost" +msgstr "Bass-ის გáƒáƒ«áƒšáƒ˜áƒ”რებáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2671 +msgid "No Bass Boost" +msgstr "Bass-ის გáƒáƒ«áƒšáƒ˜áƒ”რების გáƒáƒ ეშე" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2672 +#: spa/plugins/bluez5/bluez5-device.c:1335 +msgid "Speaker" +msgstr "დინáƒáƒ›áƒ˜áƒ™áƒ˜" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2673 +#: spa/plugins/alsa/acp/alsa-mixer.c:2751 +msgid "Headphones" +msgstr "ყურსáƒáƒªáƒ•áƒáƒ›áƒ”ბი" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2740 +msgid "Analog Input" +msgstr "áƒáƒœáƒáƒšáƒáƒ’ური შეყვáƒáƒœáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2744 +msgid "Dock Microphone" +msgstr "მისáƒáƒ›áƒáƒ’რებელი მიკრáƒáƒ¤áƒáƒœáƒ˜" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2746 +msgid "Headset Microphone" +msgstr "ყურსáƒáƒªáƒ•áƒáƒ›áƒ˜áƒ¡ მირáƒáƒ¤áƒáƒœáƒ˜" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2750 +msgid "Analog Output" +msgstr "áƒáƒœáƒáƒšáƒáƒ’ური გáƒáƒ›áƒáƒ¢áƒáƒœáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2752 +msgid "Headphones 2" +msgstr "ყურსáƒáƒªáƒ•áƒáƒ›áƒ”ბი 2" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2753 +msgid "Headphones Mono Output" +msgstr "ყურსáƒáƒªáƒ•áƒáƒ›áƒ”ბი მáƒáƒœáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2754 +msgid "Line Out" +msgstr "ხáƒáƒ–áƒáƒ•áƒáƒœáƒ˜ გáƒáƒ›áƒáƒ§áƒ•áƒáƒœáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2755 +msgid "Analog Mono Output" +msgstr "áƒáƒœáƒáƒšáƒáƒ’ური მáƒáƒœáƒ გáƒáƒ›áƒáƒ§áƒ•áƒáƒœáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2756 +msgid "Speakers" +msgstr "დინáƒáƒ›áƒ˜áƒ™áƒ”ბი" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2757 +msgid "HDMI / DisplayPort" +msgstr "HDMI / DisplayPort" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2758 +msgid "Digital Output (S/PDIF)" +msgstr "ციფრული გáƒáƒ›áƒáƒ§áƒ•áƒáƒœáƒ (S/PDIF)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2759 +msgid "Digital Input (S/PDIF)" +msgstr "ციფრული შეტáƒáƒœáƒ (S/PDIF)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2760 +msgid "Multichannel Input" +msgstr "მრáƒáƒ•áƒáƒšáƒáƒ ხიáƒáƒœáƒ˜ შეყვáƒáƒœáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2761 +msgid "Multichannel Output" +msgstr "მრáƒáƒ•áƒáƒšáƒáƒ ხიáƒáƒœáƒ˜ გáƒáƒ›áƒáƒ§áƒ•áƒáƒœáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2762 +msgid "Game Output" +msgstr "თáƒáƒ›áƒáƒ¨áƒ˜áƒ¡ გáƒáƒ›áƒáƒ§áƒ•áƒáƒœáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2763 +#: spa/plugins/alsa/acp/alsa-mixer.c:2764 +msgid "Chat Output" +msgstr "ჩáƒáƒ¢áƒ˜áƒ¡ გáƒáƒ›áƒáƒ§áƒ•áƒáƒœáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2765 +msgid "Chat Input" +msgstr "ჩáƒáƒ¢áƒ˜áƒ¡ შეყვáƒáƒœáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2766 +msgid "Virtual Surround 7.1" +msgstr "ვირტუáƒáƒšáƒ£áƒ ი სივრცითი ხმრ7.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4471 +msgid "Analog Mono" +msgstr "áƒáƒœáƒáƒšáƒáƒ’ური მáƒáƒœáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4472 +msgid "Analog Mono (Left)" +msgstr "áƒáƒœáƒáƒšáƒáƒ’ური მáƒáƒœáƒ (მáƒáƒ ცხენáƒ)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4473 +msgid "Analog Mono (Right)" +msgstr "áƒáƒœáƒáƒšáƒáƒ’ური მáƒáƒœáƒ (მáƒáƒ ჯვენáƒ)" + +#. Note: Not translated to "Analog Stereo Input", because the source +#. * name gets "Input" appended to it automatically, so adding "Input" +#. * here would lead to the source name to become "Analog Stereo Input +#. * Input". The same logic applies to analog-stereo-output, +#. * multichannel-input and multichannel-output. +#: spa/plugins/alsa/acp/alsa-mixer.c:4474 +#: spa/plugins/alsa/acp/alsa-mixer.c:4482 +#: spa/plugins/alsa/acp/alsa-mixer.c:4483 +msgid "Analog Stereo" +msgstr "áƒáƒœáƒáƒšáƒáƒ’ური სტერეáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4475 +msgid "Mono" +msgstr "მáƒáƒœáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4476 +msgid "Stereo" +msgstr "სტერეáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4484 +#: spa/plugins/alsa/acp/alsa-mixer.c:4642 +#: spa/plugins/bluez5/bluez5-device.c:1320 +msgid "Headset" +msgstr "ყურსáƒáƒªáƒ•áƒáƒ›áƒ”ბი & მიკრáƒáƒ¤áƒáƒœáƒ˜" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4485 +#: spa/plugins/alsa/acp/alsa-mixer.c:4643 +msgid "Speakerphone" +msgstr "სáƒáƒ›áƒáƒ’იდრდინáƒáƒ›áƒ˜áƒ™áƒ˜" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4486 +#: spa/plugins/alsa/acp/alsa-mixer.c:4487 +msgid "Multichannel" +msgstr "მრáƒáƒ•áƒáƒšáƒáƒ ხიáƒáƒœáƒ˜" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4488 +msgid "Analog Surround 2.1" +msgstr "áƒáƒœáƒáƒšáƒáƒ’ური სივრცითი 2.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4489 +msgid "Analog Surround 3.0" +msgstr "áƒáƒœáƒáƒšáƒáƒ’ური სივრცითი 3.0" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4490 +msgid "Analog Surround 3.1" +msgstr "áƒáƒœáƒáƒšáƒáƒ’ური სივრცითი 3.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4491 +msgid "Analog Surround 4.0" +msgstr "áƒáƒœáƒáƒšáƒáƒ’ური სივრცითი 4.0" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4492 +msgid "Analog Surround 4.1" +msgstr "áƒáƒœáƒáƒšáƒáƒ’ური სივრცითი 4.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4493 +msgid "Analog Surround 5.0" +msgstr "áƒáƒœáƒáƒšáƒáƒ’ური სივრცითი 5.0" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4494 +msgid "Analog Surround 5.1" +msgstr "áƒáƒœáƒáƒšáƒáƒ’ური სივრცითი 5.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4495 +msgid "Analog Surround 6.0" +msgstr "áƒáƒœáƒáƒšáƒáƒ’ური სივრცითი 6.0" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4496 +msgid "Analog Surround 6.1" +msgstr "áƒáƒœáƒáƒšáƒáƒ’ური სივრცითი 6.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4497 +msgid "Analog Surround 7.0" +msgstr "áƒáƒœáƒáƒšáƒáƒ’ური სივრცითი 7.0" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4498 +msgid "Analog Surround 7.1" +msgstr "áƒáƒœáƒáƒšáƒáƒ’ური სივრცითი 7.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4499 +msgid "Digital Stereo (IEC958)" +msgstr "ციფრული სტერერ(IEC958)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4500 +msgid "Digital Surround 4.0 (IEC958/AC3)" +msgstr "ციფრული სივრცითი 4.0 (IEC958/AC3)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4501 +msgid "Digital Surround 5.1 (IEC958/AC3)" +msgstr "ციფრული სივრცითი 5.1 (IEC958/AC3)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4502 +msgid "Digital Surround 5.1 (IEC958/DTS)" +msgstr "ციფრული სივრცითი 5.1 (IEC958/DTS)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4503 +msgid "Digital Stereo (HDMI)" +msgstr "ციფრული სტერერ(HDMI)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4504 +msgid "Digital Surround 5.1 (HDMI)" +msgstr "ციფრული სივრცითი 5.1 (HDMI)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4505 +msgid "Chat" +msgstr "ჩáƒáƒ¢áƒ˜" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4506 +msgid "Game" +msgstr "თáƒáƒ›áƒáƒ¨áƒ˜" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4640 +msgid "Analog Mono Duplex" +msgstr "áƒáƒœáƒáƒšáƒáƒ’ური მáƒáƒœáƒ დუპლექსი" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4641 +msgid "Analog Stereo Duplex" +msgstr "áƒáƒœáƒáƒšáƒáƒ’ური სტერერდუპლექსი" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4644 +msgid "Digital Stereo Duplex (IEC958)" +msgstr "ციფრული სტერერდუპლექსი (IEC958)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4645 +msgid "Multichannel Duplex" +msgstr "მრáƒáƒ•áƒáƒšáƒáƒ ხიáƒáƒœáƒ˜ დუპლექსი" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4646 +msgid "Stereo Duplex" +msgstr "სტერერდუპლექსი" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4647 +msgid "Mono Chat + 7.1 Surround" +msgstr "მáƒáƒœáƒ ჩáƒáƒ¢áƒ˜ + 7.1 სივრცითი" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4754 +#, c-format +msgid "%s Output" +msgstr "%s გáƒáƒ›áƒáƒ§áƒ•áƒáƒœáƒ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4761 +#, c-format +msgid "%s Input" +msgstr "%s შეყვáƒáƒœáƒ" + +#: spa/plugins/alsa/acp/alsa-util.c:1173 spa/plugins/alsa/acp/alsa-util.c:1267 +#, c-format +msgid "" +"snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " +"ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." +msgid_plural "" +"snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " +"ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." +msgstr[0] "" +"snd_pcm_avail()-ის მიერდáƒáƒ‘რუნებული მნიშვნელáƒáƒ‘რáƒáƒ áƒáƒ©áƒ•ეულებრივáƒáƒ“ დიდიáƒ: %lu " +"ბáƒáƒ˜áƒ¢áƒ˜ (%lu მწმ).\n" +"ყველáƒáƒ–ე ხშირáƒáƒ“ ეს ALSA-ს დრáƒáƒ˜áƒ•ერის (%s) შეცდáƒáƒ›áƒ˜áƒ¡ გáƒáƒ›áƒ ხდებáƒ. დáƒáƒ£áƒ™áƒáƒ•შირდით " +"ALSA-ის პრáƒáƒ’რáƒáƒ›áƒ˜áƒ¡áƒ¢áƒ”ბს." +msgstr[1] "" +"snd_pcm_avail()-ის მიერდáƒáƒ‘რუნებული მნიშვნელáƒáƒ‘რáƒáƒ áƒáƒ©áƒ•ეულებრივáƒáƒ“ დიდიáƒ: %lu " +"ბáƒáƒ˜áƒ¢áƒ˜ (%lu მწმ).\n" +"ყველáƒáƒ–ე ხშირáƒáƒ“ ეს ALSA-ს დრáƒáƒ˜áƒ•ერის (%s) შეცდáƒáƒ›áƒ˜áƒ¡ გáƒáƒ›áƒ ხდებáƒ. დáƒáƒ£áƒ™áƒáƒ•შირდით " +"ALSA-ის პრáƒáƒ’რáƒáƒ›áƒ˜áƒ¡áƒ¢áƒ”ბს." + +#: spa/plugins/alsa/acp/alsa-util.c:1239 +#, c-format +msgid "" +"snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" +"%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." +msgid_plural "" +"snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" +"%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." +msgstr[0] "" +"snd_pcm_delay()-ის მიერდáƒáƒ‘რუნებული მნიშვნელáƒáƒ‘რáƒáƒ áƒáƒ©áƒ•ეულებრივáƒáƒ“ დიდიáƒ: %li " +"ბáƒáƒ˜áƒ¢áƒ˜ (%s%lu მწმ).\n" +"ყველáƒáƒ–ე ხშირáƒáƒ“ ეს ALSA-ს დრáƒáƒ˜áƒ•ერის (%s) შეცდáƒáƒ›áƒ˜áƒ¡ გáƒáƒ›áƒ ხდებáƒ. დáƒáƒ£áƒ™áƒáƒ•შირდით " +"ALSA-ის პრáƒáƒ’რáƒáƒ›áƒ˜áƒ¡áƒ¢áƒ”ბს." +msgstr[1] "" +"snd_pcm_delay()-ის მიერდáƒáƒ‘რუნებული მნიშვნელáƒáƒ‘რáƒáƒ áƒáƒ©áƒ•ეულებრივáƒáƒ“ დიდიáƒ: %li " +"ბáƒáƒ˜áƒ¢áƒ˜ (%s%lu მწმ).\n" +"ყველáƒáƒ–ე ხშირáƒáƒ“ ეს ALSA-ს დრáƒáƒ˜áƒ•ერის (%s) შეცდáƒáƒ›áƒ˜áƒ¡ გáƒáƒ›áƒ ხდებáƒ. დáƒáƒ£áƒ™áƒáƒ•შირდით " +"ALSA-ის პრáƒáƒ’რáƒáƒ›áƒ˜áƒ¡áƒ¢áƒ”ბს." + +#: spa/plugins/alsa/acp/alsa-util.c:1286 +#, c-format +msgid "" +"snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " +"%lu.\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." +msgstr "" +"snd_pcm_avail_delay()-ის მიერდáƒáƒ‘რუნებული მნიშვნელáƒáƒ‘ები უცნáƒáƒ£áƒ იáƒ: დáƒáƒ§áƒáƒ•ნებრ" +"%lu უფრრმცირეáƒ, ვიდრე ხელმისáƒáƒ¬áƒ•დáƒáƒ›áƒ˜ დრრ%lu.\n" +"ყველáƒáƒ–ე ხშირáƒáƒ“ ეს ALSA-ს დრáƒáƒ˜áƒ•ერის (%s) შეცდáƒáƒ›áƒ˜áƒ¡ გáƒáƒ›áƒ ხდებáƒ. დáƒáƒ£áƒ™áƒáƒ•შირდით " +"ALSA-ის პრáƒáƒ’რáƒáƒ›áƒ˜áƒ¡áƒ¢áƒ”ბს." + +#: spa/plugins/alsa/acp/alsa-util.c:1329 +#, c-format +msgid "" +"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " +"(%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." +msgid_plural "" +"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " +"(%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." +msgstr[0] "" +"snd_pcm_mmap_begin()-ის მიერდáƒáƒ‘რუნებული მნიშვნელáƒáƒ‘რáƒáƒ áƒáƒ©áƒ•ეულებრივáƒáƒ“ დიდიáƒ: " +"%lu ბáƒáƒ˜áƒ¢áƒ˜ (%lu მწმ).\n" +"ყველáƒáƒ–ე ხშირáƒáƒ“ ეს ALSA-ს დრáƒáƒ˜áƒ•ერის (%s) შეცდáƒáƒ›áƒ˜áƒ¡ გáƒáƒ›áƒ ხდებáƒ. დáƒáƒ£áƒ™áƒáƒ•შირდით " +"ALSA-ის პრáƒáƒ’რáƒáƒ›áƒ˜áƒ¡áƒ¢áƒ”ბს." +msgstr[1] "" +"snd_pcm_mmap_begin()-ის მიერდáƒáƒ‘რუნებული მნიშვნელáƒáƒ‘რáƒáƒ áƒáƒ©áƒ•ეულებრივáƒáƒ“ დიდიáƒ: " +"%lu ბáƒáƒ˜áƒ¢áƒ˜ (%lu მწმ).\n" +"ყველáƒáƒ–ე ხშირáƒáƒ“ ეს ALSA-ს დრáƒáƒ˜áƒ•ერის (%s) შეცდáƒáƒ›áƒ˜áƒ¡ გáƒáƒ›áƒ ხდებáƒ. დáƒáƒ£áƒ™áƒáƒ•შირდით " +"ALSA-ის პრáƒáƒ’რáƒáƒ›áƒ˜áƒ¡áƒ¢áƒ”ბს." + +#: spa/plugins/alsa/acp/channelmap.h:457 +msgid "(invalid)" +msgstr "(áƒáƒ áƒáƒ¡áƒ¬áƒáƒ ი)" + +#: spa/plugins/alsa/acp/compat.c:189 +msgid "Built-in Audio" +msgstr "ჩáƒáƒ¨áƒ”ნებული áƒáƒ£áƒ“იáƒ" + +#: spa/plugins/alsa/acp/compat.c:194 +msgid "Modem" +msgstr "მáƒáƒ“ემი" + +#: spa/plugins/bluez5/bluez5-device.c:1172 +msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" +msgstr "Audio Gateway (A2DP წყáƒáƒ რ& HSP/HFP AG)" + +#: spa/plugins/bluez5/bluez5-device.c:1197 +#, c-format +msgid "High Fidelity Playback (A2DP Sink, codec %s)" +msgstr "მáƒáƒ¦áƒáƒšáƒ˜ ხáƒáƒ ისხის ხმრ(A2DP Sink, კáƒáƒ“ეკი %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1200 +#, c-format +msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" +msgstr "მáƒáƒ¦áƒáƒšáƒ˜ ხáƒáƒ ისხის დუპლექსი (A2DP წყáƒáƒ áƒ/Sink, კáƒáƒ“ეკი %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1208 +msgid "High Fidelity Playback (A2DP Sink)" +msgstr "მáƒáƒ¦áƒáƒšáƒ˜ ხáƒáƒ ისხის ხმრ(A2DP Sink)" + +#: spa/plugins/bluez5/bluez5-device.c:1210 +msgid "High Fidelity Duplex (A2DP Source/Sink)" +msgstr "მáƒáƒ¦áƒáƒšáƒ˜ ხáƒáƒ ისხის დუპლექსი(A2DP წყáƒáƒ áƒ/Sink)" + +#: spa/plugins/bluez5/bluez5-device.c:1238 +#, c-format +msgid "Headset Head Unit (HSP/HFP, codec %s)" +msgstr "Headset Head Unit (HSP/HFP, კáƒáƒ“ეკი %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1243 +msgid "Headset Head Unit (HSP/HFP)" +msgstr "Headset Head Unit (HSP/HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:1325 +msgid "Handsfree" +msgstr "ხელის გáƒáƒ ეშე სáƒáƒ›áƒáƒ თáƒáƒ•ი" + +#: spa/plugins/bluez5/bluez5-device.c:1340 +msgid "Headphone" +msgstr "ყურსáƒáƒªáƒ•áƒáƒ›áƒ˜" + +#: spa/plugins/bluez5/bluez5-device.c:1345 +msgid "Portable" +msgstr "გáƒáƒ“áƒáƒ¢áƒáƒœáƒáƒ“ი" + +#: spa/plugins/bluez5/bluez5-device.c:1350 +msgid "Car" +msgstr "მáƒáƒœáƒ¥áƒáƒœáƒ" + +#: spa/plugins/bluez5/bluez5-device.c:1355 +msgid "HiFi" +msgstr "HiFi" + +#: spa/plugins/bluez5/bluez5-device.c:1360 +msgid "Phone" +msgstr "ტელეფáƒáƒœáƒ˜" + +#: spa/plugins/bluez5/bluez5-device.c:1366 +msgid "Bluetooth" +msgstr "Bluetooth" diff --git a/po/pipewire.pot b/po/pipewire.pot index 82b498e7f6e68d2e32398bd3ae230082893937d8..26d49f3d46b9729260ed2b821e0c36f70f0d5037 100644 --- a/po/pipewire.pot +++ b/po/pipewire.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/issues/new\n" -"POT-Creation-Date: 2022-04-03 12:56+0200\n" +"POT-Creation-Date: 2022-06-30 12:50+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -27,8 +27,8 @@ msgid "" " -c, --config Load config (Default %s)\n" msgstr "" -#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:190 -#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:190 +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:180 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:180 #, c-format msgid "Tunnel to %s/%s" msgstr "" @@ -37,36 +37,36 @@ msgstr "" msgid "Dummy Output" msgstr "" -#: src/modules/module-pulse-tunnel.c:545 +#: src/modules/module-pulse-tunnel.c:648 #, c-format msgid "Tunnel for %s@%s" msgstr "" -#: src/modules/module-zeroconf-discover.c:313 +#: src/modules/module-zeroconf-discover.c:332 msgid "Unknown device" msgstr "" -#: src/modules/module-zeroconf-discover.c:325 +#: src/modules/module-zeroconf-discover.c:344 #, c-format msgid "%s on %s@%s" msgstr "" -#: src/modules/module-zeroconf-discover.c:329 +#: src/modules/module-zeroconf-discover.c:348 #, c-format msgid "%s on %s" msgstr "" -#: src/tools/pw-cat.c:1087 +#: src/tools/pw-cat.c:784 #, c-format msgid "" -"%s [options] <file>\n" +"%s [options] [<file>|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" -#: src/tools/pw-cat.c:1094 +#: src/tools/pw-cat.c:791 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -80,11 +80,11 @@ msgid "" " or direct samples (256)\n" " the rate is the one of the source " "file\n" -" --list-targets List available targets for --target\n" +" -P --properties Set node properties\n" "\n" msgstr "" -#: src/tools/pw-cat.c:1112 +#: src/tools/pw-cat.c:809 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -104,7 +104,7 @@ msgid "" "\n" msgstr "" -#: src/tools/pw-cat.c:1129 +#: src/tools/pw-cat.c:826 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" @@ -113,7 +113,7 @@ msgid "" "\n" msgstr "" -#: src/tools/pw-cli.c:3051 +#: src/tools/pw-cli.c:3165 #, c-format msgid "" "%s [options] [command]\n" @@ -128,8 +128,8 @@ msgstr "" msgid "Pro Audio" msgstr "" -#: spa/plugins/alsa/acp/acp.c:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 -#: spa/plugins/bluez5/bluez5-device.c:1159 +#: spa/plugins/alsa/acp/acp.c:446 spa/plugins/alsa/acp/alsa-mixer.c:4648 +#: spa/plugins/bluez5/bluez5-device.c:1161 msgid "Off" msgstr "" @@ -156,7 +156,7 @@ msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:1328 +#: spa/plugins/bluez5/bluez5-device.c:1330 msgid "Microphone" msgstr "" @@ -222,7 +222,7 @@ msgid "No Bass Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:1333 +#: spa/plugins/bluez5/bluez5-device.c:1335 msgid "Speaker" msgstr "" @@ -337,7 +337,7 @@ msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 -#: spa/plugins/bluez5/bluez5-device.c:1318 +#: spa/plugins/bluez5/bluez5-device.c:1320 msgid "Headset" msgstr "" @@ -516,7 +516,7 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: spa/plugins/alsa/acp/channelmap.h:464 +#: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "" @@ -528,61 +528,61 @@ msgstr "" msgid "Modem" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1170 +#: spa/plugins/bluez5/bluez5-device.c:1172 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1195 +#: spa/plugins/bluez5/bluez5-device.c:1197 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1198 +#: spa/plugins/bluez5/bluez5-device.c:1200 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1206 +#: spa/plugins/bluez5/bluez5-device.c:1208 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1208 +#: spa/plugins/bluez5/bluez5-device.c:1210 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1236 +#: spa/plugins/bluez5/bluez5-device.c:1238 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1241 +#: spa/plugins/bluez5/bluez5-device.c:1243 msgid "Headset Head Unit (HSP/HFP)" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1323 +#: spa/plugins/bluez5/bluez5-device.c:1325 msgid "Handsfree" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1338 +#: spa/plugins/bluez5/bluez5-device.c:1340 msgid "Headphone" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1343 +#: spa/plugins/bluez5/bluez5-device.c:1345 msgid "Portable" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1348 +#: spa/plugins/bluez5/bluez5-device.c:1350 msgid "Car" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1353 +#: spa/plugins/bluez5/bluez5-device.c:1355 msgid "HiFi" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1358 +#: spa/plugins/bluez5/bluez5-device.c:1360 msgid "Phone" msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:1364 +#: spa/plugins/bluez5/bluez5-device.c:1366 msgid "Bluetooth" msgstr "" diff --git a/po/pl.po b/po/pl.po index 72bda0316f6958b717a62988a0bb24a3ffaec096..b84b59b9c0538a3ac7d5699944418ebadcb68a19 100644 --- a/po/pl.po +++ b/po/pl.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" -"POT-Creation-Date: 2022-05-20 15:26+0000\n" -"PO-Revision-Date: 2022-05-21 12:49+0200\n" +"POT-Creation-Date: 2022-08-27 13:57+0000\n" +"PO-Revision-Date: 2022-08-27 16:00+0200\n" "Last-Translator: Piotr DrÄ…g <piotrdrag@gmail.com>\n" "Language-Team: Polish <community-poland@mozilla.org>\n" "Language: pl\n" @@ -41,8 +41,8 @@ msgstr "System multimediów PipeWire" msgid "Start the PipeWire Media System" msgstr "Uruchomienie systemu multimediów PipeWire" -#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:183 -#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:183 +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:180 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:180 #, c-format msgid "Tunnel to %s/%s" msgstr "Tunel do %s/%s" @@ -51,7 +51,7 @@ msgstr "Tunel do %s/%s" msgid "Dummy Output" msgstr "GÅ‚uche wyjÅ›cie" -#: src/modules/module-pulse-tunnel.c:639 +#: src/modules/module-pulse-tunnel.c:648 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunel dla %s@%s" @@ -70,7 +70,7 @@ msgstr "%s na %s@%s" msgid "%s on %s" msgstr "%s na %s" -#: src/tools/pw-cat.c:872 +#: src/tools/pw-cat.c:784 #, c-format msgid "" "%s [options] [<file>|-]\n" @@ -85,7 +85,7 @@ msgstr "" " -v, --verbose WyÅ›wietla wiÄ™cej komunikatów\n" "\n" -#: src/tools/pw-cat.c:879 +#: src/tools/pw-cat.c:791 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -122,7 +122,7 @@ msgstr "" " -P --properties Ustawia wÅ‚aÅ›ciwoÅ›ci wÄ™zÅ‚a\n" "\n" -#: src/tools/pw-cat.c:897 +#: src/tools/pw-cat.c:809 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -158,7 +158,7 @@ msgstr "" "(domyÅ›lnie %d)\n" "\n" -#: src/tools/pw-cat.c:914 +#: src/tools/pw-cat.c:826 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" @@ -172,7 +172,7 @@ msgstr "" " -d, --dsd Tryb DSD\n" "\n" -#: src/tools/pw-cli.c:3139 +#: src/tools/pw-cli.c:2255 #, c-format msgid "" "%s [options] [command]\n" @@ -195,7 +195,7 @@ msgid "Pro Audio" msgstr "DźwiÄ™k w zastosowaniach profesjonalnych" #: spa/plugins/alsa/acp/acp.c:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 -#: spa/plugins/bluez5/bluez5-device.c:1161 +#: spa/plugins/bluez5/bluez5-device.c:1188 msgid "Off" msgstr "Wyłączone" @@ -222,7 +222,7 @@ msgstr "WejÅ›cie liniowe" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:1330 +#: spa/plugins/bluez5/bluez5-device.c:1360 msgid "Microphone" msgstr "Mikrofon" @@ -288,7 +288,7 @@ msgid "No Bass Boost" msgstr "Brak podbicia basów" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:1335 +#: spa/plugins/bluez5/bluez5-device.c:1366 msgid "Speaker" msgstr "GÅ‚oÅ›nik" @@ -403,7 +403,7 @@ msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 -#: spa/plugins/bluez5/bluez5-device.c:1320 +#: spa/plugins/bluez5/bluez5-device.c:1348 msgid "Headset" msgstr "SÅ‚uchawki z mikrofonem" @@ -527,7 +527,7 @@ msgstr "WyjÅ›cie %s" msgid "%s Input" msgstr "WejÅ›cie %s" -#: spa/plugins/alsa/acp/alsa-util.c:1173 spa/plugins/alsa/acp/alsa-util.c:1267 +#: spa/plugins/alsa/acp/alsa-util.c:1187 spa/plugins/alsa/acp/alsa-util.c:1281 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -552,7 +552,7 @@ msgstr[2] "" "Prawdopodobnie jest to błąd sterownika ALSA „%sâ€. ProszÄ™ zgÅ‚osić ten problem " "programistom usÅ‚ugi ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1239 +#: spa/plugins/alsa/acp/alsa-util.c:1253 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" @@ -577,7 +577,7 @@ msgstr[2] "" "Prawdopodobnie jest to błąd sterownika ALSA „%sâ€. ProszÄ™ zgÅ‚osić ten problem " "programistom usÅ‚ugi ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1286 +#: spa/plugins/alsa/acp/alsa-util.c:1300 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -590,7 +590,7 @@ msgstr "" "Prawdopodobnie jest to błąd sterownika ALSA „%sâ€. ProszÄ™ zgÅ‚osić ten problem " "programistom usÅ‚ugi ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1329 +#: spa/plugins/alsa/acp/alsa-util.c:1343 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " @@ -615,7 +615,7 @@ msgstr[2] "" "Prawdopodobnie jest to błąd sterownika ALSA „%sâ€. ProszÄ™ zgÅ‚osić ten problem " "programistom usÅ‚ugi ALSA." -#: spa/plugins/alsa/acp/channelmap.h:464 +#: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(nieprawidÅ‚owe)" @@ -627,61 +627,77 @@ msgstr "Wbudowany dźwiÄ™k" msgid "Modem" msgstr "Modem" -#: spa/plugins/bluez5/bluez5-device.c:1172 +#: spa/plugins/bluez5/bluez5-device.c:1199 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Bramka dźwiÄ™ku (źródÅ‚o A2DP i AG HSP/HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1197 +#: spa/plugins/bluez5/bluez5-device.c:1224 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Odtwarzanie o wysokiej dokÅ‚adnoÅ›ci (odpÅ‚yw A2DP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1200 +#: spa/plugins/bluez5/bluez5-device.c:1227 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Dupleks o wysokiej dokÅ‚adnoÅ›ci (źródÅ‚o/odpÅ‚yw A2DP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1208 +#: spa/plugins/bluez5/bluez5-device.c:1235 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Odtwarzanie o wysokiej dokÅ‚adnoÅ›ci (odpÅ‚yw A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:1210 +#: spa/plugins/bluez5/bluez5-device.c:1237 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Dupleks o wysokiej dokÅ‚adnoÅ›ci (źródÅ‚o/odpÅ‚yw A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:1238 +#: spa/plugins/bluez5/bluez5-device.c:1265 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Jednostka główna sÅ‚uchawek z mikrofonem (HSP/HFP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1243 +#: spa/plugins/bluez5/bluez5-device.c:1270 msgid "Headset Head Unit (HSP/HFP)" msgstr "Jednostka główna sÅ‚uchawek z mikrofonem (HSP/HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1325 +#: spa/plugins/bluez5/bluez5-device.c:1349 +#: spa/plugins/bluez5/bluez5-device.c:1354 +#: spa/plugins/bluez5/bluez5-device.c:1361 +#: spa/plugins/bluez5/bluez5-device.c:1367 +#: spa/plugins/bluez5/bluez5-device.c:1373 +#: spa/plugins/bluez5/bluez5-device.c:1379 +#: spa/plugins/bluez5/bluez5-device.c:1385 +#: spa/plugins/bluez5/bluez5-device.c:1391 +#: spa/plugins/bluez5/bluez5-device.c:1397 msgid "Handsfree" msgstr "Zestaw gÅ‚oÅ›nomówiÄ…cy" -#: spa/plugins/bluez5/bluez5-device.c:1340 +#: spa/plugins/bluez5/bluez5-device.c:1355 +msgid "Handsfree (HFP)" +msgstr "Zestaw gÅ‚oÅ›nomówiÄ…cy (HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:1372 msgid "Headphone" msgstr "SÅ‚uchawki" -#: spa/plugins/bluez5/bluez5-device.c:1345 +#: spa/plugins/bluez5/bluez5-device.c:1378 msgid "Portable" msgstr "PrzenoÅ›ne" -#: spa/plugins/bluez5/bluez5-device.c:1350 +#: spa/plugins/bluez5/bluez5-device.c:1384 msgid "Car" msgstr "Samochód" -#: spa/plugins/bluez5/bluez5-device.c:1355 +#: spa/plugins/bluez5/bluez5-device.c:1390 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:1360 +#: spa/plugins/bluez5/bluez5-device.c:1396 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:1366 +#: spa/plugins/bluez5/bluez5-device.c:1403 msgid "Bluetooth" msgstr "Bluetooth" + +#: spa/plugins/bluez5/bluez5-device.c:1404 +msgid "Bluetooth (HFP)" +msgstr "Bluetooth (HFP)" diff --git a/po/sv.po b/po/sv.po index a5dd5fb60b51c0e0779a8448a5a227cf990bed5f..f9a57802245ab89f1446ec91f7aa8c2e28e66010 100644 --- a/po/sv.po +++ b/po/sv.po @@ -19,8 +19,8 @@ msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" -"POT-Creation-Date: 2022-05-20 15:26+0000\n" -"PO-Revision-Date: 2022-05-23 11:01+0200\n" +"POT-Creation-Date: 2022-07-19 15:27+0000\n" +"PO-Revision-Date: 2022-07-10 10:22+0200\n" "Last-Translator: Anders Jonsson <anders.jonsson@norsjovallen.se>\n" "Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n" "Language: sv\n" @@ -28,7 +28,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Poedit 3.0.1\n" +"X-Generator: Poedit 3.1\n" #: src/daemon/pipewire.c:46 #, c-format @@ -51,8 +51,8 @@ msgstr "PipeWire mediasystem" msgid "Start the PipeWire Media System" msgstr "Starta mediasystemet PipeWire" -#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:183 -#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:183 +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:180 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:180 #, c-format msgid "Tunnel to %s/%s" msgstr "Tunnel till %s/%s" @@ -61,7 +61,7 @@ msgstr "Tunnel till %s/%s" msgid "Dummy Output" msgstr "AttrapputgÃ¥ng" -#: src/modules/module-pulse-tunnel.c:639 +#: src/modules/module-pulse-tunnel.c:648 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunnel för %s@%s" @@ -80,7 +80,7 @@ msgstr "%s pÃ¥ %s@%s" msgid "%s on %s" msgstr "%s pÃ¥ %s" -#: src/tools/pw-cat.c:872 +#: src/tools/pw-cat.c:784 #, c-format msgid "" "%s [options] [<file>|-]\n" @@ -95,7 +95,7 @@ msgstr "" " -v, --verbose Aktivera utförliga operationer\n" "\n" -#: src/tools/pw-cat.c:879 +#: src/tools/pw-cat.c:791 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -125,7 +125,7 @@ msgstr "" " -P --properties Sätt nodegenskaper\n" "\n" -#: src/tools/pw-cat.c:897 +#: src/tools/pw-cat.c:809 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -160,7 +160,7 @@ msgstr "" "%d)\n" "\n" -#: src/tools/pw-cat.c:914 +#: src/tools/pw-cat.c:826 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" @@ -174,7 +174,7 @@ msgstr "" " -d, --dsd DSD-läge\n" "\n" -#: src/tools/pw-cli.c:3139 +#: src/tools/pw-cli.c:3165 #, c-format msgid "" "%s [options] [command]\n" @@ -195,8 +195,8 @@ msgstr "" msgid "Pro Audio" msgstr "Professionellt ljud" -#: spa/plugins/alsa/acp/acp.c:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 -#: spa/plugins/bluez5/bluez5-device.c:1161 +#: spa/plugins/alsa/acp/acp.c:446 spa/plugins/alsa/acp/alsa-mixer.c:4648 +#: spa/plugins/bluez5/bluez5-device.c:1188 msgid "Off" msgstr "Av" @@ -223,7 +223,7 @@ msgstr "Linje in" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:1330 +#: spa/plugins/bluez5/bluez5-device.c:1360 msgid "Microphone" msgstr "Mikrofon" @@ -289,7 +289,7 @@ msgid "No Bass Boost" msgstr "Ingen basökning" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:1335 +#: spa/plugins/bluez5/bluez5-device.c:1366 msgid "Speaker" msgstr "Högtalare" @@ -404,7 +404,7 @@ msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 -#: spa/plugins/bluez5/bluez5-device.c:1320 +#: spa/plugins/bluez5/bluez5-device.c:1348 msgid "Headset" msgstr "Headset" @@ -554,13 +554,13 @@ msgstr[1] "" #: spa/plugins/alsa/acp/alsa-util.c:1239 #, c-format msgid "" -"snd_pcm_delay() returned a value that is exceptionally large: %li byte " -"(%s%lu ms).\n" +"snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" +"%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" -"snd_pcm_delay() returned a value that is exceptionally large: %li bytes " -"(%s%lu ms).\n" +"snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" +"%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" @@ -610,7 +610,7 @@ msgstr[1] "" "Förmodligen är detta ett fel i ALSA-drivrutinen â€%sâ€. Vänligen rapportera " "problemet till ALSA-utvecklarna." -#: spa/plugins/alsa/acp/channelmap.h:464 +#: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(ogiltig)" @@ -622,61 +622,77 @@ msgstr "Inbyggt ljud" msgid "Modem" msgstr "Modem" -#: spa/plugins/bluez5/bluez5-device.c:1172 +#: spa/plugins/bluez5/bluez5-device.c:1199 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Audio gateway (A2DP-källa & HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1197 +#: spa/plugins/bluez5/bluez5-device.c:1224 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "High fidelity-uppspelning (A2DP-utgÃ¥ng, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1200 +#: spa/plugins/bluez5/bluez5-device.c:1227 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "High fidelity duplex (A2DP-källa/utgÃ¥ng, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1208 +#: spa/plugins/bluez5/bluez5-device.c:1235 msgid "High Fidelity Playback (A2DP Sink)" msgstr "High fidelity-uppspelning (A2DP-utgÃ¥ng)" -#: spa/plugins/bluez5/bluez5-device.c:1210 +#: spa/plugins/bluez5/bluez5-device.c:1237 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "High fidelity duplex (A2DP-källa/utgÃ¥ng)" -#: spa/plugins/bluez5/bluez5-device.c:1238 +#: spa/plugins/bluez5/bluez5-device.c:1265 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Headset-huvudenhet (HSP/HFP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1243 +#: spa/plugins/bluez5/bluez5-device.c:1270 msgid "Headset Head Unit (HSP/HFP)" msgstr "Headset-huvudenhet (HSP/HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1325 +#: spa/plugins/bluez5/bluez5-device.c:1349 +#: spa/plugins/bluez5/bluez5-device.c:1354 +#: spa/plugins/bluez5/bluez5-device.c:1361 +#: spa/plugins/bluez5/bluez5-device.c:1367 +#: spa/plugins/bluez5/bluez5-device.c:1373 +#: spa/plugins/bluez5/bluez5-device.c:1379 +#: spa/plugins/bluez5/bluez5-device.c:1385 +#: spa/plugins/bluez5/bluez5-device.c:1391 +#: spa/plugins/bluez5/bluez5-device.c:1397 msgid "Handsfree" msgstr "Handsfree" -#: spa/plugins/bluez5/bluez5-device.c:1340 +#: spa/plugins/bluez5/bluez5-device.c:1355 +msgid "Handsfree (HFP)" +msgstr "Handsfree (HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:1372 msgid "Headphone" msgstr "Hörlurar" -#: spa/plugins/bluez5/bluez5-device.c:1345 +#: spa/plugins/bluez5/bluez5-device.c:1378 msgid "Portable" msgstr "Bärbar" -#: spa/plugins/bluez5/bluez5-device.c:1350 +#: spa/plugins/bluez5/bluez5-device.c:1384 msgid "Car" msgstr "Bil" -#: spa/plugins/bluez5/bluez5-device.c:1355 +#: spa/plugins/bluez5/bluez5-device.c:1390 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:1360 +#: spa/plugins/bluez5/bluez5-device.c:1396 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:1366 +#: spa/plugins/bluez5/bluez5-device.c:1403 msgid "Bluetooth" msgstr "Bluetooth" + +#: spa/plugins/bluez5/bluez5-device.c:1404 +msgid "Bluetooth (HFP)" +msgstr "Bluetooth (HFP)" diff --git a/po/uk.po b/po/uk.po index e2e04ecb3941531e5684865f13bb2008fd6c5888..a8d04cb35108c12f33dbfb48e41b44fd6adbf94f 100644 --- a/po/uk.po +++ b/po/uk.po @@ -7,8 +7,8 @@ msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/issue" "s\n" -"POT-Creation-Date: 2022-05-05 03:28+0000\n" -"PO-Revision-Date: 2022-05-05 21:06+0300\n" +"POT-Creation-Date: 2022-05-20 15:26+0000\n" +"PO-Revision-Date: 2022-06-18 13:07+0300\n" "Last-Translator: Yuri Chornoivan <yurchor@ukr.net>\n" "Language-Team: Ukrainian <trans-uk@lists.fedoraproject.org>\n" "Language: uk\n" @@ -48,7 +48,6 @@ msgid "Tunnel to %s/%s" msgstr "Тунель до %s/%s" #: src/modules/module-fallback-sink.c:51 -#| msgid "Game Output" msgid "Dummy Output" msgstr "Фіктивний вихід" @@ -71,23 +70,29 @@ msgstr "%s на %s@%s" msgid "%s on %s" msgstr "%s на %s" -#: src/tools/pw-cat.c:871 +#: src/tools/pw-cat.c:872 #, c-format +#| msgid "" +#| "%s [options] <file>\n" +#| " -h, --help Show this help\n" +#| " --version Show version\n" +#| " -v, --verbose Enable verbose operations\n" +#| "\n" msgid "" -"%s [options] <file>\n" +"%s [options] [<file>|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" -"%s [параметри] <файл>\n" +"%s [параметри] [<файл>|-]\n" " -h, --help вивеÑти довідку\n" " --version вивеÑти дані щодо верÑÑ–Ñ—\n" " -v, --verbose ввімкнути Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð´Ð¾ÐºÐ»Ð°Ð´Ð½Ð¾Ñ— " "інформації\n" "\n" -#: src/tools/pw-cat.c:878 +#: src/tools/pw-cat.c:879 #, c-format #| msgid "" #| " -R, --remote Remote daemon name\n" @@ -101,8 +106,6 @@ msgstr "" #| " or direct samples (256)\n" #| " the rate is the one of the " #| "source file\n" -#| " --list-targets List available targets for --" -#| "target\n" #| "\n" msgid "" " -R, --remote Remote daemon name\n" @@ -116,6 +119,7 @@ msgid "" " or direct samples (256)\n" " the rate is the one of the source " "file\n" +" -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote назва віддаленої фонової Ñлужби\n" @@ -133,9 +137,10 @@ msgstr "" "ns)\n" " або безпоÑередні Ñемпли (256)\n" " чаÑтота — чаÑтота з файла джерела\n" +" -P --properties вÑтановити влаÑтивоÑті вузла\n" "\n" -#: src/tools/pw-cat.c:895 +#: src/tools/pw-cat.c:897 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -171,7 +176,7 @@ msgstr "" "(типово, %d)\n" "\n" -#: src/tools/pw-cat.c:912 +#: src/tools/pw-cat.c:914 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" diff --git a/spa/examples/adapter-control.c b/spa/examples/adapter-control.c index 39a22a4e25da0583aeaefa5aacfb7b1454afacc8..7caf32f92f76873013fae7c8dd77d9bb54dc5c19 100644 --- a/spa/examples/adapter-control.c +++ b/spa/examples/adapter-control.c @@ -62,7 +62,9 @@ static SPA_LOG_IMPL(default_log); -#define MIN_LATENCY 1024 +#define MIN_LATENCY 1024 +#define CONTROL_BUFFER_SIZE 32768 + struct buffer { struct spa_buffer buffer; @@ -95,10 +97,19 @@ struct data { struct spa_node *sink_follower_node; // alsa-pcm-sink struct spa_node *sink_node; // adapter for alsa-pcm-sink + struct spa_io_position position; struct spa_io_buffers source_sink_io[1]; struct spa_buffer *source_buffers[1]; struct buffer source_buffer[1]; - uint8_t ctrl[1024]; + + struct spa_io_buffers control_io; + struct spa_buffer *control_buffers[1]; + struct buffer control_buffer[1]; + + int buffer_count; + bool start_fade_in; + double volume_accum; + uint32_t volume_offs; bool running; pthread_t thread; @@ -168,6 +179,11 @@ int init_data(struct data *data) str = PLUGINDIR; data->plugin_dir = str; + /* start not doing fade-in */ + data->start_fade_in = true; + data->volume_accum = 0.0; + data->volume_offs = 0; + /* init the graph */ spa_graph_init(&data->graph, &data->graph_state); @@ -274,29 +290,104 @@ exit_cleanup: return res; } -static int on_sink_node_ready(void *_data, int status) +static int fade_in(struct data *data) { - struct data *data = _data; + struct spa_pod_builder b; + struct spa_pod_frame f[1]; + void *buffer = data->control_buffer->datas[0].data; + uint32_t buffer_size = data->control_buffer->datas[0].maxsize; + data->control_buffer->datas[0].chunk[0].size = buffer_size; + + printf ("fading in\n"); + + spa_pod_builder_init(&b, buffer, buffer_size); + spa_pod_builder_push_sequence(&b, &f[0], 0); + data->volume_offs = 0; + do { + spa_pod_builder_control(&b, data->volume_offs, SPA_CONTROL_Properties); + spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, 0, + SPA_PROP_volume, SPA_POD_Float(data->volume_accum)); + data->volume_accum += 0.003; + data->volume_offs += 200; + } while (data->volume_accum < 1.0); + spa_pod_builder_pop(&b, &f[0]); - spa_graph_node_process(&data->graph_source_node); - spa_graph_node_process(&data->graph_sink_node); return 0; } -static int -on_sink_node_reuse_buffer(void *_data, uint32_t port_id, uint32_t buffer_id) +static int fade_out(struct data *data) +{ + struct spa_pod_builder b; + struct spa_pod_frame f[1]; + void *buffer = data->control_buffer->datas[0].data; + uint32_t buffer_size = data->control_buffer->datas[0].maxsize; + data->control_buffer->datas[0].chunk[0].size = buffer_size; + + printf ("fading out\n"); + + spa_pod_builder_init(&b, buffer, buffer_size); + spa_pod_builder_push_sequence(&b, &f[0], 0); + data->volume_offs = 200; + do { + spa_pod_builder_control(&b, data->volume_offs, SPA_CONTROL_Properties); + spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, 0, + SPA_PROP_volume, SPA_POD_Float(data->volume_accum)); + data->volume_accum -= 0.003; + data->volume_offs += 200; + } while (data->volume_accum > 0.0); + spa_pod_builder_pop(&b, &f[0]); + + return 0; +} + +static void do_fade(struct data *data) +{ + switch (data->control_io.status) { + case SPA_STATUS_OK: + case SPA_STATUS_NEED_DATA: + break; + case SPA_STATUS_HAVE_DATA: + case SPA_STATUS_STOPPED: + default: + return; + } + + /* fade */ + if (data->start_fade_in) + fade_in(data); + else + fade_out(data); + + data->control_io.status = SPA_STATUS_HAVE_DATA; + data->control_io.buffer_id = 0; + + /* alternate */ + data->start_fade_in = !data->start_fade_in; +} + +static int on_sink_node_ready(void *_data, int status) { struct data *data = _data; - printf ("reuse_buffer: port_id=%d\n", port_id); - data->source_sink_io[0].buffer_id = buffer_id; + /* only do fade in/out when buffer count is 0 */ + if (data->buffer_count == 0) + do_fade(data); + + /* update buffer count */ + data->buffer_count++; + if (data->buffer_count > 64) + data->buffer_count = 0; + + spa_graph_node_process(&data->graph_source_node); + spa_graph_node_process(&data->graph_sink_node); return 0; } static const struct spa_node_callbacks sink_node_callbacks = { SPA_VERSION_NODE_CALLBACKS, .ready = on_sink_node_ready, - .reuse_buffer = on_sink_node_reuse_buffer }; static int make_nodes(struct data *data, const char *device) @@ -306,15 +397,17 @@ static int make_nodes(struct data *data, const char *device) struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; char value[32]; - struct spa_dict_item items[1]; + struct spa_dict_item items[2]; struct spa_audio_info_raw info; struct spa_pod *param; + items[0] = SPA_DICT_ITEM_INIT("clock.quantum-limit", "8192"); + /* make the source node (audiotestsrc) */ if ((res = make_node(data, &data->source_follower_node, - "audiotestsrc/libspa-audiotestsrc.so", - "audiotestsrc", - NULL)) < 0) { + "audiotestsrc/libspa-audiotestsrc.so", + "audiotestsrc", + &SPA_DICT_INIT(items, 1))) < 0) { printf("can't create source follower node (audiotestsrc): %d\n", res); return res; } @@ -335,11 +428,11 @@ static int make_nodes(struct data *data, const char *device) /* make the sink adapter node */ snprintf(value, sizeof(value), "pointer:%p", data->source_follower_node); - items[0] = SPA_DICT_ITEM_INIT("audio.adapt.follower", value); + items[1] = SPA_DICT_ITEM_INIT("audio.adapt.follower", value); if ((res = make_node(data, &data->source_node, - "audioconvert/libspa-audioconvert.so", - SPA_NAME_AUDIO_ADAPT, - &SPA_DICT_INIT(items, 1))) < 0) { + "audioconvert/libspa-audioconvert.so", + SPA_NAME_AUDIO_ADAPT, + &SPA_DICT_INIT(items, 2))) < 0) { printf("can't create source adapter node: %d\n", res); return res; } @@ -376,20 +469,20 @@ static int make_nodes(struct data *data, const char *device) /* make the sink follower node (alsa-pcm-sink) */ if ((res = make_node(data, &data->sink_follower_node, - "alsa/libspa-alsa.so", - SPA_NAME_API_ALSA_PCM_SINK, - NULL)) < 0) { + "alsa/libspa-alsa.so", + SPA_NAME_API_ALSA_PCM_SINK, + &SPA_DICT_INIT(items, 1))) < 0) { printf("can't create sink follower node (alsa-pcm-sink): %d\n", res); return res; } /* make the sink adapter node */ snprintf(value, sizeof(value), "pointer:%p", data->sink_follower_node); - items[0] = SPA_DICT_ITEM_INIT("audio.adapt.follower", value); + items[1] = SPA_DICT_ITEM_INIT("audio.adapt.follower", value); if ((res = make_node(data, &data->sink_node, - "audioconvert/libspa-audioconvert.so", - SPA_NAME_AUDIO_ADAPT, - &SPA_DICT_INIT(items, 1))) < 0) { + "audioconvert/libspa-audioconvert.so", + SPA_NAME_AUDIO_ADAPT, + &SPA_DICT_INIT(items, 2))) < 0) { printf("can't create sink adapter node: %d\n", res); return res; } @@ -420,6 +513,7 @@ static int make_nodes(struct data *data, const char *device) SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_INPUT), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), + SPA_PARAM_PORT_CONFIG_control, SPA_POD_Bool(true), SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); if ((res = spa_node_set_param(data->sink_node, SPA_PARAM_PortConfig, 0, param) < 0)) { printf("can't setup sink node %d\n", res); @@ -442,6 +536,42 @@ static int make_nodes(struct data *data, const char *device) printf("can't set io buffers on port 0 of sink node: %d\n", res); return res; } + /* set io position and clock on source and sink nodes */ + data->position.clock.rate = SPA_FRACTION(1, 48000); + data->position.clock.duration = 1024; + if ((res = spa_node_set_io(data->source_node, + SPA_IO_Position, + &data->position, sizeof(data->position))) < 0) { + printf("can't set io position on source node: %d\n", res); + return res; + } + if ((res = spa_node_set_io(data->sink_node, + SPA_IO_Position, + &data->position, sizeof(data->position))) < 0) { + printf("can't set io position on sink node: %d\n", res); + return res; + } + if ((res = spa_node_set_io(data->source_node, + SPA_IO_Clock, + &data->position.clock, sizeof(data->position.clock))) < 0) { + printf("can't set io clock on source node: %d\n", res); + return res; + } + if ((res = spa_node_set_io(data->sink_node, + SPA_IO_Clock, + &data->position.clock, sizeof(data->position.clock))) < 0) { + printf("can't set io clock on sink node: %d\n", res); + return res; + } + + /* set io buffers on control port of sink node */ + if ((res = spa_node_port_set_io(data->sink_node, + SPA_DIRECTION_INPUT, 1, + SPA_IO_Buffers, + &data->control_io, sizeof(data->control_io))) < 0) { + printf("can't set io buffers on control port 1 of sink node\n"); + return res; + } /* add source node to the graph */ spa_graph_node_init(&data->graph_source_node, &data->graph_source_state); @@ -508,17 +638,6 @@ static int negotiate_formats(struct data *data) uint32_t state = 0; size_t buffer_size = 1024; - /* get the source follower node buffer size */ - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - if (spa_node_port_enum_params_sync(data->source_follower_node, - SPA_DIRECTION_OUTPUT, 0, - SPA_PARAM_Buffers, &state, filter, ¶m, &b) != 1) - return -ENOTSUP; - spa_pod_fixate(param); - if ((res = spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamBuffers, NULL, - SPA_PARAM_BUFFERS_size, SPA_POD_Int(&buffer_size))) < 0) - return res; - /* set the sink and source formats */ spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = spa_format_audio_dsp_build(&b, 0, @@ -531,6 +650,26 @@ static int negotiate_formats(struct data *data) SPA_DIRECTION_INPUT, 0, SPA_PARAM_Format, 0, param)) < 0) return res; + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Format, SPA_PARAM_Format, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); + if ((res = spa_node_port_set_param(data->sink_node, + SPA_DIRECTION_INPUT, 1, SPA_PARAM_Format, 0, param)) < 0) + return res; + + /* get the source node buffer size */ + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + if ((res = spa_node_port_enum_params_sync(data->source_node, + SPA_DIRECTION_OUTPUT, 0, + SPA_PARAM_Buffers, &state, filter, ¶m, &b)) != 1) + return res ? res : -ENOTSUP; + spa_pod_fixate(param); + if ((res = spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamBuffers, NULL, + SPA_PARAM_BUFFERS_size, SPA_POD_Int(&buffer_size))) < 0) + return res; + /* use buffers on the source and sink */ init_buffer(data, data->source_buffers, data->source_buffer, 1, buffer_size); if ((res = spa_node_port_use_buffers(data->source_node, @@ -540,6 +679,12 @@ static int negotiate_formats(struct data *data) SPA_DIRECTION_INPUT, 0, 0, data->source_buffers, 1)) < 0) return res; + /* Set the control buffers */ + init_buffer(data, data->control_buffers, data->control_buffer, 1, CONTROL_BUFFER_SIZE); + if ((res = spa_node_port_use_buffers(data->sink_node, + SPA_DIRECTION_INPUT, 1, 0, data->control_buffers, 1)) < 0) + return res; + return 0; } diff --git a/spa/include/spa/buffer/buffer.h b/spa/include/spa/buffer/buffer.h index c4aa8a556051446c8f409ab3749785d66e9a03c3..47204acbcfd260ff1d869eba4cb8026f469ecbdf 100644 --- a/spa/include/spa/buffer/buffer.h +++ b/spa/include/spa/buffer/buffer.h @@ -63,6 +63,9 @@ struct spa_chunk { int32_t stride; /**< stride of valid data */ #define SPA_CHUNK_FLAG_NONE 0 #define SPA_CHUNK_FLAG_CORRUPTED (1u<<0) /**< chunk data is corrupted in some way */ +#define SPA_CHUNK_FLAG_EMPTY (1u<<1) /**< chunk data is empty with media specific + * neutral data such as silence or black. This + * could be used to optimize processing. */ int32_t flags; /**< chunk flags */ }; diff --git a/spa/include/spa/interfaces/audio/aec.h b/spa/include/spa/interfaces/audio/aec.h index 17e4e4e463f63657a36168cb11beea4309a718a8..601f7b61e211b6d5c4f6edce01b266c9b9d2cab1 100644 --- a/spa/include/spa/interfaces/audio/aec.h +++ b/spa/include/spa/interfaces/audio/aec.h @@ -68,9 +68,9 @@ struct spa_audio_aec_methods { const struct spa_audio_aec_events *events, void *data); - int (*init) (void *data, const struct spa_dict *args, const struct spa_audio_info_raw *info); - int (*run) (void *data, const float *rec[], const float *play[], float *out[], uint32_t n_samples); - int (*set_props) (void *data, const struct spa_dict *args); + 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); }; #define spa_audio_aec_method(o,method,version,...) \ diff --git a/spa/include/spa/node/node.h b/spa/include/spa/node/node.h index f32adec16cd1e2563a93aa627c66e2eb3e6fcfc6..17be05d67f371088ab86dfa258d973acb7179c4f 100644 --- a/spa/include/spa/node/node.h +++ b/spa/include/spa/node/node.h @@ -632,6 +632,12 @@ struct spa_node_methods { * * When the node can accept new input in the next cycle, the * SPA_STATUS_NEED_DATA bit will be set. + * + * Note that the node might return SPA_STATUS_NEED_DATA even when + * no input ports have this status. This means that the amount of + * data still available on the input ports is likely not going to + * be enough for the next cycle and the host might need to prefetch + * data for the next cycle. */ int (*process) (void *object); }; diff --git a/spa/include/spa/param/audio/layout.h b/spa/include/spa/param/audio/layout.h index 1868144ef34e333838e67f24043001a8485d3ae3..66154bf62e10226436465e8d7886ff216474c9f0 100644 --- a/spa/include/spa/param/audio/layout.h +++ b/spa/include/spa/param/audio/layout.h @@ -29,7 +29,7 @@ extern "C" { #endif -#ifndef __FreeBSD__ +#if !defined(__FreeBSD__) && !defined(__MidnightBSD__) #include <endian.h> #endif diff --git a/spa/include/spa/param/audio/raw.h b/spa/include/spa/param/audio/raw.h index 47fe90dfa158ba0d07b906a69977771c8592ea09..a34915c422b32e47af5ec10ef785d8100c7518a5 100644 --- a/spa/include/spa/param/audio/raw.h +++ b/spa/include/spa/param/audio/raw.h @@ -31,7 +31,7 @@ extern "C" { #include <stdint.h> -#ifndef __FreeBSD__ +#if !defined(__FreeBSD__) && !defined(__MidnightBSD__) #include <endian.h> #endif diff --git a/spa/include/spa/param/bluetooth/audio.h b/spa/include/spa/param/bluetooth/audio.h index 5c215b411f273e88f3b80f81fef4236f27bb3493..f1037657c5df72c91222a0a8bdc9221afff77878 100644 --- a/spa/include/spa/param/bluetooth/audio.h +++ b/spa/include/spa/param/bluetooth/audio.h @@ -49,6 +49,11 @@ enum spa_bluetooth_audio_codec { SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM, SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX, SPA_BLUETOOTH_AUDIO_CODEC_LC3PLUS_HR, + SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05, + SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_51, + SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_71, + SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_DUPLEX, + SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO, /* HFP */ SPA_BLUETOOTH_AUDIO_CODEC_CVSD = 0x100, diff --git a/spa/include/spa/param/bluetooth/type-info.h b/spa/include/spa/param/bluetooth/type-info.h index 0471dcce853f77d93ad2d1321051ee3cb6ddd78d..8286b970ad2576556093b756d16fd46358e6c447 100644 --- a/spa/include/spa/param/bluetooth/type-info.h +++ b/spa/include/spa/param/bluetooth/type-info.h @@ -53,6 +53,11 @@ static const struct spa_type_info spa_type_bluetooth_audio_codec[] = { { SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "faststream", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "faststream_duplex", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_LC3PLUS_HR, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "lc3plus_hr", NULL }, + { SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "opus_05", NULL }, + { SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_51, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "opus_05_51", NULL }, + { SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_71, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "opus_05_71", NULL }, + { SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_DUPLEX, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "opus_05_duplex", NULL }, + { SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "opus_05_pro", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_CVSD, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "cvsd", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_MSBC, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "msbc", NULL }, diff --git a/spa/include/spa/utils/defs.h b/spa/include/spa/utils/defs.h index 874fab410f6d23c58b9f139444e642aee168f30f..cf15cc2446cd2fd7d7850aaa0960c7a48799afb1 100644 --- a/spa/include/spa/utils/defs.h +++ b/spa/include/spa/utils/defs.h @@ -131,13 +131,13 @@ struct spa_fraction { ({ \ __typeof__(a) _min_a = (a); \ __typeof__(b) _min_b = (b); \ - SPA_LIKELY(_min_a < _min_b) ? _min_a : _min_b; \ + SPA_LIKELY(_min_a <= _min_b) ? _min_a : _min_b; \ }) #define SPA_MAX(a,b) \ ({ \ __typeof__(a) _max_a = (a); \ __typeof__(b) _max_b = (b); \ - SPA_LIKELY(_max_a > _max_b) ? _max_a : _max_b; \ + SPA_LIKELY(_max_a >= _max_b) ? _max_a : _max_b; \ }) #define SPA_CLAMP(v,low,high) \ ({ \ @@ -147,6 +147,12 @@ struct spa_fraction { SPA_MIN(SPA_MAX(_v, _low), _high); \ }) +#define SPA_CLAMPF(v,low,high) \ +({ \ + fminf(fmaxf(v, low), high); \ +}) + + #define SPA_SWAP(a,b) \ ({ \ __typeof__(a) _t = (a); \ @@ -209,6 +215,7 @@ struct spa_fraction { #define SPA_SENTINEL __attribute__((__sentinel__)) #define SPA_UNUSED __attribute__ ((unused)) #define SPA_NORETURN __attribute__ ((noreturn)) +#define SPA_WARN_UNUSED_RESULT __attribute__ ((warn_unused_result)) #else #define SPA_PRINTF_FUNC(fmt, arg1) #define SPA_FORMAT_ARG_FUNC(arg1) @@ -218,6 +225,7 @@ struct spa_fraction { #define SPA_SENTINEL #define SPA_UNUSED #define SPA_NORETURN +#define SPA_WARN_UNUSED_RESULT #endif #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L diff --git a/spa/include/spa/utils/hook.h b/spa/include/spa/utils/hook.h index 9b1a50b63bc98a1377743171cab2a16a4d317a00..953b97445ed05f0986358af25f828693db695467 100644 --- a/spa/include/spa/utils/hook.h +++ b/spa/include/spa/utils/hook.h @@ -382,7 +382,8 @@ static inline void spa_hook_list_prepend(struct spa_hook_list *list, /** Remove a hook */ static inline void spa_hook_remove(struct spa_hook *hook) { - spa_list_remove(&hook->link); + if (spa_list_is_initialized(&hook->link)) + spa_list_remove(&hook->link); if (hook->removed) hook->removed(hook); } diff --git a/spa/include/spa/utils/json.h b/spa/include/spa/utils/json.h index 73cad7f59666dc6f04dc0258b3211b3d741144b0..19093169b87fb43da4d72bec9a9f7e1f487faaed 100644 --- a/spa/include/spa/utils/json.h +++ b/spa/include/spa/utils/json.h @@ -240,6 +240,8 @@ static inline bool spa_json_is_null(const char *val, int len) static inline int spa_json_parse_float(const char *val, int len, float *result) { char *end; + if (strspn(val, "+-0123456789.Ee") < (size_t)len) + return 0; *result = spa_strtof(val, &end); return len > 0 && end == val + len; } diff --git a/spa/include/spa/utils/list.h b/spa/include/spa/utils/list.h index 62aa9a3df7f9cc7cf6a4d5ee586d7ed1e46fc442..2300905edaf254418f54b543fc4f94084bbce183 100644 --- a/spa/include/spa/utils/list.h +++ b/spa/include/spa/utils/list.h @@ -51,6 +51,11 @@ static inline void spa_list_init(struct spa_list *list) *list = SPA_LIST_INIT(list); } +static inline int spa_list_is_initialized(struct spa_list *list) +{ + return !!list->prev; +} + #define spa_list_is_empty(l) ((l)->next == (l)) static inline void spa_list_insert(struct spa_list *list, struct spa_list *elem) diff --git a/spa/meson.build b/spa/meson.build index 82e274e9ff871c50f4217d78a7c3dc2c1b4fcde7..44b86eaf51968fd526b43e0266a0dea096d9aea8 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -62,6 +62,8 @@ if get_option('spa-plugins').allowed() endif endif summary({'LC3plus': lc3plus_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') + opus_dep = dependency('opus', required : get_option('bluez5-codec-opus')) + summary({'Opus': opus_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') endif avcodec_dep = dependency('libavcodec', required: get_option('ffmpeg')) jack_dep = dependency('jack', version : '>= 1.9.10', required: get_option('jack')) diff --git a/spa/plugins/aec/aec-null.c b/spa/plugins/aec/aec-null.c index 0dea9fc292b8ad81779248380373c4fa7f516e7b..b8d5b8528d4fb741749a1e6a7e7c0fe3694b113a 100644 --- a/spa/plugins/aec/aec-null.c +++ b/spa/plugins/aec/aec-null.c @@ -58,19 +58,18 @@ static int null_run(void *object, const float *rec[], const float *play[], float return 0; } -static struct spa_audio_aec_methods impl_aec = { +static const struct spa_audio_aec_methods impl_aec = { + SPA_VERSION_AUDIO_AEC, .init = null_init, .run = null_run, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { - struct impl *impl; - spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); - impl = (struct impl *) handle; + struct impl *impl = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_AUDIO_AEC)) *interface = &impl->aec; @@ -101,15 +100,13 @@ impl_init(const struct spa_handle_factory *factory, const struct spa_support *support, uint32_t n_support) { - struct impl *impl; - spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; - impl = (struct impl *) handle; + struct impl *impl = (struct impl *) handle; impl->aec.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_AUDIO_AEC, @@ -119,7 +116,7 @@ impl_init(const struct spa_handle_factory *factory, impl->aec.info = NULL; impl->aec.latency = NULL; - impl->log = (struct spa_log*)spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); spa_log_topic_init(impl->log, &log_topic); spa_hook_list_init(&impl->hooks_list); @@ -151,7 +148,7 @@ impl_enum_interface_info(const struct spa_handle_factory *factory, return 1; } -const struct spa_handle_factory spa_aec_null_factory = { +static const struct spa_handle_factory spa_aec_null_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_AEC, NULL, @@ -160,7 +157,6 @@ const struct spa_handle_factory spa_aec_null_factory = { impl_enum_interface_info, }; - SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { diff --git a/spa/plugins/aec/aec-webrtc.cpp b/spa/plugins/aec/aec-webrtc.cpp index 89abb05926deed25d1ff77e99d404144658e1642..5c129c534b5c2ee5fa17c9bc24a2c601fdb2a2e3 100644 --- a/spa/plugins/aec/aec-webrtc.cpp +++ b/spa/plugins/aec/aec-webrtc.cpp @@ -50,19 +50,17 @@ static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.eac.webrtc"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic -static bool webrtc_get_spa_bool(const struct spa_dict *args, const char *key, bool default_value) { - const char *str_val; - bool value = default_value; - str_val = spa_dict_lookup(args, key); - if (str_val != NULL) - value =spa_atob(str_val); - - return value; +static bool webrtc_get_spa_bool(const struct spa_dict *args, const char *key, bool default_value) +{ + if (auto str = spa_dict_lookup(args, key)) + return spa_atob(str); + + return default_value; } -static int webrtc_init(void *data, const struct spa_dict *args, const struct spa_audio_info_raw *info) +static int webrtc_init(void *object, const struct spa_dict *args, const struct spa_audio_info_raw *info) { - auto impl = reinterpret_cast<struct impl_data*>(data); + auto impl = static_cast<struct impl_data*>(object); bool extended_filter = webrtc_get_spa_bool(args, "webrtc.extended_filter", true); bool delay_agnostic = webrtc_get_spa_bool(args, "webrtc.delay_agnostic", true); @@ -122,9 +120,9 @@ static int webrtc_init(void *data, const struct spa_dict *args, const struct spa return 0; } -static int webrtc_run(void *data, const float *rec[], const float *play[], float *out[], uint32_t n_samples) +static int webrtc_run(void *object, const float *rec[], const float *play[], float *out[], uint32_t n_samples) { - auto impl = reinterpret_cast<struct impl_data*>(data); + auto impl = static_cast<struct impl_data*>(object); webrtc::StreamConfig config = webrtc::StreamConfig(impl->info.rate, impl->info.channels, false); unsigned int num_blocks = n_samples * 1000 / impl->info.rate / 10; @@ -160,7 +158,7 @@ static int webrtc_run(void *data, const float *rec[], const float *play[], float return 0; } -static struct spa_audio_aec_methods impl_aec = { +static const struct spa_audio_aec_methods impl_aec = { SPA_VERSION_AUDIO_AEC_METHODS, .add_listener = NULL, .init = webrtc_init, @@ -220,7 +218,7 @@ impl_init(const struct spa_handle_factory *factory, impl->aec.info = NULL; impl->aec.latency = "480/48000", - impl->log = (struct spa_log*)spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + impl->log = static_cast<struct spa_log *>(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log)); spa_log_topic_init(impl->log, &log_topic); return 0; @@ -250,7 +248,7 @@ impl_enum_interface_info(const struct spa_handle_factory *factory, return 1; } -const struct spa_handle_factory spa_aec_webrtc_factory = { +static const struct spa_handle_factory spa_aec_webrtc_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_AEC, NULL, @@ -259,7 +257,6 @@ const struct spa_handle_factory spa_aec_webrtc_factory = { impl_enum_interface_info, }; - SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { diff --git a/spa/plugins/alsa/90-pipewire-alsa.rules b/spa/plugins/alsa/90-pipewire-alsa.rules index 494e89d40c74157d50f49beaf51fd745ea81ed23..f451275b692d9fc158b224cd9336389213d8b114 100644 --- a/spa/plugins/alsa/90-pipewire-alsa.rules +++ b/spa/plugins/alsa/90-pipewire-alsa.rules @@ -177,6 +177,10 @@ ATTRS{idVendor}=="1395", ATTRS{idProduct}=="0300", ENV{ACP_PROFILE_SET}="usb-gam # Sennheiser GSP 670 USB headset ATTRS{idVendor}=="1395", ATTRS{idProduct}=="008a", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" +# Audioengine HD3 powered speakers support IEC958 but don't actually +# have any digital outputs. +ATTRS{idVendor}=="0a12", ATTRS{idProduct}=="4007", ENV{ACP_PROFILE_SET}="analog-only.conf" + GOTO="pipewire_end" LABEL="pipewire_check_pci" diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index 39475baf5ef7cfe905901e6e0a52d954615a6148..aea4b440d02e677be9b5d130dea54f1496290df5 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -382,8 +382,7 @@ static int add_pro_profile(pa_card *impl, uint32_t index) 0, NULL, NULL, false))) { pa_alsa_init_proplist_pcm(NULL, m->output_proplist, m->output_pcm); pa_proplist_setf(m->output_proplist, "clock.name", "api.alsa.%u", index); - snd_pcm_close(m->output_pcm); - m->output_pcm = NULL; + pa_alsa_close(&m->output_pcm); m->supported = true; pa_channel_map_init_auto(&m->channel_map, m->sample_spec.channels, PA_CHANNEL_MAP_AUX); } @@ -413,8 +412,7 @@ static int add_pro_profile(pa_card *impl, uint32_t index) 0, NULL, NULL, false))) { pa_alsa_init_proplist_pcm(NULL, m->input_proplist, m->input_pcm); pa_proplist_setf(m->input_proplist, "clock.name", "api.alsa.%u", index); - snd_pcm_close(m->input_pcm); - m->input_pcm = NULL; + pa_alsa_close(&m->input_pcm); m->supported = true; pa_channel_map_init_auto(&m->channel_map, m->sample_spec.channels, PA_CHANNEL_MAP_AUX); } @@ -1058,6 +1056,9 @@ static int read_volume(pa_alsa_device *dev) uint32_t i; int res; + if (!dev->mixer_handle) + return 0; + if ((res = pa_alsa_path_get_volume(dev->mixer_path, dev->mixer_handle, &dev->mapping->channel_map, &r)) < 0) return res; @@ -1089,6 +1090,9 @@ static void set_volume(pa_alsa_device *dev, const pa_cvolume *v) dev->real_volume = *v; + if (!dev->mixer_handle) + return; + /* Shift up by the base volume */ pa_sw_cvolume_divide_scalar(&r, &dev->real_volume, dev->base_volume); @@ -1139,6 +1143,9 @@ static int read_mute(pa_alsa_device *dev) bool mute; int res; + if (!dev->mixer_handle) + return 0; + if ((res = pa_alsa_path_get_mute(dev->mixer_path, dev->mixer_handle, &mute)) < 0) return res; @@ -1157,6 +1164,10 @@ static int read_mute(pa_alsa_device *dev) static void set_mute(pa_alsa_device *dev, bool mute) { dev->muted = mute; + + if (!dev->mixer_handle) + return; + pa_alsa_path_set_mute(dev->mixer_path, dev->mixer_handle, mute); } @@ -1735,7 +1746,8 @@ static void sync_mixer(pa_alsa_device *d, pa_device_port *port) setting = data->setting; } - pa_alsa_path_select(d->mixer_path, setting, d->mixer_handle, d->muted); + if (d->mixer_handle) + pa_alsa_path_select(d->mixer_path, setting, d->mixer_handle, d->muted); if (d->set_mute) d->set_mute(d, d->muted); diff --git a/spa/plugins/alsa/acp/alsa-mixer.c b/spa/plugins/alsa/acp/alsa-mixer.c index e19700ecb41fe4c827cb4ccd30de2125c098e24b..86425fd248c11ef34c1d85c1051851984a73638a 100644 --- a/spa/plugins/alsa/acp/alsa-mixer.c +++ b/spa/plugins/alsa/acp/alsa-mixer.c @@ -4966,8 +4966,7 @@ static void profile_finalize_probing(pa_alsa_profile *to_be_finalized, pa_alsa_p continue; pa_alsa_init_proplist_pcm(NULL, m->output_proplist, m->output_pcm); - snd_pcm_close(m->output_pcm); - m->output_pcm = NULL; + pa_alsa_close(&m->output_pcm); } if (to_be_finalized->input_mappings) @@ -4986,8 +4985,7 @@ static void profile_finalize_probing(pa_alsa_profile *to_be_finalized, pa_alsa_p continue; pa_alsa_init_proplist_pcm(NULL, m->input_proplist, m->input_pcm); - snd_pcm_close(m->input_pcm); - m->input_pcm = NULL; + pa_alsa_close(&m->input_pcm); } } diff --git a/spa/plugins/alsa/acp/alsa-ucm.c b/spa/plugins/alsa/acp/alsa-ucm.c index eea173b323a05fd6910e5548b716d9d9eaf21cdf..f66b771997bd2876e5a9289688e096b4776b78f5 100644 --- a/spa/plugins/alsa/acp/alsa-ucm.c +++ b/spa/plugins/alsa/acp/alsa-ucm.c @@ -1941,8 +1941,7 @@ static void profile_finalize_probing(pa_alsa_profile *p) { continue; pa_alsa_init_proplist_pcm(NULL, m->output_proplist, m->output_pcm); - snd_pcm_close(m->output_pcm); - m->output_pcm = NULL; + pa_alsa_close(&m->output_pcm); } PA_IDXSET_FOREACH(m, p->input_mappings, idx) { @@ -1953,8 +1952,7 @@ static void profile_finalize_probing(pa_alsa_profile *p) { continue; pa_alsa_init_proplist_pcm(NULL, m->input_proplist, m->input_pcm); - snd_pcm_close(m->input_pcm); - m->input_pcm = NULL; + pa_alsa_close(&m->input_pcm); } } diff --git a/spa/plugins/alsa/acp/alsa-util.c b/spa/plugins/alsa/acp/alsa-util.c index 0a365974ecb506b73bd79a4fd32e364e3cb344c1..c76cef3e21c7d1758e3d79679888406e9a36eeeb 100644 --- a/spa/plugins/alsa/acp/alsa-util.c +++ b/spa/plugins/alsa/acp/alsa-util.c @@ -656,6 +656,20 @@ snd_pcm_t *pa_alsa_open_by_device_id_mapping( return pcm_handle; } +int pa_alsa_close(snd_pcm_t **pcm) +{ + int err; + pa_assert(pcm); + pa_log_info("ALSA device close %p", *pcm); + if (*pcm == NULL) + return 0; + if ((err = snd_pcm_close(*pcm)) < 0) { + pa_log_warn("ALSA close failed: %s", snd_strerror(err)); + } + *pcm = NULL; + return err; +} + snd_pcm_t *pa_alsa_open_by_device_string( const char *device, char **dev, @@ -691,8 +705,8 @@ snd_pcm_t *pa_alsa_open_by_device_string( pa_log_info("Error opening PCM device %s: %s", d, pa_alsa_strerror(err)); goto fail; } - - pa_log_debug("Managed to open %s", d); + pa_log_info("ALSA device open '%s' %s: %p", d, + mode == SND_PCM_STREAM_CAPTURE ? "capture" : "playback", pcm_handle); if ((err = pa_alsa_set_hw_params( pcm_handle, @@ -707,7 +721,7 @@ snd_pcm_t *pa_alsa_open_by_device_string( if (!reformat) { reformat = true; - snd_pcm_close(pcm_handle); + pa_alsa_close(&pcm_handle); continue; } @@ -721,12 +735,12 @@ snd_pcm_t *pa_alsa_open_by_device_string( reformat = false; - snd_pcm_close(pcm_handle); + pa_alsa_close(&pcm_handle); continue; } pa_log_info("Failed to set hardware parameters on %s: %s", d, pa_alsa_strerror(err)); - snd_pcm_close(pcm_handle); + pa_alsa_close(&pcm_handle); goto fail; } @@ -734,7 +748,7 @@ snd_pcm_t *pa_alsa_open_by_device_string( if (ss->channels > PA_CHANNELS_MAX) { pa_log("Device %s has %u channels, but PulseAudio supports only %u channels. Unable to use the device.", d, ss->channels, PA_CHANNELS_MAX); - snd_pcm_close(pcm_handle); + pa_alsa_close(&pcm_handle); goto fail; } @@ -1626,7 +1640,12 @@ static int mixer_class_event(snd_mixer_class_t *class, unsigned int mask, { int err; const char *name = snd_hctl_elem_get_name(helem); - if (mask & SND_CTL_EVENT_MASK_ADD) { + // NOTE: The remove event defined as '~0U`. + if (mask == SND_CTL_EVENT_MASK_REMOVE) { + // NOTE: unless remove pointer to melem from link-list at private_data of helem, hits + // assersion in alsa-lib since the list is not empty. + snd_mixer_elem_detach(melem, helem); + } else if (mask & SND_CTL_EVENT_MASK_ADD) { snd_ctl_elem_iface_t iface = snd_hctl_elem_get_interface(helem); if (iface == SND_CTL_ELEM_IFACE_CARD || iface == SND_CTL_ELEM_IFACE_PCM) { snd_mixer_elem_t *new_melem; diff --git a/spa/plugins/alsa/acp/alsa-util.h b/spa/plugins/alsa/acp/alsa-util.h index 76b8402ace34b8405f34defd10587226aaf7c1d8..b18b98df9ce98a85e8467831794f6609e82c5f1f 100644 --- a/spa/plugins/alsa/acp/alsa-util.h +++ b/spa/plugins/alsa/acp/alsa-util.h @@ -115,6 +115,7 @@ snd_pcm_t *pa_alsa_open_by_template( void pa_alsa_dump(pa_log_level_t level, snd_pcm_t *pcm); void pa_alsa_dump_status(snd_pcm_t *pcm); #endif +int pa_alsa_close(snd_pcm_t **pcm); void pa_alsa_refcnt_inc(void); void pa_alsa_refcnt_dec(void); diff --git a/spa/plugins/alsa/alsa-pcm-sink.c b/spa/plugins/alsa/alsa-pcm-sink.c index b26554b683c0f8e53a194ea0e589cbbf2e6ca4df..a0b5b4e6862571828872d54c9456eaf85725a53d 100644 --- a/spa/plugins/alsa/alsa-pcm-sink.c +++ b/spa/plugins/alsa/alsa-pcm-sink.c @@ -616,7 +616,7 @@ static int port_set_format(void *object, const struct spa_pod *format) { struct state *this = object; - int err; + int err = 0; if (format == NULL) { if (!this->have_format) @@ -673,7 +673,7 @@ static int port_set_format(void *object, } emit_port_info(this, false); - return 0; + return err; } static int @@ -707,6 +707,7 @@ impl_node_port_set_param(void *object, this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; this->port_params[PORT_Latency].user++; emit_port_info(this, false); + res = 0; break; } default: @@ -797,38 +798,38 @@ static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t static int impl_node_process(void *object) { struct state *this = object; - struct spa_io_buffers *input; + struct spa_io_buffers *io; spa_return_val_if_fail(this != NULL, -EINVAL); - input = this->io; - spa_return_val_if_fail(input != NULL, -EIO); + if ((io = this->io) == NULL) + return -EIO; - spa_log_trace_fp(this->log, "%p: process %d %d/%d", this, input->status, - input->buffer_id, this->n_buffers); + spa_log_trace_fp(this->log, "%p: process %d %d/%d", this, io->status, + io->buffer_id, this->n_buffers); if (this->position && this->position->clock.flags & SPA_IO_CLOCK_FLAG_FREEWHEEL) { - input->status = SPA_STATUS_NEED_DATA; + io->status = SPA_STATUS_NEED_DATA; return SPA_STATUS_HAVE_DATA; } - if (input->status == SPA_STATUS_HAVE_DATA && - input->buffer_id < this->n_buffers) { - struct buffer *b = &this->buffers[input->buffer_id]; + if (io->status == SPA_STATUS_HAVE_DATA && + io->buffer_id < this->n_buffers) { + struct buffer *b = &this->buffers[io->buffer_id]; if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { spa_log_warn(this->log, "%p: buffer %u in use", - this, input->buffer_id); - input->status = -EINVAL; + this, io->buffer_id); + io->status = -EINVAL; return -EINVAL; } - spa_log_trace_fp(this->log, "%p: queue buffer %u", this, input->buffer_id); + spa_log_trace_fp(this->log, "%p: queue buffer %u", this, io->buffer_id); spa_list_append(&this->ready, &b->link); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); - input->buffer_id = SPA_ID_INVALID; + io->buffer_id = SPA_ID_INVALID; spa_alsa_write(this); - input->status = SPA_STATUS_OK; + io->status = SPA_STATUS_OK; } return SPA_STATUS_HAVE_DATA; } diff --git a/spa/plugins/alsa/alsa-pcm-source.c b/spa/plugins/alsa/alsa-pcm-source.c index c4222dc8912285c9e00240f1d95622324741f55b..a1e9690cf138002c2647f914239c02d0e414309b 100644 --- a/spa/plugins/alsa/alsa-pcm-source.c +++ b/spa/plugins/alsa/alsa-pcm-source.c @@ -566,7 +566,7 @@ static int port_set_format(void *object, uint32_t flags, const struct spa_pod *format) { struct state *this = object; - int err; + int err = 0; if (format == NULL) { if (!this->have_format) @@ -610,7 +610,7 @@ static int port_set_format(void *object, } emit_port_info(this, false); - return 0; + return err; } static int @@ -753,8 +753,8 @@ static int impl_node_process(void *object) spa_return_val_if_fail(this != NULL, -EINVAL); - io = this->io; - spa_return_val_if_fail(io != NULL, -EIO); + if ((io = this->io) == NULL) + return -EIO; spa_log_trace_fp(this->log, "%p; status %d", this, io->status); diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index de321ee3cd36f3a20a4ae11800f1fef42cf27316..c171e5f888a8203d4c1473e090dab0c095127803 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -10,6 +10,7 @@ #include <spa/pod/filter.h> #include <spa/utils/string.h> +#include <spa/utils/result.h> #include <spa/support/system.h> #include <spa/utils/keys.h> @@ -442,9 +443,31 @@ int spa_alsa_parse_prop_params(struct state *state, struct spa_pod *params) return changed; } +#define CHECK(s,msg,...) if ((err = (s)) < 0) { spa_log_error(state->log, msg ": %s", ##__VA_ARGS__, snd_strerror(err)); return err; } + +static ssize_t log_write(void *cookie, const char *buf, size_t size) +{ + struct state *state = cookie; + int len; + + while (size > 0) { + len = strcspn(buf, "\n"); + if (len > 0) + spa_log_debug(state->log, "%.*s", (int)len, buf); + buf += len + 1; + size -= len + 1; + } + return size; +} + +static cookie_io_functions_t io_funcs = { + .write = log_write, +}; + int spa_alsa_init(struct state *state, const struct spa_dict *info) { uint32_t i; + int err; snd_config_update_free_global(); @@ -481,20 +504,31 @@ int spa_alsa_init(struct state *state, const struct spa_dict *info) spa_log_error(state->log, "can't create card %u", state->card_index); return -errno; } + state->log_file = fopencookie(state, "w", io_funcs); + if (state->log_file == NULL) { + spa_log_error(state->log, "can't create log file"); + return -errno; + } + CHECK(snd_output_stdio_attach(&state->output, state->log_file, 0), "attach failed"); + return 0; } int spa_alsa_clear(struct state *state) { + int err; + release_card(state->card); state->card = NULL; state->card_index = SPA_ID_INVALID; - return 0; -} + if ((err = snd_output_close(state->output)) < 0) + spa_log_warn(state->log, "output close failed: %s", snd_strerror(err)); + fclose(state->log_file); -#define CHECK(s,msg,...) if ((err = (s)) < 0) { spa_log_error(state->log, msg ": %s", ##__VA_ARGS__, snd_strerror(err)); return err; } + return err; +} int spa_alsa_open(struct state *state, const char *params) { @@ -505,8 +539,6 @@ int spa_alsa_open(struct state *state, const char *params) if (state->opened) return 0; - CHECK(snd_output_stdio_attach(&state->output, stderr, 0), "attach failed"); - spa_scnprintf(device_name, sizeof(device_name), "%s%s%s", state->card->ucm_prefix ? state->card->ucm_prefix : "", props->device, params ? params : ""); @@ -538,6 +570,8 @@ int spa_alsa_open(struct state *state, const char *params) return 0; error_exit_close: + spa_log_info(state->log, "%p: Device '%s' closing: %s", state, state->props.device, + spa_strerror(err)); snd_pcm_close(state->hndl); return err; } @@ -556,9 +590,6 @@ int spa_alsa_close(struct state *state) spa_log_warn(state->log, "%s: close failed: %s", state->props.device, snd_strerror(err)); - if ((err = snd_output_close(state->output)) < 0) - spa_log_warn(state->log, "output close failed: %s", snd_strerror(err)); - spa_system_close(state->data_system, state->timerfd); if (state->have_format) @@ -752,7 +783,7 @@ static bool uint32_array_contains(uint32_t *vals, uint32_t n_vals, uint32_t val) } static int add_rate(struct state *state, uint32_t scale, bool all, uint32_t index, uint32_t *next, - snd_pcm_hw_params_t *params, struct spa_pod_builder *b) + uint32_t min_allowed_rate, snd_pcm_hw_params_t *params, struct spa_pod_builder *b) { struct spa_pod_frame f[1]; int err, dir; @@ -763,6 +794,12 @@ 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); + + min_allowed_rate /= scale; + min = SPA_MAX(min_allowed_rate, min); + if (!state->multi_rate && state->card->format_ref > 0) rate = state->card->rate; else @@ -779,6 +816,9 @@ static int add_rate(struct state *state, uint32_t scale, bool all, uint32_t inde rate = SPA_CLAMP(rate, min, max); + spa_log_debug(state->log, "rate:%u multi:%d card:%d def:%d", + rate, state->multi_rate, state->card->rate, state->default_rate); + spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0); spa_pod_builder_push_choice(b, &f[0], SPA_CHOICE_None, 0); @@ -830,7 +870,8 @@ static int add_channels(struct state *state, bool all, uint32_t index, uint32_t CHECK(snd_pcm_hw_params_get_channels_min(params, &min), "get_channels_min"); CHECK(snd_pcm_hw_params_get_channels_max(params, &max), "get_channels_max"); - spa_log_debug(state->log, "channels (%d %d)", min, max); + spa_log_debug(state->log, "channels (%d %d) default:%d all:%d", + min, max, state->default_channels, all); if (state->default_channels != 0 && !all) { if (min < state->default_channels) @@ -911,6 +952,14 @@ skip_channels: return 1; } +static void debug_hw_params(struct state *state, const char *prefix, snd_pcm_hw_params_t *params) +{ + if (SPA_UNLIKELY(spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_DEBUG))) { + spa_log_debug(state->log, "%s:", prefix); + snd_pcm_hw_params_dump(params, state->output); + fflush(state->log_file); + } +} static int enum_pcm_formats(struct state *state, uint32_t index, uint32_t *next, struct spa_pod **result, struct spa_pod_builder *b) { @@ -928,6 +977,8 @@ static int enum_pcm_formats(struct state *state, uint32_t index, uint32_t *next, snd_pcm_hw_params_alloca(¶ms); CHECK(snd_pcm_hw_params_any(hndl, params), "Broken configuration: no configurations available"); + debug_hw_params(state, __func__, params); + CHECK(snd_pcm_hw_params_set_rate_resample(hndl, params, 0), "set_rate_resample"); if (state->default_channels != 0) { @@ -1017,7 +1068,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, params, b)) != 1) + if ((res = add_rate(state, 1, false, index & 0xffff, next, 0, params, b)) != 1) return res; if ((res = add_channels(state, false, index & 0xffff, next, params, b)) != 1) @@ -1074,6 +1125,8 @@ static int enum_iec958_formats(struct state *state, uint32_t index, uint32_t *ne snd_pcm_hw_params_alloca(¶ms); CHECK(snd_pcm_hw_params_any(hndl, params), "Broken configuration: no configurations available"); + debug_hw_params(state, __func__, params); + CHECK(snd_pcm_hw_params_set_rate_resample(hndl, params, 0), "set_rate_resample"); spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); @@ -1110,7 +1163,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, params, b)) != 1) + if ((res = add_rate(state, 1, true, index & 0xffff, next, 0, params, b)) != 1) return res; (*next)++; @@ -1135,6 +1188,8 @@ static int enum_dsd_formats(struct state *state, uint32_t index, uint32_t *next, snd_pcm_hw_params_alloca(¶ms); CHECK(snd_pcm_hw_params_any(hndl, params), "Broken configuration: no configurations available"); + debug_hw_params(state, __func__, params); + snd_pcm_format_mask_alloca(&fmask); snd_pcm_hw_params_get_format_mask(params, fmask); @@ -1165,7 +1220,14 @@ static int enum_dsd_formats(struct state *state, uint32_t index, uint32_t *next, spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_interleave, 0); spa_pod_builder_int(b, interleave); - if ((res = add_rate(state, SPA_ABS(interleave), true, index & 0xffff, next, params, b)) != 1) + /* Use a lower rate limit of 352800 (= 44100 * 64 / 8). This is because in + * PipeWire, DSD rates are given in bytes, not bits, so 352800 corresponds + * to the bit rate of DSD64. (The "64" in DSD64 means "64 times the rate + * of 44.1 kHz".) Some hardware may report rates lower than that, for example + * 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) return res; if ((res = add_channels(state, true, index & 0xffff, next, params, b)) != 1) @@ -1187,7 +1249,12 @@ spa_alsa_enum_format(struct state *state, int seq, uint32_t start, uint32_t num, struct spa_result_node_params result; uint32_t count = 0; + spa_log_debug(state->log, "opened:%d format:%d started:%d", state->opened, + state->have_format, state->started); + opened = state->opened; + if (!state->started && state->have_format) + spa_alsa_close(state); if ((err = spa_alsa_open(state, NULL)) < 0) return err; @@ -1248,6 +1315,9 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ bool match = true, planar = false, is_batch; char spdif_params[128] = ""; + spa_log_debug(state->log, "opened:%d format:%d started:%d", state->opened, + state->have_format, state->started); + state->use_mmap = !state->disable_mmap; switch (fmt->media_subtype) { @@ -1360,6 +1430,8 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ return -EINVAL; } + if (!state->started && state->have_format) + spa_alsa_close(state); if ((err = spa_alsa_open(state, spdif_params)) < 0) return err; @@ -1368,6 +1440,9 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ snd_pcm_hw_params_alloca(¶ms); /* choose all parameters */ CHECK(snd_pcm_hw_params_any(hndl, params), "Broken configuration for playback: no configurations available"); + + debug_hw_params(state, __func__, params); + /* set hardware resampling, no resample */ CHECK(snd_pcm_hw_params_set_rate_resample(hndl, params, 0), "set_rate_resample"); @@ -1409,7 +1484,10 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ state->props.device, rchannels, val); if (!SPA_FLAG_IS_SET(flags, SPA_NODE_PARAM_FLAG_NEAREST)) return -EINVAL; + if (fmt->media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -EINVAL; rchannels = val; + fmt->info.raw.channels = rchannels; match = false; } @@ -1429,7 +1507,10 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ state->props.device, rrate, val); if (!SPA_FLAG_IS_SET(flags, SPA_NODE_PARAM_FLAG_NEAREST)) return -EINVAL; + if (fmt->media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -EINVAL; rrate = val; + fmt->info.raw.rate = rrate; match = false; } @@ -1549,6 +1630,12 @@ static int set_swparams(struct state *state) /* write the parameters to the playback device */ CHECK(snd_pcm_sw_params(hndl, params), "sw_params"); + if (SPA_UNLIKELY(spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_DEBUG))) { + spa_log_debug(state->log, "state after sw_params:"); + snd_pcm_dump(hndl, state->output); + fflush(state->log_file); + } + return 0; } @@ -1762,6 +1849,7 @@ static int get_status(struct state *state, uint64_t current_time, *delay = avail; *target = SPA_MAX(*target, state->read_size); } + *target = SPA_MIN(*target, state->buffer_frames); return 0; } @@ -1895,6 +1983,7 @@ int spa_alsa_write(struct state *state) snd_pcm_uframes_t written, frames, offset, off, to_write, total_written, max_write; snd_pcm_sframes_t commitres; int res = 0; + size_t frame_size = state->frame_size; check_position_config(state); @@ -1951,61 +2040,43 @@ again: written = 0; while (!spa_list_is_empty(&state->ready) && to_write > 0) { - uint8_t *dst, *src; size_t n_bytes, n_frames; struct buffer *b; struct spa_data *d; - uint32_t i, index, offs, avail, size, maxsize, l0, l1; + uint32_t i, offs, size, last_offset; b = spa_list_first(&state->ready, struct buffer, link); d = b->buf->datas; - size = d[0].chunk->size; - maxsize = d[0].maxsize; + offs = d[0].chunk->offset + state->ready_offset; + last_offset = d[0].chunk->size; + size = last_offset - state->ready_offset; - index = d[0].chunk->offset + state->ready_offset; - avail = size - state->ready_offset; - avail /= state->frame_size; + offs = SPA_MIN(offs, d[0].maxsize); + size = SPA_MIN(d[0].maxsize - offs, size); - n_frames = SPA_MIN(avail, to_write); - n_bytes = n_frames * state->frame_size; - - offs = index % maxsize; - l0 = SPA_MIN(n_bytes, maxsize - offs); - l1 = n_bytes - l0; + n_frames = SPA_MIN(size / frame_size, to_write); + n_bytes = n_frames * frame_size; if (SPA_LIKELY(state->use_mmap)) { for (i = 0; i < b->buf->n_datas; i++) { - dst = SPA_PTROFF(my_areas[i].addr, off * state->frame_size, uint8_t); - src = d[i].data; - - spa_memcpy(dst, src + offs, l0); - if (SPA_UNLIKELY(l1 > 0)) - spa_memcpy(dst + l0, src, l1); + spa_memcpy(SPA_PTROFF(my_areas[i].addr, off * frame_size, void), + SPA_PTROFF(d[i].data, offs, void), n_bytes); } } else { - if (state->planar) { - void *bufs[b->buf->n_datas]; - - for (i = 0; i < b->buf->n_datas; i++) - bufs[i] = SPA_PTROFF(d[i].data, offs, void); - snd_pcm_writen(hndl, bufs, l0 / state->frame_size); - if (SPA_UNLIKELY(l1 > 0)) { - for (i = 0; i < b->buf->n_datas; i++) - bufs[i] = d[i].data; - snd_pcm_writen(hndl, bufs, l1 / state->frame_size); - } - } else { - src = d[0].data; - snd_pcm_writei(hndl, src + offs, l0 / state->frame_size); - if (SPA_UNLIKELY(l1 > 0)) - snd_pcm_writei(hndl, src, l1 / state->frame_size); - } + void *bufs[b->buf->n_datas]; + for (i = 0; i < b->buf->n_datas; i++) + bufs[i] = SPA_PTROFF(d[i].data, offs, void); + + if (state->planar) + snd_pcm_writen(hndl, bufs, n_frames); + else + snd_pcm_writei(hndl, bufs[0], n_frames); } state->ready_offset += n_bytes; - if (state->ready_offset >= size) { + if (state->ready_offset >= last_offset) { spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); state->io->buffer_id = b->id; @@ -2071,8 +2142,7 @@ push_frames(struct state *state, spa_log_warn(state->log, "%s: no more buffers", state->props.device); total_frames = frames; } else { - uint8_t *src; - size_t n_bytes, left; + size_t n_bytes, left, frame_size = state->frame_size; struct buffer *b; struct spa_data *d; uint32_t i, avail, l0, l1; @@ -2088,23 +2158,26 @@ push_frames(struct state *state, d = b->buf->datas; - avail = d[0].maxsize / state->frame_size; + avail = d[0].maxsize / frame_size; total_frames = SPA_MIN(avail, frames); - n_bytes = total_frames * state->frame_size; + n_bytes = total_frames * frame_size; if (my_areas) { left = state->buffer_frames - offset; - l0 = SPA_MIN(n_bytes, left * state->frame_size); + l0 = SPA_MIN(n_bytes, left * frame_size); l1 = n_bytes - l0; for (i = 0; i < b->buf->n_datas; i++) { - src = SPA_PTROFF(my_areas[i].addr, offset * state->frame_size, uint8_t); - spa_memcpy(d[i].data, src, l0); - if (l1 > 0) - spa_memcpy(SPA_PTROFF(d[i].data, l0, void), my_areas[i].addr, l1); + spa_memcpy(d[i].data, + SPA_PTROFF(my_areas[i].addr, offset * frame_size, void), + l0); + if (SPA_UNLIKELY(l1 > 0)) + spa_memcpy(SPA_PTROFF(d[i].data, l0, void), + my_areas[i].addr, + l1); d[i].chunk->offset = 0; d[i].chunk->size = n_bytes; - d[i].chunk->stride = state->frame_size; + d[i].chunk->stride = frame_size; } } else { void *bufs[b->buf->n_datas]; @@ -2112,7 +2185,7 @@ push_frames(struct state *state, bufs[i] = d[i].data; d[i].chunk->offset = 0; d[i].chunk->size = n_bytes; - d[i].chunk->stride = state->frame_size; + d[i].chunk->stride = frame_size; } if (state->planar) { snd_pcm_readn(state->hndl, bufs, total_frames); @@ -2350,7 +2423,7 @@ static void alsa_on_timeout_event(struct spa_source *source) } #ifndef FASTPATH - if (SPA_UNLIKELY(spa_log_level_enabled(state->log, SPA_LOG_LEVEL_TRACE))) { + if (SPA_UNLIKELY(spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE))) { struct timespec now; uint64_t nsec; if (spa_system_clock_gettime(state->data_system, CLOCK_MONOTONIC, &now) < 0) @@ -2445,8 +2518,6 @@ int spa_alsa_start(struct state *state) state->following, state->matching, state->resample); CHECK(set_swparams(state), "swparams"); - if (SPA_UNLIKELY(spa_log_level_enabled(state->log, SPA_LOG_LEVEL_DEBUG))) - snd_pcm_dump(state->hndl, state->output); if ((err = snd_pcm_prepare(state->hndl)) < 0 && err != -EBUSY) { spa_log_error(state->log, "%s: snd_pcm_prepare error: %s", diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index 6f16e36961beba10b3e1c1c13bbd2373fa4789f6..91385f40d05342b80937672dac4d19a142e2bf10 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -106,6 +106,8 @@ struct state { struct spa_system *data_system; struct spa_loop *data_loop; + FILE *log_file; + uint32_t card_index; struct card *card; snd_pcm_stream_t stream; diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index 55f84b5ca76d2d9b2071fa2416568be8daebdd23..381bbc5e10d3661f4a800939587ccfdd1e7d2078 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -131,13 +131,19 @@ static int seq_close(struct seq_state *state, struct seq_conn *conn) static int init_stream(struct seq_state *state, enum spa_direction direction) { struct seq_stream *stream = &state->streams[direction]; + int res; stream->direction = direction; if (direction == SPA_DIRECTION_INPUT) { stream->caps = SND_SEQ_PORT_CAP_SUBS_WRITE; } else { stream->caps = SND_SEQ_PORT_CAP_SUBS_READ; } - snd_midi_event_new(MAX_EVENT_SIZE, &stream->codec); + if ((res = snd_midi_event_new(MAX_EVENT_SIZE, &stream->codec)) < 0) { + spa_log_error(state->log, "can make event decoder: %s", + snd_strerror(res)); + return res; + } + snd_midi_event_no_status(stream->codec, 1); memset(stream->ports, 0, sizeof(stream->ports)); return 0; } @@ -145,7 +151,9 @@ static int init_stream(struct seq_state *state, enum spa_direction direction) static int uninit_stream(struct seq_state *state, enum spa_direction direction) { struct seq_stream *stream = &state->streams[direction]; - snd_midi_event_free(stream->codec); + if (stream->codec) + snd_midi_event_free(stream->codec); + stream->codec = NULL; return 0; } @@ -178,7 +186,7 @@ static void init_ports(struct seq_state *state) static void debug_event(struct seq_state *state, snd_seq_event_t *ev) { - if (SPA_LIKELY(!spa_log_level_enabled(state->log, SPA_LOG_LEVEL_TRACE))) + if (SPA_LIKELY(!spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE))) return; spa_log_trace(state->log, "event type:%d flags:0x%x", ev->type, ev->flags); @@ -543,12 +551,6 @@ static int process_read(struct seq_state *state) continue; } - /* fixup NoteOn with vel 0 */ - if ((data[0] & 0xF0) == 0x90 && data[2] == 0x00) { - data[0] = 0x80 + (data[0] & 0x0F); - data[2] = 0x40; - } - /* queue_time is the estimated current time of the queue as calculated by * the DLL. Calculate the age of the event. */ ev_time = SPA_TIMESPEC_TO_NSEC(&ev->time.time); diff --git a/spa/plugins/alsa/alsa-udev.c b/spa/plugins/alsa/alsa-udev.c index 8501ff642a6b086f11bcd741e6261434271babf2..8ee217d9dff3e380769770e81b7227d686e5a08b 100644 --- a/spa/plugins/alsa/alsa-udev.c +++ b/spa/plugins/alsa/alsa-udev.c @@ -478,11 +478,10 @@ static int emit_object_info(struct impl *this, struct device *device) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_SUBSYSTEM, str); } if ((str = udev_device_get_property_value(dev, "ID_VENDOR_ID")) && *str) { - char *dec = alloca(6); /* 65535 is max */ int32_t val; - if (spa_atoi32(str, &val, 16)) { - snprintf(dec, 6, "%d", val); + char *dec = alloca(12); /* 0xffffffff is max */ + snprintf(dec, 12, "0x%04x", val); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_ID, dec); } } @@ -501,11 +500,10 @@ static int emit_object_info(struct impl *this, struct device *device) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_NAME, str); } if ((str = udev_device_get_property_value(dev, "ID_MODEL_ID")) && *str) { - char *dec = alloca(6); /* 65535 is max */ int32_t val; - if (spa_atoi32(str, &val, 16)) { - snprintf(dec, 6, "%d", val); + char *dec = alloca(12); /* 0xffffffff is max */ + snprintf(dec, 12, "0x%04x", val); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PRODUCT_ID, dec); } } @@ -540,11 +538,35 @@ static int emit_object_info(struct impl *this, struct device *device) static bool check_access(struct impl *this, struct device *device) { - char path[128]; - bool accessible; + char path[128], prefix[32]; + DIR *snd = NULL; + struct dirent *entry; + bool accessible = false; snprintf(path, sizeof(path), "/dev/snd/controlC%u", device->id); - accessible = access(path, R_OK|W_OK) >= 0; + if (access(path, R_OK|W_OK) >= 0 && (snd = opendir("/dev/snd"))) { + /* + * It's possible that controlCX is accessible before pcmCX* or + * the other way around. Return true only if all devices are + * accessible. + */ + + accessible = true; + spa_scnprintf(prefix, sizeof(prefix), "pcmC%uD", device->id); + while ((entry = readdir(snd)) != NULL) { + if (!(entry->d_type == DT_CHR && + spa_strstartswith(entry->d_name, prefix))) + continue; + + snprintf(path, sizeof(path), "/dev/snd/%.32s", entry->d_name); + if (access(path, R_OK|W_OK) < 0) { + accessible = false; + break; + } + } + closedir(snd); + } + if (accessible != device->accessible) spa_log_debug(this->log, "%s accessible:%u", path, accessible); device->accessible = accessible; @@ -657,10 +679,6 @@ static void impl_on_notify_events(struct spa_source *source) /* Device becomes accessible or not busy */ if ((event->mask & (IN_ATTRIB | IN_CLOSE_WRITE))) { bool access; - - if ((event->mask & IN_ATTRIB) && - spa_strstartswith(event->name, "pcm")) - continue; if (sscanf(event->name, "controlC%u", &id) != 1 && sscanf(event->name, "pcmC%uD", &id) != 1) continue; diff --git a/spa/plugins/alsa/meson.build b/spa/plugins/alsa/meson.build index 0584139876ac15c395adb459b314468d098ad84f..12bb5b9f4aa098a92190eb7f6f5c6a524e6d099d 100644 --- a/spa/plugins/alsa/meson.build +++ b/spa/plugins/alsa/meson.build @@ -34,13 +34,18 @@ executable('spa-acp-tool', install : true, ) - executable('test-timer', [ 'test-timer.c' ], dependencies : [ spa_dep, alsa_dep, mathlib, epoll_shim_dep ], install : false, ) +executable('test-hw-params', + [ 'test-hw-params.c' ], + dependencies : [ spa_dep, alsa_dep, mathlib ], + install : false, +) + if libudev_dep.found() install_data(alsa_udevrules, install_dir : udevrulesdir, diff --git a/spa/plugins/alsa/mixer/profile-sets/analog-only.conf b/spa/plugins/alsa/mixer/profile-sets/analog-only.conf new file mode 100644 index 0000000000000000000000000000000000000000..badd5ec0c11681e5e0fa8dc0fd1c9ed57e05caca --- /dev/null +++ b/spa/plugins/alsa/mixer/profile-sets/analog-only.conf @@ -0,0 +1,102 @@ +# PulseAudio is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation; either version 2.1 of the +# License, or (at your option) any later version. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. + +; Some USB DACs appear to support IEC958, but don't physically have any +; digital outputs. + +[General] +auto-profiles = yes + +[Mapping analog-stereo] +device-strings = front:%f +channel-map = left,right +paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2 +paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headphone-mic analog-input-headset-mic +priority = 15 + +# If everything else fails, try to use hw:0 as a stereo device... +[Mapping stereo-fallback] +device-strings = hw:%f +fallback = yes +channel-map = front-left,front-right +paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2 +paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headphone-mic analog-input-headset-mic +priority = 1 + +# ...and if even that fails, try to use hw:0 as a mono device. +[Mapping mono-fallback] +device-strings = hw:%f +fallback = yes +channel-map = mono +paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2 analog-output-mono +paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headset-mic +priority = 1 + +[Mapping analog-surround-21] +device-strings = surround21:%f +channel-map = front-left,front-right,lfe +paths-input = analog-input analog-input-linein analog-input-mic +paths-output = analog-output analog-output-lineout analog-output-speaker +priority = 13 + +[Mapping analog-surround-40] +device-strings = surround40:%f +channel-map = front-left,front-right,rear-left,rear-right +paths-input = analog-input analog-input-linein analog-input-mic +paths-output = analog-output analog-output-lineout analog-output-speaker +priority = 12 + +[Mapping analog-surround-41] +device-strings = surround41:%f +channel-map = front-left,front-right,rear-left,rear-right,lfe +paths-input = analog-input analog-input-linein analog-input-mic +paths-output = analog-output analog-output-lineout analog-output-speaker +priority = 13 + +[Mapping analog-surround-50] +device-strings = surround50:%f +channel-map = front-left,front-right,rear-left,rear-right,front-center +paths-input = analog-input analog-input-linein analog-input-mic +paths-output = analog-output analog-output-lineout analog-output-speaker +priority = 12 + +[Mapping analog-surround-51] +device-strings = surround51:%f +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe +paths-input = analog-input analog-input-linein analog-input-mic +paths-output = analog-output analog-output-lineout analog-output-speaker +priority = 13 + +[Mapping analog-surround-71] +device-strings = surround71:%f +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right +description = Analog Surround 7.1 +paths-input = analog-input analog-input-linein analog-input-mic +paths-output = analog-output analog-output-lineout analog-output-speaker +priority = 12 + +[Mapping multichannel-output] +device-strings = hw:%f +channel-map = left,right,rear-left,rear-right +exact-channels = false +fallback = yes +priority = 1 +direction = output + +[Mapping multichannel-input] +device-strings = hw:%f +channel-map = left,right,rear-left,rear-right +exact-channels = false +fallback = yes +priority = 1 +direction = input diff --git a/spa/plugins/alsa/test-hw-params.c b/spa/plugins/alsa/test-hw-params.c new file mode 100644 index 0000000000000000000000000000000000000000..7315061232ecd1fcbb732c52693d307ae9d26bad --- /dev/null +++ b/spa/plugins/alsa/test-hw-params.c @@ -0,0 +1,173 @@ +/* Spa + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <stdio.h> +#include <stdbool.h> +#include <limits.h> +#include <getopt.h> +#include <math.h> + +#include <alsa/asoundlib.h> + +#include <spa/utils/defs.h> + +#define DEFAULT_DEVICE "default" + + +struct state { + const char *device; + snd_output_t *output; + snd_pcm_t *hndl; +}; + +#define CHECK(s,msg,...) { \ + int __err; \ + if ((__err = (s)) < 0) { \ + fprintf(stderr, msg ": %s\n", ##__VA_ARGS__, snd_strerror(__err)); \ + return __err; \ + } \ +} + +static const char *get_class(snd_pcm_class_t c) +{ + switch (c) { + case SND_PCM_CLASS_GENERIC: + return "generic"; + case SND_PCM_CLASS_MULTI: + return "multichannel"; + case SND_PCM_CLASS_MODEM: + return "modem"; + case SND_PCM_CLASS_DIGITIZER: + return "digitizer"; + default: + return "unknown"; + } +} + +static const char *get_subclass(snd_pcm_subclass_t c) +{ + switch (c) { + case SND_PCM_SUBCLASS_GENERIC_MIX: + return "generic-mix"; + case SND_PCM_SUBCLASS_MULTI_MIX: + return "multichannel-mix"; + default: + return "unknown"; + } +} + +static void show_help(const char *name, bool error) +{ + fprintf(error ? stderr : stdout, "%s [options]\n" + " -h, --help Show this help\n" + " -D, --device device name (default '%s')\n" + " -C, --capture capture mode (default playback)\n", + name, DEFAULT_DEVICE); +} + +int main(int argc, char *argv[]) +{ + struct state state = { 0, }; + snd_pcm_hw_params_t *hparams; + snd_pcm_info_t *info; + snd_pcm_sync_id_t sync; + snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK; + snd_pcm_chmap_query_t **maps; + int c, i; + static const struct option long_options[] = { + { "help", no_argument, NULL, 'h' }, + { "device", required_argument, NULL, 'D' }, + { "capture", no_argument, NULL, 'C' }, + { NULL, 0, NULL, 0} + }; + state.device = DEFAULT_DEVICE; + + while ((c = getopt_long(argc, argv, "hD:C", long_options, NULL)) != -1) { + switch (c) { + case 'h': + show_help(argv[0], false); + return 0; + case 'D': + state.device = optarg; + break; + case 'C': + stream = SND_PCM_STREAM_CAPTURE; + break; + default: + show_help(argv[0], true); + return -1; + } + } + + CHECK(snd_output_stdio_attach(&state.output, stdout, 0), "attach failed"); + + fprintf(stdout, "opening device: '%s'\n", state.device); + + CHECK(snd_pcm_open(&state.hndl, state.device, stream, 0), + "open %s failed", state.device); + + snd_pcm_info_alloca(&info); + snd_pcm_info(state.hndl, info); + + fprintf(stdout, "info:\n"); + fprintf(stdout, " device: %u\n", snd_pcm_info_get_device(info)); + fprintf(stdout, " subdevice: %u\n", snd_pcm_info_get_subdevice(info)); + fprintf(stdout, " stream: %s\n", snd_pcm_stream_name(snd_pcm_info_get_stream(info))); + fprintf(stdout, " card: %d\n", snd_pcm_info_get_card(info)); + fprintf(stdout, " id: '%s'\n", snd_pcm_info_get_id(info)); + fprintf(stdout, " name: '%s'\n", snd_pcm_info_get_name(info)); + fprintf(stdout, " subdevice name: '%s'\n", snd_pcm_info_get_subdevice_name(info)); + fprintf(stdout, " class: %s\n", get_class(snd_pcm_info_get_class(info))); + fprintf(stdout, " subclass: %s\n", get_subclass(snd_pcm_info_get_subclass(info))); + fprintf(stdout, " subdevice count: %u\n", snd_pcm_info_get_subdevices_count(info)); + fprintf(stdout, " subdevice avail: %u\n", snd_pcm_info_get_subdevices_avail(info)); + sync = snd_pcm_info_get_sync(info); + fprintf(stdout, " sync: %08x:%08x:%08x:%08x\n", + sync.id32[0], sync.id32[1], sync.id32[2],sync.id32[3]); + + /* channel maps */ + if ((maps = snd_pcm_query_chmaps(state.hndl)) != NULL) { + fprintf(stdout, "channels:\n"); + + for (i = 0; maps[i]; i++) { + snd_pcm_chmap_t* map = &maps[i]->map; + char buf[2048]; + + snd_pcm_chmap_print(map, sizeof(buf), buf); + + fprintf(stdout, " %d: %s\n", map->channels, buf); + } + snd_pcm_free_chmaps(maps); + } + + /* hw params */ + snd_pcm_hw_params_alloca(&hparams); + snd_pcm_hw_params_any(state.hndl, hparams); + + snd_pcm_hw_params_dump(hparams, state.output); + + snd_pcm_close(state.hndl); + + return EXIT_SUCCESS; +} diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index eeeb5d1fbba11a062dcb218ba1a064a77a5f2721..dcaed9ad3d8ee32644b0a7929b35eb88ecef4f1e 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -49,7 +49,7 @@ static struct spa_log_topic *log_topic = &SPA_LOG_TOPIC(0, "spa.audioadapter"); #define DEFAULT_ALIGN 16 -#define MAX_PORTS SPA_AUDIO_MAX_CHANNELS +#define MAX_PORTS (SPA_AUDIO_MAX_CHANNELS+1) /** \cond */ @@ -442,6 +442,29 @@ static int configure_format(struct impl *this, uint32_t flags, const struct spa_ 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, @@ -450,12 +473,6 @@ static int configure_format(struct impl *this, uint32_t flags, const struct spa_ return res; } - if ((res = spa_node_port_set_param(this->follower, - this->direction, 0, - SPA_PARAM_Format, flags, - format)) < 0) - return res; - this->have_format = format != NULL; if (format == NULL) { this->n_buffers = 0; @@ -494,7 +511,7 @@ static int reconfigure_mode(struct impl *this, bool passthrough, int res = 0; struct spa_hook l; - spa_log_debug(this->log, "%p: passthrough mode %d", this, passthrough); + spa_log_info(this->log, "%p: passthrough mode %d", this, passthrough); if (this->passthrough != passthrough) { if (passthrough) { @@ -513,7 +530,7 @@ static int reconfigure_mode(struct impl *this, bool passthrough, /* set new target */ this->target = passthrough ? this->follower : this->convert; - if ((res = configure_format(this, 0, format)) < 0) + if ((res = configure_format(this, SPA_NODE_PARAM_FLAG_NEAREST, format)) < 0) return res; if (this->passthrough != passthrough) { @@ -595,7 +612,6 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, enum spa_direction dir; enum spa_param_port_config_mode mode; struct spa_pod *format = NULL; - int monitor = false; if (this->started) { spa_log_error(this->log, "was started"); @@ -606,7 +622,6 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, 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_monitor, SPA_POD_OPT_Bool(&monitor), SPA_PARAM_PORT_CONFIG_format, SPA_POD_OPT_Pod(&format)) < 0) return -EINVAL; @@ -777,7 +792,7 @@ static int negotiate_format(struct impl *this) spa_pod_fixate(format); - res = configure_format(this, 0, format); + res = configure_format(this, SPA_NODE_PARAM_FLAG_NEAREST, format); done: spa_node_send_command(this->follower, @@ -852,6 +867,12 @@ static void convert_node_info(void *data, const struct spa_node_info *info) 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; @@ -1132,8 +1153,21 @@ static int follower_ready(void *data, int status) if (this->target != this->follower) { this->driver = true; - if (this->direction == SPA_DIRECTION_OUTPUT) - status = spa_node_process(this->convert); + 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; + } + } + + } } return spa_node_call_ready(&this->callbacks, status); diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 02c7bc1a99bcc56c8aa0041850d473c5dd5f5b4a..7873efdb0436ef22488cbb6646b5401dd18bc921 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -1,6 +1,6 @@ /* Spa * - * Copyright © 2018 Wim Taymans + * Copyright © 2022 Wim Taymans * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -25,52 +25,153 @@ #include <errno.h> #include <string.h> #include <stdio.h> +#include <limits.h> #include <spa/support/plugin.h> -#include <spa/support/log.h> #include <spa/support/cpu.h> +#include <spa/support/log.h> #include <spa/utils/result.h> #include <spa/utils/list.h> #include <spa/utils/names.h> #include <spa/utils/string.h> #include <spa/node/node.h> -#include <spa/buffer/alloc.h> #include <spa/node/io.h> #include <spa/node/utils.h> +#include <spa/node/keys.h> #include <spa/param/audio/format-utils.h> #include <spa/param/param.h> +#include <spa/param/latency-utils.h> #include <spa/pod/filter.h> -#include <spa/debug/pod.h> #include <spa/debug/types.h> +#include <spa/debug/pod.h> + +#include "volume-ops.h" +#include "fmt-ops.h" +#include "channelmix-ops.h" +#include "resample.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.audioconvert"); -#define DEFAULT_ALIGN 16 +#define DEFAULT_RATE 48000 +#define DEFAULT_CHANNELS 2 + +#define MAX_ALIGN FMT_OPS_MAX_ALIGN +#define MAX_BUFFERS 32 +#define MAX_DATAS SPA_AUDIO_MAX_CHANNELS +#define MAX_PORTS (SPA_AUDIO_MAX_CHANNELS+1) + +#define DEFAULT_MUTE false +#define DEFAULT_VOLUME VOLUME_NORM + +struct volumes { + bool mute; + uint32_t n_volumes; + float volumes[SPA_AUDIO_MAX_CHANNELS]; +}; + +static void init_volumes(struct volumes *vol) +{ + uint32_t i; + vol->mute = DEFAULT_MUTE; + vol->n_volumes = 0; + for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) + vol->volumes[i] = DEFAULT_VOLUME; +} + +struct props { + float volume; + uint32_t n_channels; + uint32_t channel_map[SPA_AUDIO_MAX_CHANNELS]; + struct volumes channel; + struct volumes soft; + struct volumes monitor; + unsigned int have_soft_volume:1; + unsigned int mix_disabled:1; + unsigned int resample_quality; + unsigned int resample_disabled:1; + double rate; +}; -#define MAX_PORTS SPA_AUDIO_MAX_CHANNELS +static void props_reset(struct props *props) +{ + uint32_t i; + props->volume = DEFAULT_VOLUME; + props->n_channels = 0; + for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) + props->channel_map[i] = SPA_AUDIO_CHANNEL_UNKNOWN; + init_volumes(&props->channel); + init_volumes(&props->soft); + init_volumes(&props->monitor); + props->mix_disabled = false; + props->rate = 1.0; + props->resample_quality = RESAMPLE_DEFAULT_QUALITY; + props->resample_disabled = false; +} struct buffer { - struct spa_list link; -#define BUFFER_FLAG_OUT (1 << 0) + uint32_t id; +#define BUFFER_FLAG_QUEUED (1<<0) uint32_t flags; - struct spa_buffer *outbuf; - struct spa_meta_header *h; + struct spa_list link; + struct spa_buffer *buf; + void *datas[MAX_DATAS]; }; -struct link { - struct spa_node *out_node; - uint32_t out_port; - uint32_t out_flags; - struct spa_node *in_node; - uint32_t in_port; - uint32_t in_flags; - struct spa_io_buffers io; - uint32_t min_buffers; +struct port { + uint32_t direction; + uint32_t id; + + struct spa_io_buffers *io; + + uint64_t info_all; + struct spa_port_info info; +#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 N_PORT_PARAMS 6 + struct spa_param_info params[N_PORT_PARAMS]; + char position[16]; + + struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; - struct spa_buffer **buffers; - unsigned int negotiated:1; + + struct spa_audio_info format; + unsigned int have_format:1; + unsigned int is_dsp:1; + unsigned int is_monitor:1; + unsigned int is_control:1; + + uint32_t blocks; + uint32_t stride; + + const struct spa_pod_sequence *ctrl; + uint32_t ctrl_offset; + + struct spa_list queue; +}; + +struct dir { + struct port *ports[MAX_PORTS]; + uint32_t n_ports; + + enum spa_param_port_config_mode mode; + + struct spa_audio_info format; + unsigned int have_format:1; + unsigned int have_profile:1; + struct spa_latency_info latency; + + uint32_t remap[MAX_PORTS]; + + struct convert conv; + unsigned int need_remap:1; + unsigned int is_passthrough:1; + unsigned int control:1; }; struct impl { @@ -80,9 +181,15 @@ struct impl { struct spa_log *log; struct spa_cpu *cpu; + uint32_t cpu_flags; uint32_t max_align; + uint32_t quantum_limit; + enum spa_direction direction; - struct spa_hook_list hooks; + struct props props; + + struct spa_io_position *io_position; + struct spa_io_rate_match *io_rate_match; uint64_t info_all; struct spa_node_info info; @@ -90,52 +197,48 @@ struct impl { #define IDX_PortConfig 1 #define IDX_PropInfo 2 #define IDX_Props 3 - struct spa_param_info params[4]; - uint32_t param_flags[4]; - - int n_links; - struct link links[8]; - int n_nodes; - struct spa_node *nodes[8]; +#define N_NODE_PARAMS 4 + struct spa_param_info params[N_NODE_PARAMS]; - enum spa_param_port_config_mode mode[2]; - bool fmt_removing[2]; - - struct spa_handle *hnd_merger; - struct spa_handle *hnd_convert_in; - struct spa_handle *hnd_channelmix; - struct spa_handle *hnd_resample; - struct spa_handle *hnd_convert_out; - struct spa_handle *hnd_splitter; - - struct spa_node *merger; - struct spa_node *convert_in; - struct spa_node *channelmix; - struct spa_node *resample; - struct spa_node *convert_out; - struct spa_node *splitter; + struct spa_hook_list hooks; - struct spa_node *fmt[2]; - struct spa_hook fmt_listener[2]; - bool have_fmt_listener[2]; + unsigned int monitor:1; + unsigned int monitor_channel_volumes:1; - struct spa_hook listener[2]; + struct dir dir[2]; + struct channelmix mix; + struct resample resample; + struct volume volume; + double rate_scale; + uint32_t in_offset; + uint32_t out_offset; unsigned int started:1; - unsigned int add_listener:1; + unsigned int peaks:1; + unsigned int is_passthrough:1; + unsigned int drained:1; + + uint32_t empty_size; + float *empty; + float *scratch; + float *tmp[2]; + float *tmp_datas[2][MAX_PORTS]; }; -#define IS_MONITOR_PORT(this,dir,port_id) (dir == SPA_DIRECTION_OUTPUT && port_id > 0 && \ - this->mode[SPA_DIRECTION_INPUT] == SPA_PARAM_PORT_CONFIG_MODE_dsp && \ - this->mode[SPA_DIRECTION_OUTPUT] != SPA_PARAM_PORT_CONFIG_MODE_dsp) +#define CHECK_PORT(this,d,p) ((p) < this->dir[d].n_ports) +#define GET_PORT(this,d,p) (this->dir[d].ports[p]) +#define GET_IN_PORT(this,p) GET_PORT(this,SPA_DIRECTION_INPUT,p) +#define GET_OUT_PORT(this,p) GET_PORT(this,SPA_DIRECTION_OUTPUT,p) + +#define PORT_IS_DSP(this,d,p) (GET_PORT(this,d,p)->is_dsp) +#define PORT_IS_CONTROL(this,d,p) (GET_PORT(this,d,p)->is_control) + +static void set_volume(struct impl *this); static void emit_node_info(struct impl *this, bool full) { - uint32_t i; uint64_t old = full ? this->info.change_mask : 0; - - if (this->add_listener) - return; + uint32_t i; if (full) this->info.change_mask = this->info_all; @@ -153,344 +256,103 @@ static void emit_node_info(struct impl *this, bool full) } } -static int make_link(struct impl *this, - struct spa_node *out_node, uint32_t out_port, - struct spa_node *in_node, uint32_t in_port, uint32_t min_buffers) -{ - struct link *l = &this->links[this->n_links++]; - - l->out_node = out_node; - l->out_port = out_port; - l->out_flags = 0; - l->in_node = in_node; - l->in_port = in_port; - l->in_flags = 0; - l->negotiated = false; - l->io = SPA_IO_BUFFERS_INIT; - l->n_buffers = 0; - l->min_buffers = min_buffers; - - spa_node_port_set_io(out_node, - SPA_DIRECTION_OUTPUT, out_port, - SPA_IO_Buffers, - &l->io, sizeof(l->io)); - spa_node_port_set_io(in_node, - SPA_DIRECTION_INPUT, in_port, - SPA_IO_Buffers, - &l->io, sizeof(l->io)); - return 0; -} - -static void clean_link(struct impl *this, struct link *link) -{ - spa_node_port_set_param(link->in_node, - SPA_DIRECTION_INPUT, link->in_port, - SPA_PARAM_Format, 0, NULL); - spa_node_port_set_param(link->out_node, - SPA_DIRECTION_OUTPUT, link->out_port, - SPA_PARAM_Format, 0, NULL); - if (link->buffers) - free(link->buffers); - link->buffers = NULL; -} - -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) -{ - 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:"); - - 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) - 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_link_format(struct impl *this, struct link *link) -{ - struct spa_pod_builder b = { 0 }; - uint8_t buffer[4096]; - uint32_t state; - struct spa_pod *format, *filter; - int res; - - if (link->negotiated) - return 0; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - state = 0; - filter = NULL; - if ((res = spa_node_port_enum_params_sync(link->out_node, - SPA_DIRECTION_OUTPUT, link->out_port, - SPA_PARAM_EnumFormat, &state, - filter, &format, &b)) != 1) { - debug_params(this, link->out_node, SPA_DIRECTION_OUTPUT, link->out_port, - SPA_PARAM_EnumFormat, filter); - return -ENOTSUP; - } - filter = format; - state = 0; - if ((res = spa_node_port_enum_params_sync(link->in_node, - SPA_DIRECTION_INPUT, link->in_port, - SPA_PARAM_EnumFormat, &state, - filter, &format, &b)) != 1) { - debug_params(this, link->in_node, SPA_DIRECTION_INPUT, link->in_port, - SPA_PARAM_EnumFormat, filter); - return -ENOTSUP; - } - filter = format; - - spa_pod_fixate(filter); - - if ((res = spa_node_port_set_param(link->out_node, - SPA_DIRECTION_OUTPUT, link->out_port, - SPA_PARAM_Format, 0, - filter)) < 0) - return res; - - if ((res = spa_node_port_set_param(link->in_node, - SPA_DIRECTION_INPUT, link->in_port, - SPA_PARAM_Format, 0, - filter)) < 0) - return res; - - link->negotiated = true; - - return 0; -} - -static int setup_convert(struct impl *this) +static void emit_port_info(struct impl *this, struct port *port, bool full) { - int i, j, res; - - spa_log_debug(this->log, "setup convert n_links:%d", this->n_links); + uint64_t old = full ? port->info.change_mask : 0; + uint32_t i; - if (this->n_links > 0) - return 0; + if (full) + port->info.change_mask = port->info_all; + if (port->info.change_mask) { + struct spa_dict_item items[3]; + uint32_t n_items = 0; + + if (PORT_IS_DSP(this, port->direction, port->id)) { + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit float mono audio"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_AUDIO_CHANNEL, port->position); + if (port->is_monitor) + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_MONITOR, "true"); + } else if (PORT_IS_CONTROL(this, port->direction, port->id)) { + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "control"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"); + } + port->info.props = &SPA_DICT_INIT(items, n_items); - this->n_nodes = 0; - /* unpack */ - this->nodes[this->n_nodes++] = this->fmt[SPA_DIRECTION_INPUT]; - /* down mix */ - this->nodes[this->n_nodes++] = this->channelmix; - /* resample */ - this->nodes[this->n_nodes++] = this->resample; - /* pack */ - this->nodes[this->n_nodes++] = this->fmt[SPA_DIRECTION_OUTPUT]; - - make_link(this, this->nodes[0], 0, this->nodes[1], 0, 2); - make_link(this, this->nodes[1], 0, this->nodes[2], 0, 2); - make_link(this, this->nodes[2], 0, this->nodes[3], 0, 1); - - for (i = 0, j = this->n_links - 1; j >= i; i++, j--) { - spa_log_debug(this->log, "negotiate %d", i); - if ((res = negotiate_link_format(this, &this->links[i])) < 0) - return res; - spa_log_debug(this->log, "negotiate %d", j); - if ((res = negotiate_link_format(this, &this->links[j])) < 0) - return res; + if (port->info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { + for (i = 0; i < SPA_N_ELEMENTS(port->params); i++) { + if (port->params[i].user > 0) { + port->params[i].flags ^= SPA_PARAM_INFO_SERIAL; + port->params[i].user = 0; + } + } + } + spa_node_emit_port_info(&this->hooks, port->direction, port->id, &port->info); + port->info.change_mask = old; } - return 0; } -static int negotiate_link_buffers(struct impl *this, struct link *link) +static int init_port(struct impl *this, enum spa_direction direction, uint32_t port_id, + uint32_t position, bool is_dsp, bool is_monitor, bool is_control) { - uint8_t buffer[4096]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - uint32_t state; - struct spa_pod *param = NULL, *filter; - int res; - bool in_alloc, out_alloc; - uint32_t i, size, buffers, blocks, align, flags; - uint32_t *aligns; - struct spa_data *datas; + struct port *port = GET_PORT(this, direction, port_id); + const char *name; - if (link->n_buffers > 0) - return 0; + spa_assert(port_id < MAX_PORTS); - state = 0; - filter = NULL; - if ((res = spa_node_port_enum_params_sync(link->in_node, - SPA_DIRECTION_INPUT, link->in_port, - SPA_PARAM_Buffers, &state, - filter, ¶m, &b)) != 1) { - debug_params(this, link->in_node, SPA_DIRECTION_INPUT, link->in_port, - SPA_PARAM_Buffers, filter); - return -ENOTSUP; - } - state = 0; - filter = param; - if ((res = spa_node_port_enum_params_sync(link->out_node, - SPA_DIRECTION_OUTPUT, link->out_port, - SPA_PARAM_Buffers, &state, - filter, ¶m, &b)) != 1) { - debug_params(this, link->out_node, SPA_DIRECTION_OUTPUT, link->out_port, - SPA_PARAM_Buffers, filter); - return -ENOTSUP; + if (port == NULL) { + port = calloc(1, sizeof(struct port)); + if (port == NULL) + return -errno; + this->dir[direction].ports[port_id] = port; } - - spa_pod_fixate(param); - - in_alloc = SPA_FLAG_IS_SET(link->in_flags, - SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); - out_alloc = SPA_FLAG_IS_SET(link->out_flags, - SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); - - flags = 0; - if (out_alloc || in_alloc) { - flags |= SPA_BUFFER_ALLOC_FLAG_NO_DATA; - if (out_alloc) - in_alloc = false; + port->direction = direction; + port->id = port_id; + + name = spa_debug_type_find_short_name(spa_type_audio_channel, position); + snprintf(port->position, sizeof(port->position), "%s", name ? name : "UNK"); + + port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | + SPA_PORT_CHANGE_MASK_PROPS | + SPA_PORT_CHANGE_MASK_PARAMS; + port->info = SPA_PORT_INFO_INIT(); + port->info.flags = SPA_PORT_FLAG_NO_REF | + SPA_PORT_FLAG_DYNAMIC_DATA; + port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); + port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); + port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); + port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + port->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); + port->info.params = port->params; + port->info.n_params = N_PORT_PARAMS; + + port->n_buffers = 0; + port->have_format = false; + port->is_monitor = is_monitor; + port->is_dsp = is_dsp; + if (port->is_dsp) { + port->format.media_type = SPA_MEDIA_TYPE_audio; + port->format.media_subtype = SPA_MEDIA_SUBTYPE_dsp; + port->format.info.dsp.format = SPA_AUDIO_FORMAT_DSP_F32; + port->blocks = 1; + port->stride = 4; } - - align = DEFAULT_ALIGN; - - if (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 -EINVAL; - - spa_log_debug(this->log, "%p: buffers %d, blocks %d, size %d, align %d %d:%d", - this, buffers, blocks, size, align, out_alloc, in_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_DYNAMIC; - datas[i].maxsize = size; - aligns[i] = align; + port->is_control = is_control; + if (port->is_control) { + port->format.media_type = SPA_MEDIA_TYPE_application; + port->format.media_subtype = SPA_MEDIA_SUBTYPE_control; + port->blocks = 1; + port->stride = 1; } + spa_list_init(&port->queue); - buffers = SPA_MAX(link->min_buffers, buffers); - - if (link->buffers) - free(link->buffers); - link->buffers = spa_buffer_alloc_array(buffers, flags, 0, NULL, blocks, datas, aligns); - if (link->buffers == NULL) - return -errno; - - link->n_buffers = buffers; - - if ((res = spa_node_port_use_buffers(link->out_node, - SPA_DIRECTION_OUTPUT, link->out_port, - out_alloc ? SPA_NODE_BUFFERS_FLAG_ALLOC : 0, - link->buffers, link->n_buffers)) < 0) - return res; - - if ((res = spa_node_port_use_buffers(link->in_node, - SPA_DIRECTION_INPUT, link->in_port, - in_alloc ? SPA_NODE_BUFFERS_FLAG_ALLOC : 0, - link->buffers, link->n_buffers)) < 0) - return res; - - return 0; -} - -static void flush_convert(struct impl *this) -{ - int i; - spa_log_debug(this->log, "%p: %d", this, this->n_links); - for (i = 0; i < this->n_links; i++) - this->links[i].io.status = SPA_STATUS_OK; -} - -static void clean_convert(struct impl *this) -{ - int i; - - spa_log_debug(this->log, "%p: %d", this, this->n_links); - - for (i = 0; i < this->n_links; i++) - clean_link(this, &this->links[i]); - this->n_links = 0; -} - -static int setup_buffers(struct impl *this, enum spa_direction direction) -{ - int i, res; - - spa_log_debug(this->log, "%p: %d %d", this, direction, this->n_links); - - if (direction == SPA_DIRECTION_INPUT) { - for (i = 0; i < this->n_links; i++) { - if ((res = negotiate_link_buffers(this, &this->links[i])) < 0) - spa_log_error(this->log, "%p: buffers %d failed %s", - this, i, spa_strerror(res)); - } - } else { - for (i = this->n_links-1; i >= 0 ; i--) { - if ((res = negotiate_link_buffers(this, &this->links[i])) < 0) - spa_log_error(this->log, "%p: buffers %d failed %s", - this, i, spa_strerror(res)); - } - } + spa_log_info(this->log, "%p: add port %d:%d position:%s %d %d %d", + this, direction, port_id, port->position, is_dsp, is_monitor, is_control); + emit_port_info(this, port, true); return 0; } -static int enum_params(struct impl *this, - uint32_t id, - struct spa_result_node_params *result, - const struct spa_pod *filter, - struct spa_pod_builder *builder) -{ - int res; - result->param = NULL; - result->next = result->index; - if (result->next < 0x1000) { - if (this->fmt[SPA_DIRECTION_INPUT] == this->merger && - (res = spa_node_enum_params_sync(this->merger, - id, &result->next, filter, &result->param, builder)) == 1) { - return res; - } - result->next = 0x1000; - } - if (result->next < 0x2000) { - result->next &= 0xfff; - if ((res = spa_node_enum_params_sync(this->channelmix, - id, &result->next, filter, &result->param, builder)) == 1) { - result->next |= 0x1000; - return res; - } - result->next = 0x2000; - } - if (result->next >= 0x2000) { - result->next &= 0xfff; - if ((res = spa_node_enum_params_sync(this->resample, - id, &result->next, filter, &result->param, builder)) == 1) { - result->next |= 0x2000; - return res; - } - } - 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) @@ -501,7 +363,6 @@ static int impl_node_enum_params(void *object, int seq, uint8_t buffer[4096]; struct spa_result_node_params result; uint32_t count = 0; - int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); @@ -509,7 +370,6 @@ static int impl_node_enum_params(void *object, int seq, result.id = id; result.next = start; next: - res = 0; result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); @@ -552,13 +412,13 @@ static int impl_node_enum_params(void *object, int seq, param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, id, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_INPUT), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(this->mode[SPA_DIRECTION_INPUT])); + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(this->dir[SPA_DIRECTION_INPUT].mode)); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, id, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_OUTPUT), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(this->mode[SPA_DIRECTION_OUTPUT])); + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(this->dir[SPA_DIRECTION_OUTPUT].mode)); break; default: return 0; @@ -566,23 +426,317 @@ static int impl_node_enum_params(void *object, int seq, break; case SPA_PARAM_PropInfo: - if ((res = enum_params(this, id, &result, filter, &b)) != 1) - return res; + { + struct props *p = &this->props; + struct spa_pod_frame f[2]; + uint32_t i; + + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_volume), + SPA_PROP_INFO_description, SPA_POD_String("Volume"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0)); + break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_mute), + SPA_PROP_INFO_description, SPA_POD_String("Mute"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->channel.mute)); + break; + case 2: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_channelVolumes), + SPA_PROP_INFO_description, SPA_POD_String("Channel Volumes"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0), + SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); + break; + case 3: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_channelMap), + SPA_PROP_INFO_description, SPA_POD_String("Channel Map"), + SPA_PROP_INFO_type, SPA_POD_Id(SPA_AUDIO_CHANNEL_UNKNOWN), + SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); + break; + case 4: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_monitorMute), + SPA_PROP_INFO_description, SPA_POD_String("Monitor Mute"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->monitor.mute)); + break; + case 5: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_monitorVolumes), + SPA_PROP_INFO_description, SPA_POD_String("Monitor Volumes"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0), + SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); + break; + case 6: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_softMute), + SPA_PROP_INFO_description, SPA_POD_String("Soft Mute"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->soft.mute)); + break; + case 7: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_softVolumes), + SPA_PROP_INFO_description, SPA_POD_String("Soft Volumes"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0), + SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); + break; + case 8: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("monitor.channel-volumes"), + SPA_PROP_INFO_description, SPA_POD_String("Monitor channel volume"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( + this->monitor_channel_volumes), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 9: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.disable"), + SPA_PROP_INFO_description, SPA_POD_String("Disable Channel mixing"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->mix_disabled), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 10: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.normalize"), + SPA_PROP_INFO_description, SPA_POD_String("Normalize Volumes"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( + SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_NORMALIZE)), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 11: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.mix-lfe"), + SPA_PROP_INFO_description, SPA_POD_String("Mix LFE into channels"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( + SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_MIX_LFE)), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 12: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.upmix"), + SPA_PROP_INFO_description, SPA_POD_String("Enable upmixing"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( + SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_UPMIX)), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 13: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.lfe-cutoff"), + SPA_PROP_INFO_description, SPA_POD_String("LFE cutoff frequency"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( + this->mix.lfe_cutoff, 0.0, 1000.0), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 14: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.fc-cutoff"), + SPA_PROP_INFO_description, SPA_POD_String("FC cutoff frequency (Hz)"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( + this->mix.fc_cutoff, 0.0, 48000.0), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 15: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.rear-delay"), + SPA_PROP_INFO_description, SPA_POD_String("Rear channels delay (ms)"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( + this->mix.rear_delay, 0.0, 1000.0), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 16: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.stereo-widen"), + SPA_PROP_INFO_description, SPA_POD_String("Stereo widen"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( + this->mix.widen, 0.0, 1.0), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 17: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.hilbert-taps"), + SPA_PROP_INFO_description, SPA_POD_String("Taps for phase shift of rear"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int( + this->mix.hilbert_taps, 0, MAX_TAPS), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 18: + spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_PropInfo, id); + spa_pod_builder_add(&b, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.upmix-method"), + SPA_PROP_INFO_description, SPA_POD_String("Upmix method to use"), + SPA_PROP_INFO_type, SPA_POD_String( + channelmix_upmix_info[this->mix.upmix].label), + SPA_PROP_INFO_params, SPA_POD_Bool(true), + 0); + + spa_pod_builder_prop(&b, SPA_PROP_INFO_labels, 0); + spa_pod_builder_push_struct(&b, &f[1]); + for (i = 0; i < SPA_N_ELEMENTS(channelmix_upmix_info); i++) { + spa_pod_builder_string(&b, channelmix_upmix_info[i].label); + spa_pod_builder_string(&b, channelmix_upmix_info[i].description); + } + spa_pod_builder_pop(&b, &f[1]); + param = spa_pod_builder_pop(&b, &f[0]); + break; + case 19: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_rate), + SPA_PROP_INFO_description, SPA_POD_String("Rate scaler"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Double(p->rate, 0.0, 10.0)); + break; + case 20: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_quality), + SPA_PROP_INFO_name, SPA_POD_String("resample.quality"), + SPA_PROP_INFO_description, SPA_POD_String("Resample Quality"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->resample_quality, 0, 14), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 21: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("resample.disable"), + SPA_PROP_INFO_description, SPA_POD_String("Disable Resampling"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->resample_disabled), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 22: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("dither.noise"), + SPA_PROP_INFO_description, SPA_POD_String("Add noise bits"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(this->dir[1].conv.noise_bits, 0, 16), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 23: + spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_PropInfo, id); + spa_pod_builder_add(&b, + SPA_PROP_INFO_name, SPA_POD_String("dither.method"), + SPA_PROP_INFO_description, SPA_POD_String("The dithering method"), + SPA_PROP_INFO_type, SPA_POD_String( + dither_method_info[this->dir[1].conv.method].label), + SPA_PROP_INFO_params, SPA_POD_Bool(true), + 0); + spa_pod_builder_prop(&b, SPA_PROP_INFO_labels, 0); + spa_pod_builder_push_struct(&b, &f[1]); + for (i = 0; i < SPA_N_ELEMENTS(dither_method_info); i++) { + spa_pod_builder_string(&b, dither_method_info[i].label); + spa_pod_builder_string(&b, dither_method_info[i].description); + } + spa_pod_builder_pop(&b, &f[1]); + param = spa_pod_builder_pop(&b, &f[0]); + break; + default: + return 0; + } break; + } case SPA_PARAM_Props: - if ((res = enum_params(this, id, &result, filter, &b)) != 1) - return res; - break; + { + struct props *p = &this->props; + struct spa_pod_frame f[2]; + switch (result.index) { + case 0: + spa_pod_builder_push_object(&b, &f[0], + SPA_TYPE_OBJECT_Props, id); + spa_pod_builder_add(&b, + SPA_PROP_volume, SPA_POD_Float(p->volume), + SPA_PROP_mute, SPA_POD_Bool(p->channel.mute), + SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float), + SPA_TYPE_Float, + p->channel.n_volumes, + p->channel.volumes), + SPA_PROP_channelMap, SPA_POD_Array(sizeof(uint32_t), + SPA_TYPE_Id, + p->n_channels, + p->channel_map), + SPA_PROP_softMute, SPA_POD_Bool(p->soft.mute), + SPA_PROP_softVolumes, SPA_POD_Array(sizeof(float), + SPA_TYPE_Float, + p->soft.n_volumes, + p->soft.volumes), + SPA_PROP_monitorMute, SPA_POD_Bool(p->monitor.mute), + SPA_PROP_monitorVolumes, SPA_POD_Array(sizeof(float), + SPA_TYPE_Float, + p->monitor.n_volumes, + p->monitor.volumes), + 0); + spa_pod_builder_prop(&b, SPA_PROP_params, 0); + spa_pod_builder_push_struct(&b, &f[1]); + spa_pod_builder_string(&b, "monitor.channel-volumes"); + spa_pod_builder_bool(&b, this->monitor_channel_volumes); + spa_pod_builder_string(&b, "channelmix.disable"); + spa_pod_builder_bool(&b, this->props.mix_disabled); + spa_pod_builder_string(&b, "channelmix.normalize"); + spa_pod_builder_bool(&b, SPA_FLAG_IS_SET(this->mix.options, + CHANNELMIX_OPTION_NORMALIZE)); + spa_pod_builder_string(&b, "channelmix.mix-lfe"); + spa_pod_builder_bool(&b, SPA_FLAG_IS_SET(this->mix.options, + CHANNELMIX_OPTION_MIX_LFE)); + spa_pod_builder_string(&b, "channelmix.upmix"); + spa_pod_builder_bool(&b, SPA_FLAG_IS_SET(this->mix.options, + CHANNELMIX_OPTION_UPMIX)); + spa_pod_builder_string(&b, "channelmix.lfe-cutoff"); + spa_pod_builder_float(&b, this->mix.lfe_cutoff); + spa_pod_builder_string(&b, "channelmix.fc-cutoff"); + spa_pod_builder_float(&b, this->mix.fc_cutoff); + spa_pod_builder_string(&b, "channelmix.rear-delay"); + spa_pod_builder_float(&b, this->mix.rear_delay); + spa_pod_builder_string(&b, "channelmix.stereo-widen"); + spa_pod_builder_float(&b, this->mix.widen); + spa_pod_builder_string(&b, "channelmix.hilbert-taps"); + spa_pod_builder_int(&b, this->mix.hilbert_taps); + spa_pod_builder_string(&b, "channelmix.upmix-method"); + spa_pod_builder_string(&b, channelmix_upmix_info[this->mix.upmix].label); + spa_pod_builder_string(&b, "resample.quality"); + spa_pod_builder_int(&b, p->resample_quality); + spa_pod_builder_string(&b, "resample.disable"); + spa_pod_builder_bool(&b, p->resample_disabled); + spa_pod_builder_string(&b, "dither.noise"); + spa_pod_builder_int(&b, this->dir[1].conv.noise_bits); + spa_pod_builder_string(&b, "dither.method"); + spa_pod_builder_string(&b, dither_method_info[this->dir[1].conv.method].label); + spa_pod_builder_pop(&b, &f[1]); + param = spa_pod_builder_pop(&b, &f[0]); + break; + default: + return 0; + } + break; + } default: - return -ENOENT; + return 0; } - if (res == 0) { - if (spa_pod_filter(&b, &result.param, param, filter) < 0) - goto next; - } + if (spa_pod_filter(&b, &result.param, param, filter) < 0) + goto next; + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) @@ -594,7 +748,6 @@ static int impl_node_enum_params(void *object, int seq, static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; - int res; spa_return_val_if_fail(this != NULL, -EINVAL); @@ -602,230 +755,296 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) switch (id) { case SPA_IO_Position: - res = spa_node_set_io(this->resample, id, data, size); - res = spa_node_set_io(this->channelmix, id, data, size); - res = spa_node_set_io(this->fmt[0], id, data, size); - res = spa_node_set_io(this->fmt[1], id, data, size); + this->io_position = data; break; default: - res = -ENOENT; - break; + return -ENOENT; } - return res; + return 0; } -static void fmt_input_port_info(void *data, - enum spa_direction direction, uint32_t port, - const struct spa_port_info *info) +static int audioconvert_set_param(struct impl *this, const char *k, const char *s) { - struct impl *this = data; - bool is_monitor = IS_MONITOR_PORT(this, direction, port); - - if (this->fmt_removing[direction]) - info = NULL; - if (is_monitor && this->fmt_removing[SPA_DIRECTION_INPUT]) - info = NULL; - - spa_log_debug(this->log, "%p: %d.%d info", this, direction, port); - - if (direction == SPA_DIRECTION_INPUT || is_monitor) - spa_node_emit_port_info(&this->hooks, direction, port, info); + if (spa_streq(k, "monitor.channel-volumes")) + this->monitor_channel_volumes = spa_atob(s); + else if (spa_streq(k, "channelmix.disable")) + this->props.mix_disabled = spa_atob(s); + else if (spa_streq(k, "channelmix.normalize")) + SPA_FLAG_UPDATE(this->mix.options, CHANNELMIX_OPTION_NORMALIZE, spa_atob(s)); + else if (spa_streq(k, "channelmix.mix-lfe")) + SPA_FLAG_UPDATE(this->mix.options, CHANNELMIX_OPTION_MIX_LFE, spa_atob(s)); + else if (spa_streq(k, "channelmix.upmix")) + SPA_FLAG_UPDATE(this->mix.options, CHANNELMIX_OPTION_UPMIX, spa_atob(s)); + else if (spa_streq(k, "channelmix.lfe-cutoff")) + spa_atof(s, &this->mix.lfe_cutoff); + else if (spa_streq(k, "channelmix.fc-cutoff")) + spa_atof(s, &this->mix.fc_cutoff); + else if (spa_streq(k, "channelmix.rear-delay")) + spa_atof(s, &this->mix.rear_delay); + else if (spa_streq(k, "channelmix.stereo-widen")) + spa_atof(s, &this->mix.widen); + else if (spa_streq(k, "channelmix.hilbert-taps")) + spa_atou32(s, &this->mix.hilbert_taps, 0); + else if (spa_streq(k, "channelmix.upmix-method")) + this->mix.upmix = channelmix_upmix_from_label(s); + else if (spa_streq(k, "resample.quality")) + this->props.resample_quality = atoi(s); + else if (spa_streq(k, "resample.disable")) + this->props.resample_disabled = spa_atob(s); + else if (spa_streq(k, "dither.noise")) + spa_atou32(s, &this->dir[1].conv.noise_bits, 0); + else if (spa_streq(k, "dither.method")) + this->dir[1].conv.method = dither_method_from_label(s); + else + return 0; + return 1; } -static const struct spa_node_events fmt_input_events = { - SPA_VERSION_NODE_EVENTS, - .port_info = fmt_input_port_info, -}; - -static void fmt_output_port_info(void *data, - enum spa_direction direction, uint32_t port, - const struct spa_port_info *info) +static int parse_prop_params(struct impl *this, struct spa_pod *params) { - struct impl *this = data; + struct spa_pod_parser prs; + struct spa_pod_frame f; + int changed = 0; - if (this->fmt_removing[direction]) - info = NULL; + spa_pod_parser_pod(&prs, params); + if (spa_pod_parser_push_struct(&prs, &f) < 0) + return 0; - spa_log_debug(this->log, "%p: %d.%d info", this, direction, port); + while (true) { + const char *name; + struct spa_pod *pod; + char value[512]; - if (direction == SPA_DIRECTION_OUTPUT) - spa_node_emit_port_info(&this->hooks, direction, port, info); -} + if (spa_pod_parser_get_string(&prs, &name) < 0) + break; -static const struct spa_node_events fmt_output_events = { - SPA_VERSION_NODE_EVENTS, - .port_info = fmt_output_port_info, -}; + if (spa_pod_parser_get_pod(&prs, &pod) < 0) + break; -static void on_channelmix_info(void *data, const struct spa_node_info *info) -{ - struct impl *this = data; - uint32_t i; + if (spa_pod_is_string(pod)) { + spa_pod_copy_string(pod, sizeof(value), value); + } else if (spa_pod_is_float(pod)) { + snprintf(value, sizeof(value), "%f", + SPA_POD_VALUE(struct spa_pod_float, pod)); + } else if (spa_pod_is_int(pod)) { + snprintf(value, sizeof(value), "%d", + SPA_POD_VALUE(struct spa_pod_int, pod)); + } else if (spa_pod_is_bool(pod)) { + snprintf(value, sizeof(value), "%s", + SPA_POD_VALUE(struct spa_pod_bool, pod) ? + "true" : "false"); + } else + continue; - if ((info->change_mask & SPA_NODE_CHANGE_MASK_PARAMS) == 0) - return; - - for (i = 0; i < info->n_params; i++) { - uint32_t idx; + 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; +} - switch (info->params[i].id) { - case SPA_PARAM_PropInfo: - idx = IDX_PropInfo; +static int apply_props(struct impl *this, const struct spa_pod *param) +{ + struct spa_pod_prop *prop; + struct spa_pod_object *obj = (struct spa_pod_object *) param; + struct props *p = &this->props; + bool have_channel_volume = false; + bool have_soft_volume = false; + int changed = 0; + uint32_t n; + + SPA_POD_OBJECT_FOREACH(obj, prop) { + switch (prop->key) { + case SPA_PROP_volume: + if (spa_pod_get_float(&prop->value, &p->volume) == 0) + changed++; break; - case SPA_PARAM_Props: - idx = IDX_Props; + case SPA_PROP_mute: + if (spa_pod_get_bool(&prop->value, &p->channel.mute) == 0) { + have_channel_volume = true; + changed++; + } + break; + case SPA_PROP_channelVolumes: + if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, + p->channel.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) { + have_channel_volume = true; + p->channel.n_volumes = n; + changed++; + } + break; + case SPA_PROP_channelMap: + if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Id, + p->channel_map, SPA_AUDIO_MAX_CHANNELS)) > 0) { + p->n_channels = n; + changed++; + } + break; + case SPA_PROP_softMute: + if (spa_pod_get_bool(&prop->value, &p->soft.mute) == 0) { + have_soft_volume = true; + changed++; + } + break; + case SPA_PROP_softVolumes: + if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, + p->soft.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) { + have_soft_volume = true; + p->soft.n_volumes = n; + changed++; + } + break; + case SPA_PROP_monitorMute: + if (spa_pod_get_bool(&prop->value, &p->monitor.mute) == 0) + changed++; + break; + case SPA_PROP_monitorVolumes: + if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, + p->monitor.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) { + p->monitor.n_volumes = n; + changed++; + } + break; + case SPA_PROP_rate: + spa_pod_get_double(&prop->value, &p->rate); + break; + case SPA_PROP_params: + changed += parse_prop_params(this, &prop->value); break; default: - continue; + break; } - if (!this->add_listener && - this->param_flags[idx] == info->params[i].flags) - continue; - - this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; - this->param_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 (changed) { + if (have_soft_volume) + p->have_soft_volume = true; + else if (have_channel_volume) + p->have_soft_volume = false; - if (!this->add_listener) - this->params[idx].user++; + set_volume(this); } - emit_node_info(this, false); + return changed; } -static const struct spa_node_events channelmix_events = { - SPA_VERSION_NODE_EVENTS, - .info = on_channelmix_info, -}; - -static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode mode, - enum spa_direction direction, bool monitor, struct spa_audio_info *info) +static int apply_midi(struct impl *this, const struct spa_pod *value) { - int res = 0; - struct spa_node *old, *new; - bool do_signal; + const uint8_t *val = SPA_POD_BODY(value); + uint32_t size = SPA_POD_BODY_SIZE(value); + struct props *p = &this->props; - spa_log_debug(this->log, "%p: mode %d", this, mode); - - /* old node on input/output */ - old = this->fmt[direction]; - - /* decide on new node based on mode and direction */ - switch (mode) { - case SPA_PARAM_PORT_CONFIG_MODE_convert: - new = direction == SPA_DIRECTION_INPUT ? this->convert_in : this->convert_out; - break; + if (size < 3) + return -EINVAL; - case SPA_PARAM_PORT_CONFIG_MODE_dsp: - new = direction == SPA_DIRECTION_INPUT ? this->merger : this->splitter; - break; - case SPA_PARAM_PORT_CONFIG_MODE_none: - new = NULL; - break; - default: - return -EIO; - } + if ((val[0] & 0xf0) != 0xb0 || val[1] != 7) + return 0; - clean_convert(this); + p->volume = val[2] / 127.0f; + set_volume(this); + return 1; +} - this->fmt[direction] = new; +static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode mode, + enum spa_direction direction, bool monitor, bool control, struct spa_audio_info *info) +{ + struct dir *dir; + uint32_t i; - /* signal if we change nodes or when DSP config changes */ - do_signal = this->fmt[direction] != old || - mode == SPA_PARAM_PORT_CONFIG_MODE_dsp; + dir = &this->dir[direction]; - if (do_signal && old != NULL) { - /* change, remove old ports. We trigger a new port_info event - * on the old node with info set to NULL to mark delete */ - if (this->have_fmt_listener[direction]) { - spa_hook_remove(&this->fmt_listener[direction]); + if (dir->have_profile && this->monitor == monitor && dir->mode == mode && + dir->control == control && + (info == NULL || memcmp(&dir->format, info, sizeof(*info)) == 0)) + return 0; - this->fmt_removing[direction] = true; - spa_node_add_listener(old, - &this->fmt_listener[direction], - direction == SPA_DIRECTION_INPUT ? - &fmt_input_events : &fmt_output_events, - this); - this->fmt_removing[direction] = false; + spa_log_info(this->log, "%p: port config direction:%d monitor:%d control:%d mode:%d %d", this, + direction, monitor, control, mode, dir->n_ports); - spa_hook_remove(&this->fmt_listener[direction]); - this->have_fmt_listener[direction] = false; - } + for (i = 0; i < dir->n_ports; i++) { + spa_node_emit_port_info(&this->hooks, direction, i, NULL); + if (this->monitor && direction == SPA_DIRECTION_INPUT) + spa_node_emit_port_info(&this->hooks, SPA_DIRECTION_OUTPUT, i+1, NULL); } - this->mode[direction] = mode; - - if (new != NULL) { - struct spa_pod_builder b = { 0 }; - uint8_t buffer[4096]; - struct spa_pod *param = NULL; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); + this->monitor = monitor; + dir->control = control; + dir->have_profile = true; + dir->mode = mode; + switch (mode) { + case SPA_PARAM_PORT_CONFIG_MODE_dsp: + { if (info) { - spa_log_debug(this->log, "%p: port config %d", this, info->info.raw.channels); - param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info->info.raw); - } - if (mode == SPA_PARAM_PORT_CONFIG_MODE_dsp) { - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(direction), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), - SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(monitor), - SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); - res = spa_node_set_param(this->fmt[direction], SPA_PARAM_PortConfig, 0, param); + dir->n_ports = info->info.raw.channels; + dir->format = *info; + dir->format.info.raw.format = SPA_AUDIO_FORMAT_DSP_F32; + dir->format.info.raw.rate = 0; + dir->have_format = true; } else { - res = spa_node_port_set_param(this->fmt[direction], direction, 0, - SPA_PARAM_Format, 0, param); + dir->n_ports = 0; } - if (res < 0) - return res; - 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++; - } - - /* notify ports of new node */ - if (do_signal && new != NULL) { - if (this->have_fmt_listener[direction]) - spa_hook_remove(&this->fmt_listener[direction]); + if (this->monitor && direction == SPA_DIRECTION_INPUT) + this->dir[SPA_DIRECTION_OUTPUT].n_ports = dir->n_ports + 1; - spa_node_add_listener(this->fmt[direction], - &this->fmt_listener[direction], - direction == SPA_DIRECTION_INPUT ? - &fmt_input_events : &fmt_output_events, - this); - this->have_fmt_listener[direction] = true; + for (i = 0; i < dir->n_ports; i++) { + init_port(this, direction, i, info->info.raw.position[i], true, false, false); + if (this->monitor && direction == SPA_DIRECTION_INPUT) + init_port(this, SPA_DIRECTION_OUTPUT, i+1, + info->info.raw.position[i], true, true, false); + } + break; + } + case SPA_PARAM_PORT_CONFIG_MODE_convert: + { + dir->n_ports = 1; + dir->have_format = false; + init_port(this, direction, 0, 0, false, false, false); + break; + } + default: + return -ENOTSUP; + } + if (direction == SPA_DIRECTION_INPUT && dir->control) { + i = dir->n_ports++; + init_port(this, direction, i, 0, false, false, true); } - emit_node_info(this, false); + 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++; + this->params[IDX_PortConfig].user++; return 0; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { - int res = 0; struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); + if (param == NULL) + return 0; + switch (id) { case SPA_PARAM_PortConfig: { - enum spa_direction dir; - enum spa_param_port_config_mode mode; - struct spa_pod *format = NULL; struct spa_audio_info info = { 0, }, *infop = NULL; - int monitor = false; + struct spa_pod *format = NULL; + enum spa_direction direction; + enum spa_param_port_config_mode mode; + bool monitor = false, control = false; + int res; if (spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamPortConfig, NULL, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(&dir), + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(&direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(&mode), SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_OPT_Bool(&monitor), + SPA_PARAM_PORT_CONFIG_control, SPA_POD_OPT_Bool(&control), SPA_PARAM_PORT_CONFIG_format, SPA_POD_OPT_Pod(&format)) < 0) return -EINVAL; @@ -838,95 +1057,498 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, if (info.media_type != SPA_MEDIA_TYPE_audio || info.media_subtype != SPA_MEDIA_SUBTYPE_raw) - return -ENOTSUP; + return -EINVAL; if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; - if (info.info.raw.channels == 0 || info.info.raw.rate == 0) + if (info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) return -EINVAL; infop = &info; } - spa_log_debug(this->log, "mode:%d direction:%d %d", mode, dir, monitor); + if ((res = reconfigure_mode(this, mode, direction, monitor, control, infop)) < 0) + return res; - switch (mode) { - case SPA_PARAM_PORT_CONFIG_MODE_passthrough: - return -ENOTSUP; + emit_node_info(this, false); + break; + } + case SPA_PARAM_Props: + if (apply_props(this, param) > 0) + emit_node_info(this, false); + break; + default: + return -ENOENT; + } + return 0; +} - case SPA_PARAM_PORT_CONFIG_MODE_none: - case SPA_PARAM_PORT_CONFIG_MODE_convert: - break; +static int int32_cmp(const void *v1, const void *v2) +{ + int32_t a1 = *(int32_t*)v1; + int32_t a2 = *(int32_t*)v2; + if (a1 == 0 && a2 != 0) + return 1; + if (a2 == 0 && a1 != 0) + return -1; + return a1 - a2; +} - case SPA_PARAM_PORT_CONFIG_MODE_dsp: - info.info.raw.format = SPA_AUDIO_FORMAT_F32P; +static int setup_in_convert(struct impl *this) +{ + uint32_t i, j; + struct dir *in = &this->dir[SPA_DIRECTION_INPUT]; + struct spa_audio_info src_info, dst_info; + int res; + bool remap = false; + + src_info = in->format; + dst_info = src_info; + dst_info.info.raw.format = SPA_AUDIO_FORMAT_DSP_F32; + + spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d", this, + spa_debug_type_find_name(spa_type_audio_format, src_info.info.raw.format), + src_info.info.raw.channels, + src_info.info.raw.rate, + spa_debug_type_find_name(spa_type_audio_format, dst_info.info.raw.format), + dst_info.info.raw.channels, + dst_info.info.raw.rate); + + qsort(dst_info.info.raw.position, dst_info.info.raw.channels, + sizeof(uint32_t), int32_cmp); + + for (i = 0; i < src_info.info.raw.channels; i++) { + for (j = 0; j < dst_info.info.raw.channels; j++) { + if (src_info.info.raw.position[i] != + dst_info.info.raw.position[j]) + continue; + in->remap[i] = j; + if (i != j) + remap = true; + spa_log_debug(this->log, "%p: channel %d (%d) -> %d (%s -> %s)", this, + i, in->remap[i], j, + spa_debug_type_find_short_name(spa_type_audio_channel, + src_info.info.raw.position[i]), + spa_debug_type_find_short_name(spa_type_audio_channel, + dst_info.info.raw.position[j])); + dst_info.info.raw.position[j] = -1; break; - default: - return -EINVAL; } + } + if (in->conv.free) + convert_free(&in->conv); + + in->conv.src_fmt = src_info.info.raw.format; + in->conv.dst_fmt = dst_info.info.raw.format; + in->conv.n_channels = dst_info.info.raw.channels; + in->conv.cpu_flags = this->cpu_flags; + in->need_remap = remap; + + if ((res = convert_init(&in->conv)) < 0) + return res; + + spa_log_debug(this->log, "%p: got converter features %08x:%08x passthrough:%d remap:%d %s", this, + this->cpu_flags, in->conv.cpu_flags, in->conv.is_passthrough, + remap, in->conv.func_name); + + return 0; +} - res = reconfigure_mode(this, mode, dir, monitor, infop); +#define _MASK(ch) (1ULL << SPA_AUDIO_CHANNEL_ ## ch) +#define STEREO (_MASK(FL)|_MASK(FR)) +static uint64_t default_mask(uint32_t channels) +{ + uint64_t mask = 0; + switch (channels) { + case 7: + case 8: + mask |= _MASK(RL); + mask |= _MASK(RR); + SPA_FALLTHROUGH + case 5: + case 6: + mask |= _MASK(SL); + mask |= _MASK(SR); + if ((channels & 1) == 0) + mask |= _MASK(LFE); + SPA_FALLTHROUGH + case 3: + mask |= _MASK(FC); + SPA_FALLTHROUGH + case 2: + mask |= _MASK(FL); + mask |= _MASK(FR); break; - } - case SPA_PARAM_Props: - { - if (this->fmt[SPA_DIRECTION_INPUT] == this->merger) - res = spa_node_set_param(this->merger, id, flags, param); - res = spa_node_set_param(this->channelmix, id, flags, param); - res = spa_node_set_param(this->resample, id, flags, param); + case 1: + mask |= _MASK(MONO); break; - } - default: - res = -ENOTSUP; + case 4: + mask |= _MASK(FL); + mask |= _MASK(FR); + mask |= _MASK(RL); + mask |= _MASK(RR); break; } + return mask; +} + +static void fix_volumes(struct impl *this, struct volumes *vols, uint32_t channels) +{ + float s; + uint32_t i; + spa_log_debug(this->log, "%p %d -> %d", this, vols->n_volumes, channels); + if (vols->n_volumes > 0) { + s = 0.0f; + for (i = 0; i < vols->n_volumes; i++) + s += vols->volumes[i]; + s /= vols->n_volumes; + } else { + s = 1.0f; + } + vols->n_volumes = channels; + for (i = 0; i < vols->n_volumes; i++) + vols->volumes[i] = s; +} + +static int remap_volumes(struct impl *this, const struct spa_audio_info *info) +{ + struct props *p = &this->props; + uint32_t i, j, target = info->info.raw.channels; + + for (i = 0; i < p->n_channels; i++) { + for (j = i; j < target; j++) { + spa_log_debug(this->log, "%d %d: %d <-> %d", i, j, + p->channel_map[i], info->info.raw.position[j]); + if (p->channel_map[i] != info->info.raw.position[j]) + continue; + if (i != j) { + SPA_SWAP(p->channel_map[i], p->channel_map[j]); + SPA_SWAP(p->channel.volumes[i], p->channel.volumes[j]); + SPA_SWAP(p->soft.volumes[i], p->soft.volumes[j]); + SPA_SWAP(p->monitor.volumes[i], p->monitor.volumes[j]); + } + break; + } + } + p->n_channels = target; + for (i = 0; i < p->n_channels; i++) + p->channel_map[i] = info->info.raw.position[i]; + + if (target == 0) + return 0; + if (p->channel.n_volumes != target) + fix_volumes(this, &p->channel, target); + if (p->soft.n_volumes != target) + fix_volumes(this, &p->soft, target); + if (p->monitor.n_volumes != target) + fix_volumes(this, &p->monitor, target); + + return 1; +} + +static void set_volume(struct impl *this) +{ + struct volumes *vol; + uint32_t i; + float volumes[SPA_AUDIO_MAX_CHANNELS]; + struct dir *dir = &this->dir[this->direction]; + + spa_log_debug(this->log, "%p", this); + + if (dir->have_format) + remap_volumes(this, &dir->format); + + if (this->mix.set_volume == NULL) + return; + + if (this->props.have_soft_volume) + vol = &this->props.soft; + else + vol = &this->props.channel; + + for (i = 0; i < vol->n_volumes; i++) + volumes[i] = vol->volumes[dir->remap[i]]; + + channelmix_set_volume(&this->mix, this->props.volume, vol->mute, + vol->n_volumes, volumes); + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + this->params[IDX_Props].user++; +} + +static int setup_channelmix(struct impl *this) +{ + struct dir *in = &this->dir[SPA_DIRECTION_INPUT]; + struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT]; + uint32_t i, src_chan, dst_chan, p; + uint64_t src_mask, dst_mask; + int res; + + src_chan = in->format.info.raw.channels; + dst_chan = out->format.info.raw.channels; + + for (i = 0, src_mask = 0; i < src_chan; i++) { + p = in->format.info.raw.position[i]; + src_mask |= 1ULL << (p < 64 ? p : 0); + } + for (i = 0, dst_mask = 0; i < dst_chan; i++) { + p = out->format.info.raw.position[i]; + dst_mask |= 1ULL << (p < 64 ? p : 0); + } + + if (src_mask & 1) + src_mask = default_mask(src_chan); + if (dst_mask & 1) + dst_mask = default_mask(dst_chan); + + spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d %08"PRIx64":%08"PRIx64, this, + spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), + src_chan, + in->format.info.raw.rate, + spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), + dst_chan, + in->format.info.raw.rate, + src_mask, dst_mask); + + this->mix.src_chan = src_chan; + this->mix.src_mask = src_mask; + this->mix.dst_chan = dst_chan; + this->mix.dst_mask = dst_mask; + this->mix.cpu_flags = this->cpu_flags; + this->mix.log = this->log; + this->mix.freq = in->format.info.raw.rate; + + if ((res = channelmix_init(&this->mix)) < 0) + return res; + + set_volume(this); + + spa_log_debug(this->log, "%p: got channelmix features %08x:%08x flags:%08x %s", + this, this->cpu_flags, this->mix.cpu_flags, + this->mix.flags, this->mix.func_name); + return 0; +} + +static int setup_resample(struct impl *this) +{ + struct dir *in = &this->dir[SPA_DIRECTION_INPUT]; + struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT]; + int res; + + spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d", this, + spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), + out->format.info.raw.channels, + in->format.info.raw.rate, + spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), + out->format.info.raw.channels, + out->format.info.raw.rate); + + if (this->resample.free) + resample_free(&this->resample); + + this->resample.channels = out->format.info.raw.channels; + this->resample.i_rate = in->format.info.raw.rate; + this->resample.o_rate = out->format.info.raw.rate; + this->resample.log = this->log; + this->resample.quality = this->props.resample_quality; + this->resample.cpu_flags = this->cpu_flags; + + if (this->peaks) + res = resample_peaks_init(&this->resample); + else + res = resample_native_init(&this->resample); + + spa_log_debug(this->log, "%p: got resample features %08x:%08x %s", + this, this->cpu_flags, this->resample.cpu_flags, + this->resample.func_name); return res; } +static int calc_width(struct spa_audio_info *info) +{ + switch (info->info.raw.format) { + case SPA_AUDIO_FORMAT_U8: + case SPA_AUDIO_FORMAT_U8P: + case SPA_AUDIO_FORMAT_S8: + case SPA_AUDIO_FORMAT_S8P: + case SPA_AUDIO_FORMAT_ULAW: + case SPA_AUDIO_FORMAT_ALAW: + return 1; + case SPA_AUDIO_FORMAT_S16P: + case SPA_AUDIO_FORMAT_S16: + case SPA_AUDIO_FORMAT_S16_OE: + return 2; + case SPA_AUDIO_FORMAT_S24P: + case SPA_AUDIO_FORMAT_S24: + case SPA_AUDIO_FORMAT_S24_OE: + return 3; + case SPA_AUDIO_FORMAT_F64P: + case SPA_AUDIO_FORMAT_F64: + case SPA_AUDIO_FORMAT_F64_OE: + return 8; + default: + return 4; + } +} + +static int setup_out_convert(struct impl *this) +{ + uint32_t i, j; + struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT]; + struct spa_audio_info src_info, dst_info; + int res; + bool remap = false; + + dst_info = out->format; + src_info = dst_info; + src_info.info.raw.format = SPA_AUDIO_FORMAT_DSP_F32; + + spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d", this, + spa_debug_type_find_name(spa_type_audio_format, src_info.info.raw.format), + src_info.info.raw.channels, + src_info.info.raw.rate, + spa_debug_type_find_name(spa_type_audio_format, dst_info.info.raw.format), + dst_info.info.raw.channels, + dst_info.info.raw.rate); + + qsort(src_info.info.raw.position, src_info.info.raw.channels, + sizeof(uint32_t), int32_cmp); + + for (i = 0; i < src_info.info.raw.channels; i++) { + for (j = 0; j < dst_info.info.raw.channels; j++) { + if (src_info.info.raw.position[i] != + dst_info.info.raw.position[j]) + continue; + out->remap[i] = j; + if (i != j) + remap = true; + + spa_log_debug(this->log, "%p: channel %d (%d) -> %d (%s -> %s)", this, + i, out->remap[i], j, + spa_debug_type_find_short_name(spa_type_audio_channel, + src_info.info.raw.position[i]), + spa_debug_type_find_short_name(spa_type_audio_channel, + dst_info.info.raw.position[j])); + dst_info.info.raw.position[j] = -1; + break; + } + } + if (out->conv.free) + convert_free(&out->conv); + + out->conv.src_fmt = src_info.info.raw.format; + out->conv.dst_fmt = dst_info.info.raw.format; + out->conv.rate = dst_info.info.raw.rate; + out->conv.n_channels = dst_info.info.raw.channels; + out->conv.cpu_flags = this->cpu_flags; + out->need_remap = remap; + + if ((res = convert_init(&out->conv)) < 0) + return res; + + spa_log_debug(this->log, "%p: got converter features %08x:%08x quant:%d:%d" + " passthrough:%d remap:%d %s", this, + this->cpu_flags, out->conv.cpu_flags, out->conv.method, + out->conv.noise_bits, out->conv.is_passthrough, remap, out->conv.func_name); + + return 0; +} + +static int setup_convert(struct impl *this) +{ + struct dir *in, *out; + uint32_t i, rate; + int res; + + in = &this->dir[SPA_DIRECTION_INPUT]; + out = &this->dir[SPA_DIRECTION_OUTPUT]; + + if (!in->have_format || !out->have_format) + return -EINVAL; + + rate = this->io_position ? this->io_position->clock.rate.denom : DEFAULT_RATE; + + /* in DSP mode we always convert to the DSP rate */ + if (in->mode == SPA_PARAM_PORT_CONFIG_MODE_dsp) + in->format.info.raw.rate = rate; + if (out->mode == SPA_PARAM_PORT_CONFIG_MODE_dsp) + out->format.info.raw.rate = rate; + + /* try to passthrough the rates */ + if (in->format.info.raw.rate == 0) + in->format.info.raw.rate = out->format.info.raw.rate; + else if (out->format.info.raw.rate == 0) + out->format.info.raw.rate = in->format.info.raw.rate; + + /* try to passthrough the channels */ + if (in->format.info.raw.channels == 0) + in->format.info.raw.channels = out->format.info.raw.channels; + else if (out->format.info.raw.channels == 0) + out->format.info.raw.channels = in->format.info.raw.channels; + + if (in->format.info.raw.rate == 0 || out->format.info.raw.rate == 0) + return -EINVAL; + if (in->format.info.raw.channels == 0 || out->format.info.raw.channels == 0) + return -EINVAL; + + if ((res = setup_in_convert(this)) < 0) + return res; + if ((res = setup_channelmix(this)) < 0) + return res; + if ((res = setup_resample(this)) < 0) + return res; + if ((res = setup_out_convert(this)) < 0) + return res; + + for (i = 0; i < MAX_PORTS; i++) { + this->tmp_datas[0][i] = SPA_PTROFF(this->tmp[0], this->empty_size * i, void); + this->tmp_datas[0][i] = SPA_PTR_ALIGN(this->tmp_datas[0][i], MAX_ALIGN, void); + this->tmp_datas[1][i] = SPA_PTROFF(this->tmp[1], this->empty_size * i, void); + this->tmp_datas[1][i] = SPA_PTR_ALIGN(this->tmp_datas[1][i], MAX_ALIGN, void); + } + + emit_node_info(this, false); + + return 0; +} + +static void reset_node(struct impl *this) +{ + if (this->resample.reset) + resample_reset(&this->resample); + this->in_offset = 0; + this->out_offset = 0; +} + static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; - int res, i; + int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: + if (this->started) + return 0; if ((res = setup_convert(this)) < 0) return res; - if ((res = setup_buffers(this, SPA_DIRECTION_INPUT)) < 0) - return res; + this->started = true; break; - case SPA_NODE_COMMAND_Suspend: - clean_convert(this); - SPA_FALLTHROUGH + SPA_FALLTHROUGH; case SPA_NODE_COMMAND_Flush: - flush_convert(this); - SPA_FALLTHROUGH + reset_node(this); + SPA_FALLTHROUGH; case SPA_NODE_COMMAND_Pause: this->started = false; break; default: return -ENOTSUP; } - - for (i = 0; i < this->n_nodes; i++) { - if ((res = spa_node_send_command(this->nodes[i], command)) < 0) { - spa_log_error(this->log, "%p: can't send command to node %d: %s", - this, i, spa_strerror(res)); - } - } - - switch (SPA_NODE_COMMAND_ID(command)) { - case SPA_NODE_COMMAND_Start: - this->started = true; - break; - } - return 0; } @@ -937,37 +1559,21 @@ impl_node_add_listener(void *object, void *data) { struct impl *this = object; + uint32_t i; struct spa_hook_list save; - struct spa_hook l[3]; spa_return_val_if_fail(this != NULL, -EINVAL); - spa_hook_list_isolate(&this->hooks, &save, listener, events, data); - spa_log_trace(this->log, "%p: add listener %p", this, listener); - - this->add_listener = true; - - spa_zero(l); - if (this->fmt[SPA_DIRECTION_INPUT]) - spa_node_add_listener(this->fmt[SPA_DIRECTION_INPUT], - &l[0], &fmt_input_events, this); - spa_node_add_listener(this->channelmix, - &l[1], &channelmix_events, this); - if (this->fmt[SPA_DIRECTION_OUTPUT]) - spa_node_add_listener(this->fmt[SPA_DIRECTION_OUTPUT], - &l[2], &fmt_output_events, this); - - if (this->fmt[SPA_DIRECTION_INPUT]) - spa_hook_remove(&l[0]); - spa_hook_remove(&l[1]); - if (this->fmt[SPA_DIRECTION_OUTPUT]) - spa_hook_remove(&l[2]); - - this->add_listener = false; + spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); - + for (i = 0; i < this->dir[SPA_DIRECTION_INPUT].n_ports; i++) { + emit_port_info(this, GET_IN_PORT(this, i), true); + } + for (i = 0; i < this->dir[SPA_DIRECTION_OUTPUT].n_ports; i++) { + emit_port_info(this, GET_OUT_PORT(this, i), true); + } spa_hook_list_join(&this->hooks, &save); return 0; @@ -978,7 +1584,7 @@ impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *user_data) { - return -ENOTSUP; + return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, @@ -993,81 +1599,374 @@ impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_ return -ENOTSUP; } -static int -impl_node_port_enum_params(void *object, int seq, - enum spa_direction direction, uint32_t port_id, - uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) +static int port_enum_formats(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t index, + struct spa_pod **param, + struct spa_pod_builder *builder) +{ + struct impl *this = object; + struct port *port = GET_PORT(this, direction, port_id); + + switch (index) { + case 0: + if (PORT_IS_DSP(this, direction, port_id)) { + struct spa_audio_info_dsp info; + info.format = SPA_AUDIO_FORMAT_DSP_F32; + *param = spa_format_audio_dsp_build(builder, + SPA_PARAM_EnumFormat, &info); + } else if (PORT_IS_CONTROL(this, direction, port_id)) { + *param = spa_pod_builder_add_object(builder, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); + } else if (port->have_format) { + *param = spa_format_audio_raw_build(builder, + SPA_PARAM_EnumFormat, &this->dir[direction].format.info.raw); + } + else { + uint32_t rate = this->io_position ? + this->io_position->clock.rate.denom : DEFAULT_RATE; + + *param = spa_pod_builder_add_object(builder, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(25, + SPA_AUDIO_FORMAT_F32P, + SPA_AUDIO_FORMAT_F32P, + SPA_AUDIO_FORMAT_F32, + SPA_AUDIO_FORMAT_F32_OE, + SPA_AUDIO_FORMAT_F64P, + SPA_AUDIO_FORMAT_F64, + SPA_AUDIO_FORMAT_F64_OE, + SPA_AUDIO_FORMAT_S32P, + SPA_AUDIO_FORMAT_S32, + SPA_AUDIO_FORMAT_S32_OE, + SPA_AUDIO_FORMAT_S24_32P, + SPA_AUDIO_FORMAT_S24_32, + SPA_AUDIO_FORMAT_S24_32_OE, + SPA_AUDIO_FORMAT_S24P, + SPA_AUDIO_FORMAT_S24, + SPA_AUDIO_FORMAT_S24_OE, + SPA_AUDIO_FORMAT_S16P, + SPA_AUDIO_FORMAT_S16, + SPA_AUDIO_FORMAT_S16_OE, + SPA_AUDIO_FORMAT_S8P, + SPA_AUDIO_FORMAT_S8, + SPA_AUDIO_FORMAT_U8P, + SPA_AUDIO_FORMAT_U8, + SPA_AUDIO_FORMAT_ULAW, + SPA_AUDIO_FORMAT_ALAW), + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int( + rate, 1, INT32_MAX), + SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int( + DEFAULT_CHANNELS, 1, SPA_AUDIO_MAX_CHANNELS)); + } + break; + default: + return 0; + } + return 1; +} + +static int +impl_node_port_enum_params(void *object, int seq, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + struct impl *this = object; + struct port *port; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[2048]; + struct spa_result_node_params result; + uint32_t count = 0; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + + spa_log_debug(this->log, "%p: enum params port %d.%d %d %u", + this, direction, port_id, seq, id); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + result.id = id; + result.next = start; + next: + result.index = result.next++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + switch (id) { + case SPA_PARAM_EnumFormat: + if ((res = port_enum_formats(object, direction, port_id, result.index, ¶m, &b)) <= 0) + return res; + break; + case SPA_PARAM_Format: + if (!port->have_format) + return -EIO; + if (result.index > 0) + return 0; + + if (PORT_IS_DSP(this, direction, port_id)) + param = spa_format_audio_dsp_build(&b, id, &port->format.info.dsp); + else if (PORT_IS_CONTROL(this, direction, port_id)) + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Format, id, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); + else + param = spa_format_audio_raw_build(&b, id, &port->format.info.raw); + break; + 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) { + /* DSP ports always use the quantum_limit as the buffer + * size. */ + size = this->quantum_limit; + } else { + uint32_t irate, orate; + /* Convert ports are scaled so that they can always + * provide one quantum of data */ + irate = dir->format.info.raw.rate; + + /* collect the other port rate */ + dir = &this->dir[SPA_DIRECTION_REVERSE(direction)]; + if (dir->mode == SPA_PARAM_PORT_CONFIG_MODE_dsp) + orate = this->io_position ? this->io_position->clock.rate.denom : DEFAULT_RATE; + else + orate = dir->format.info.raw.rate; + + /* always keep some extra room for adaptive resampling */ + size = this->quantum_limit * 2; + /* scale the buffer size when we can. */ + if (irate != 0 && orate != 0) + size = size * (irate + orate - 1) / orate; + } + + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamBuffers, id, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->blocks), + SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( + size * port->stride, + 16 * port->stride, + INT32_MAX), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->stride)); + break; + } + case SPA_PARAM_Meta: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamMeta, id, + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), + SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); + break; + default: + return 0; + } + break; + case SPA_PARAM_IO: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); + break; + default: + return 0; + } + break; + case SPA_PARAM_Latency: + switch (result.index) { + case 0: case 1: + param = spa_latency_build(&b, id, &this->dir[result.index].latency); + break; + default: + return 0; + } + break; + default: + return -ENOENT; + } + + if (spa_pod_filter(&b, &result.param, param, filter) < 0) + goto next; + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + return 0; +} + +static int clear_buffers(struct impl *this, struct port *port) +{ + if (port->n_buffers > 0) { + spa_log_debug(this->log, "%p: clear buffers %p", this, port); + port->n_buffers = 0; + spa_list_init(&port->queue); + } + return 0; +} + +static int port_set_latency(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t flags, + const struct spa_pod *latency) +{ + struct impl *this = object; + struct port *port; + enum spa_direction other = SPA_DIRECTION_REVERSE(direction); + uint32_t i; + + spa_log_debug(this->log, "%p: set latency direction:%d id:%d", + this, direction, port_id); + + port = GET_PORT(this, direction, port_id); + if (port->is_monitor) + return 0; + + if (latency == NULL) { + this->dir[other].latency = SPA_LATENCY_INFO(other); + } else { + struct spa_latency_info info; + if (spa_latency_parse(latency, &info) < 0 || + info.direction != other) + return -EINVAL; + this->dir[other].latency = info; + } + + for (i = 0; i < this->dir[other].n_ports; i++) { + port = GET_PORT(this, other, i); + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->params[IDX_Latency].user++; + emit_port_info(this, port, false); + } + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->params[IDX_Latency].user++; + emit_port_info(this, port, false); + return 0; +} + +static int port_set_format(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t flags, + const struct spa_pod *format) { struct impl *this = object; - struct spa_pod *param; - struct spa_pod_builder b = { 0 }; - uint8_t buffer[4096]; - struct spa_result_node_params result; - uint32_t count = 0; + struct port *port; int res; - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(num != 0, -EINVAL); - - spa_log_debug(this->log, "%p: port %d.%d %d %u", this, direction, port_id, seq, id); + port = GET_PORT(this, direction, port_id); - result.id = id; - result.next = start; - next: - result.index = result.next; + spa_log_debug(this->log, "%p: set format", this); - spa_pod_builder_init(&b, buffer, sizeof(buffer)); + if (format == NULL) { + port->have_format = false; + clear_buffers(this, port); + } else { + struct spa_audio_info info = { 0 }; - switch (id) { - case SPA_PARAM_IO: - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamIO, id, - SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), - SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); - break; - case 1: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamIO, id, - SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_RateMatch), - SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_rate_match))); - break; - default: - return 0; + if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) { + spa_log_error(this->log, "can't parse format %s", spa_strerror(res)); + return res; } - result.next++; - break; - default: - { - struct spa_node *target; - - if (IS_MONITOR_PORT(this, direction, port_id)) - target = this->fmt[SPA_DIRECTION_INPUT]; - else - target = this->fmt[direction]; + if (PORT_IS_DSP(this, direction, port_id)) { + if (info.media_type != SPA_MEDIA_TYPE_audio || + info.media_subtype != SPA_MEDIA_SUBTYPE_dsp) { + spa_log_error(this->log, "unexpected types %d/%d", + info.media_type, info.media_subtype); + return -EINVAL; + } + if ((res = spa_format_audio_dsp_parse(format, &info.info.dsp)) < 0) { + spa_log_error(this->log, "can't parse format %s", spa_strerror(res)); + return res; + } + if (info.info.dsp.format != SPA_AUDIO_FORMAT_DSP_F32) { + spa_log_error(this->log, "unexpected format %d<->%d", + info.info.dsp.format, SPA_AUDIO_FORMAT_DSP_F32); + return -EINVAL; + } + port->blocks = 1; + port->stride = 4; + } + else if (PORT_IS_CONTROL(this, direction, port_id)) { + if (info.media_type != SPA_MEDIA_TYPE_application || + info.media_subtype != SPA_MEDIA_SUBTYPE_control) { + spa_log_error(this->log, "unexpected types %d/%d", + info.media_type, info.media_subtype); + return -EINVAL; + } + port->blocks = 1; + port->stride = 1; + } + else { + if (info.media_type != SPA_MEDIA_TYPE_audio || + info.media_subtype != SPA_MEDIA_SUBTYPE_raw) { + spa_log_error(this->log, "unexpected types %d/%d", + info.media_type, info.media_subtype); + return -EINVAL; + } + if ((res = spa_format_audio_raw_parse(format, &info.info.raw)) < 0) { + spa_log_error(this->log, "can't parse format %s", spa_strerror(res)); + return res; + } + if (info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) { + spa_log_error(this->log, "too many channels %d > %d", + info.info.raw.channels, SPA_AUDIO_MAX_CHANNELS); + return -EINVAL; + } + port->stride = calc_width(&info); + if (SPA_AUDIO_FORMAT_IS_PLANAR(info.info.raw.format)) { + port->blocks = info.info.raw.channels; + } else { + port->stride *= info.info.raw.channels; + port->blocks = 1; + } + this->dir[direction].format = info; + this->dir[direction].have_format = true; + } + port->format = info; + port->have_format = true; - res = spa_node_port_enum_params_sync(target, - direction, port_id, - id, &result.next, - NULL, ¶m, &b); - if (res != 1) - return res; - } + spa_log_debug(this->log, "%p: %d %d %d", this, + port_id, port->stride, port->blocks); } - if (spa_pod_filter(&b, &result.param, param, filter) < 0) - goto next; - - spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - - if (++count != num) - goto next; + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + if (port->have_format) { + port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); + port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); + } else { + port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + } + emit_port_info(this, port, false); return 0; } + static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, @@ -1075,41 +1974,56 @@ impl_node_port_set_param(void *object, const struct spa_pod *param) { struct impl *this = object; - int res; - struct spa_node *target; - bool is_monitor; spa_return_val_if_fail(this != NULL, -EINVAL); - spa_log_debug(this->log, "%p: set param %u on port %d:%d %p", - this, id, direction, port_id, param); + spa_log_debug(this->log, "%p: set param port %d.%d %u", + this, direction, port_id, id); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); switch (id) { + case SPA_PARAM_Latency: + return port_set_latency(this, direction, port_id, flags, param); + case SPA_PARAM_Format: + return port_set_format(this, direction, port_id, flags, param); default: - is_monitor = IS_MONITOR_PORT(this, direction, port_id); - if (is_monitor) - target = this->fmt[SPA_DIRECTION_INPUT]; - else - target = this->fmt[direction]; - break; + return -ENOENT; } +} - if ((res = spa_node_port_set_param(target, - direction, port_id, id, flags, param)) < 0) - return res; +static void queue_buffer(struct impl *this, struct port *port, uint32_t id) +{ + struct buffer *b = &port->buffers[id]; - switch (id) { - case SPA_PARAM_Latency: - if (port_id == 0) { - target = this->fmt[SPA_DIRECTION_REVERSE(direction)]; - if ((res = spa_node_port_set_param(target, - direction, port_id, id, flags, param)) < 0) - return res; - } - break; - } + spa_log_trace_fp(this->log, "%p: queue buffer %d on port %d %d", + this, id, port->id, b->flags); + if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_QUEUED)) + return; - return res; + spa_list_append(&port->queue, &b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_QUEUED); +} + +static struct buffer *peek_buffer(struct impl *this, struct port *port) +{ + struct buffer *b; + + if (spa_list_is_empty(&port->queue)) + return NULL; + + b = spa_list_first(&port->queue, struct buffer, link); + spa_log_trace_fp(this->log, "%p: peek buffer %d on port %d %u", + this, b->id, port->id, b->flags); + return b; +} + +static void dequeue_buffer(struct impl *this, struct port *port, struct buffer *b) +{ + spa_list_remove(&b->link); + SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_QUEUED); + spa_log_trace_fp(this->log, "%p: dequeue buffer %d on port %d %u", + this, b->id, port->id, b->flags); } static int @@ -1121,21 +2035,75 @@ impl_node_port_use_buffers(void *object, uint32_t n_buffers) { struct impl *this = object; - int res; - struct spa_node *target; + struct port *port; + uint32_t i, j, maxsize; spa_return_val_if_fail(this != NULL, -EINVAL); - if (IS_MONITOR_PORT(this, direction, port_id)) - target = this->fmt[SPA_DIRECTION_INPUT]; - else - target = this->fmt[direction]; + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - if ((res = spa_node_port_use_buffers(target, - direction, port_id, flags, buffers, n_buffers)) < 0) - return res; + port = GET_PORT(this, direction, port_id); - return res; + spa_return_val_if_fail(port->have_format, -EIO); + + spa_log_debug(this->log, "%p: use buffers %d on port %d:%d", + this, n_buffers, direction, port_id); + + clear_buffers(this, port); + + maxsize = this->quantum_limit * sizeof(float); + + for (i = 0; i < n_buffers; i++) { + struct buffer *b; + uint32_t n_datas = buffers[i]->n_datas; + struct spa_data *d = buffers[i]->datas; + + b = &port->buffers[i]; + b->id = i; + b->flags = 0; + b->buf = buffers[i]; + + if (n_datas != port->blocks) { + spa_log_error(this->log, "%p: invalid blocks %d on buffer %d", + this, n_datas, i); + return -EINVAL; + } + + for (j = 0; j < n_datas; j++) { + if (d[j].data == NULL) { + spa_log_error(this->log, "%p: invalid memory %d on buffer %d %d %p", + this, j, i, d[j].type, d[j].data); + return -EINVAL; + } + if (!SPA_IS_ALIGNED(d[j].data, this->max_align)) { + spa_log_warn(this->log, "%p: memory %d on buffer %d not aligned", + this, j, i); + } + if (direction == SPA_DIRECTION_OUTPUT && + !SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_DYNAMIC)) + this->is_passthrough = false; + + b->datas[j] = d[j].data; + + maxsize = SPA_MAX(maxsize, d[j].maxsize); + } + if (direction == SPA_DIRECTION_OUTPUT) + queue_buffer(this, port, i); + } + if (maxsize > this->empty_size) { + this->empty = realloc(this->empty, maxsize + MAX_ALIGN); + this->scratch = realloc(this->scratch, maxsize + MAX_ALIGN); + this->tmp[0] = realloc(this->tmp[0], (maxsize + MAX_ALIGN) * MAX_PORTS); + this->tmp[1] = realloc(this->tmp[1], (maxsize + MAX_ALIGN) * MAX_PORTS); + if (this->empty == NULL || this->scratch == NULL || + this->tmp[0] == NULL || this->tmp[1] == NULL) + return -errno; + memset(this->empty, 0, maxsize + MAX_ALIGN); + this->empty_size = maxsize; + } + port->n_buffers = n_buffers; + + return 0; } static int @@ -1144,81 +2112,592 @@ impl_node_port_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; - struct spa_node *target; - int res; + struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); - spa_log_debug(this->log, "set io %d %d %d", id, direction, port_id); + spa_log_debug(this->log, "%p: set io %d on port %d:%d %p", + this, id, direction, port_id, data); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); switch (id) { + case SPA_IO_Buffers: + port->io = data; + break; case SPA_IO_RateMatch: - res = spa_node_port_set_io(this->resample, direction, 0, id, data, size); + this->io_rate_match = data; break; default: - if (IS_MONITOR_PORT(this, direction, port_id)) - target = this->fmt[SPA_DIRECTION_INPUT]; - else - target = this->fmt[direction]; - - res = spa_node_port_set_io(target, direction, port_id, id, data, size); - break; + return -ENOENT; } - return res; + return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; - struct spa_node *target; + struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); - if (IS_MONITOR_PORT(this, SPA_DIRECTION_OUTPUT, port_id)) - target = this->fmt[SPA_DIRECTION_INPUT]; - else - target = this->fmt[SPA_DIRECTION_OUTPUT]; + port = GET_OUT_PORT(this, port_id); + queue_buffer(this, port, buffer_id); + + return 0; +} + +static int channelmix_process_control(struct impl *this, struct port *ctrlport, + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + struct spa_pod_control *c, *prev = NULL; + uint32_t avail_samples = n_samples; + uint32_t i; + const float *s[MAX_PORTS], **ss = (const float**) src; + float *d[MAX_PORTS], **sd = (float **) dst; + const struct spa_pod_sequence_body *body = &(ctrlport->ctrl)->body; + uint32_t size = SPA_POD_BODY_SIZE(ctrlport->ctrl); + bool end = false; + + c = spa_pod_control_first(body); + while (true) { + uint32_t chunk; + + if (c == NULL || !spa_pod_control_is_inside(body, size, c)) { + c = NULL; + end = true; + } + if (avail_samples == 0) + break; + + /* ignore old control offsets */ + if (c != NULL) { + if (c->offset <= ctrlport->ctrl_offset) { + prev = c; + if (c != NULL) + c = spa_pod_control_next(c); + continue; + } + chunk = SPA_MIN(avail_samples, c->offset - ctrlport->ctrl_offset); + spa_log_trace_fp(this->log, "%p: process %d-%d %d/%d", this, + ctrlport->ctrl_offset, c->offset, chunk, avail_samples); + } else { + chunk = avail_samples; + spa_log_trace_fp(this->log, "%p: process remain %d", this, chunk); + } + + if (prev) { + switch (prev->type) { + case SPA_CONTROL_Midi: + apply_midi(this, &prev->value); + break; + case SPA_CONTROL_Properties: + apply_props(this, &prev->value); + break; + default: + continue; + } + } + if (ss == (const float**)src && chunk != avail_samples) { + for (i = 0; i < this->mix.src_chan; i++) + s[i] = ss[i]; + for (i = 0; i < this->mix.dst_chan; i++) + d[i] = sd[i]; + ss = s; + sd = d; + } + + channelmix_process(&this->mix, (void**)sd, (const void**)ss, chunk); + + if (chunk != avail_samples) { + for (i = 0; i < this->mix.src_chan; i++) + ss[i] += chunk; + for (i = 0; i < this->mix.dst_chan; i++) + sd[i] += chunk; + } + avail_samples -= chunk; + ctrlport->ctrl_offset += chunk; + } + return end ? 1 : 0; +} + +static uint32_t resample_get_in_size(struct impl *this, bool passthrough, uint32_t out_size) +{ + uint32_t match_size = passthrough ? out_size : resample_in_len(&this->resample, out_size); + spa_log_trace_fp(this->log, "%p: current match %u", this, match_size); + return match_size; +} + +static uint32_t resample_update_rate_match(struct impl *this, bool passthrough, uint32_t out_size, uint32_t in_queued) +{ + uint32_t delay, match_size; - return spa_node_port_reuse_buffer(target, port_id, buffer_id); + if (passthrough) { + delay = 0; + match_size = out_size; + } else { + double rate = this->rate_scale / this->props.rate; + if (this->io_rate_match && + SPA_FLAG_IS_SET(this->io_rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE)) + rate *= this->io_rate_match->rate; + resample_update_rate(&this->resample, rate); + delay = resample_delay(&this->resample); + match_size = resample_in_len(&this->resample, out_size); + } + match_size -= SPA_MIN(match_size, in_queued); + + spa_log_trace_fp(this->log, "%p: next match %u", this, match_size); + + if (this->io_rate_match) { + this->io_rate_match->delay = delay; + this->io_rate_match->size = match_size; + } + return match_size; +} + +static inline bool resample_is_passthrough(struct impl *this) +{ + return this->resample.i_rate == this->resample.o_rate && this->rate_scale == 1.0 && + this->props.rate == 1.0 && + (this->io_rate_match == NULL || + !SPA_FLAG_IS_SET(this->io_rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE)); } static int impl_node_process(void *object) { struct impl *this = object; - int r, i, res = SPA_STATUS_OK; - int ready; + const void *src_datas[MAX_PORTS], **in_datas; + void *dst_datas[MAX_PORTS], *remap_src_datas[MAX_PORTS], *remap_dst_datas[MAX_PORTS]; + void **out_datas, **dst_remap; + uint32_t i, j, n_src_datas = 0, n_dst_datas = 0, n_mon_datas = 0, remap; + uint32_t n_samples, max_in, n_out, max_out, quant_samples; + struct port *port, *ctrlport = NULL; + struct buffer *buf, *out_bufs[MAX_PORTS]; + struct spa_data *bd; + struct dir *dir; + int tmp = 0, res = 0; + bool in_passthrough, mix_passthrough, resample_passthrough, out_passthrough; + bool in_avail = false, flush_in = false, flush_out = false, draining = false, in_empty = true; + struct spa_io_buffers *io, *ctrlio = NULL; + const struct spa_pod_sequence *ctrl = NULL; + + /* calculate quantum scale, this is how many samples we need to produce or + * consume. Also update the rate scale, this is sent to the resampler to adjust + * the rate, either when the graph clock changed or when the user adjusted the + * rate. */ + if (SPA_LIKELY(this->io_position)) { + double r = this->rate_scale; + + quant_samples = this->io_position->clock.duration; + if (this->direction == SPA_DIRECTION_INPUT) { + if (this->io_position->clock.rate.denom != this->resample.o_rate) + r = (double) this->io_position->clock.rate.denom / this->resample.o_rate; + else + r = 1.0; + } else { + if (this->io_position->clock.rate.denom != this->resample.i_rate) + r = (double) this->resample.i_rate / this->io_position->clock.rate.denom; + else + r = 1.0; + } + if (this->rate_scale != r) { + spa_log_info(this->log, "scale %f->%f", this->rate_scale, r); + this->rate_scale = r; + } + } + else + quant_samples = this->quantum_limit; + + dir = &this->dir[SPA_DIRECTION_INPUT]; + in_passthrough = dir->conv.is_passthrough; + max_in = UINT32_MAX; + + /* collect input port data */ + for (i = 0; i < dir->n_ports; i++) { + port = GET_IN_PORT(this, i); + + if (SPA_UNLIKELY((io = port->io) == NULL)) { + spa_log_trace_fp(this->log, "%p: no io on input port %d", + this, port->id); + buf = NULL; + } else if (SPA_UNLIKELY(io->status != SPA_STATUS_HAVE_DATA)) { + if (io->status & SPA_STATUS_DRAINED) { + spa_log_debug(this->log, "%p: port %d drained", this, port->id); + in_avail = flush_in = draining = true; + } else { + spa_log_trace_fp(this->log, "%p: empty input port %d %p %d %d %d", + this, port->id, io, io->status, io->buffer_id, + port->n_buffers); + this->drained = false; + } + buf = NULL; + } else if (SPA_UNLIKELY(io->buffer_id >= port->n_buffers)) { + spa_log_trace_fp(this->log, "%p: invalid input buffer port %d %p %d %d %d", + this, port->id, io, io->status, io->buffer_id, + port->n_buffers); + io->status = -EINVAL; + buf = NULL; + } else { + spa_log_trace_fp(this->log, "%p: input buffer port %d io:%p status:%d id:%d n:%d", + this, port->id, io, io->status, io->buffer_id, + port->n_buffers); + buf = &port->buffers[io->buffer_id]; + } + + if (SPA_UNLIKELY(buf == NULL)) { + for (j = 0; j < port->blocks; j++) { + if (port->is_control) { + spa_log_trace_fp(this->log, "%p: empty control %d", this, + i * port->blocks + j); + } else { + remap = n_src_datas++; + src_datas[remap] = SPA_PTR_ALIGN(this->empty, MAX_ALIGN, void); + spa_log_trace_fp(this->log, "%p: empty input %d->%d", this, + i * port->blocks + j, remap); + max_in = SPA_MIN(max_in, this->empty_size / port->stride); + } + } + } else { + in_avail = true; + for (j = 0; j < port->blocks; j++) { + uint32_t offs, size; + + bd = &buf->buf->datas[j]; + + offs = SPA_MIN(bd->chunk->offset, bd->maxsize); + size = SPA_MIN(bd->maxsize - offs, bd->chunk->size); + if (!SPA_FLAG_IS_SET(bd->chunk->flags, SPA_CHUNK_FLAG_EMPTY)) + in_empty = false; + + if (SPA_UNLIKELY(port->is_control)) { + spa_log_trace_fp(this->log, "%p: control %d", this, + i * port->blocks + j); + ctrlport = port; + ctrlio = io; + ctrl = spa_pod_from_data(bd->data, bd->maxsize, + bd->chunk->offset, bd->chunk->size); + if (ctrl && !spa_pod_is_sequence(&ctrl->pod)) + ctrl = NULL; + if (ctrl != ctrlport->ctrl) { + ctrlport->ctrl = ctrl; + ctrlport->ctrl_offset = 0; + } + } else { + max_in = SPA_MIN(max_in, size / port->stride); + + remap = n_src_datas++; + offs += this->in_offset * port->stride; + src_datas[remap] = SPA_PTROFF(bd->data, offs, void); + + spa_log_trace_fp(this->log, "%p: input %d:%d:%d %d %d %d->%d", this, + offs, size, port->stride, this->in_offset, max_in, + i * port->blocks + j, remap); + } + } + } + } + + /* calculate how many samples we are going to produce. */ + if (this->direction == SPA_DIRECTION_INPUT) { + /* in split mode we need to output exactly the size of the + * duration so we don't try to flush early */ + max_out = quant_samples; + flush_out = false; + } else { + /* in merge mode we consume one duration of samples and + * always output the resulting data */ + max_out = this->quantum_limit; + flush_out = true; + } + + dir = &this->dir[SPA_DIRECTION_OUTPUT]; + /* collect output ports and monitor ports data */ + for (i = 0; i < dir->n_ports; i++) { + port = GET_OUT_PORT(this, i); + + if (SPA_UNLIKELY((io = port->io) == NULL || + io->status == SPA_STATUS_HAVE_DATA)) { + buf = NULL; + } + else { + if (SPA_LIKELY(io->buffer_id < port->n_buffers)) + queue_buffer(this, port, io->buffer_id); + + buf = peek_buffer(this, port); + } + out_bufs[i] = buf; + + if (SPA_UNLIKELY(buf == NULL)) { + for (j = 0; j < port->blocks; j++) { + if (port->is_monitor) { + remap = n_mon_datas++; + spa_log_trace_fp(this->log, "%p: empty monitor %d", this, + remap); + } else if (port->is_control) { + spa_log_trace_fp(this->log, "%p: empty control %d", this, j); + } else { + remap = n_dst_datas++; + dst_datas[remap] = SPA_PTR_ALIGN(this->scratch, MAX_ALIGN, void); + spa_log_trace_fp(this->log, "%p: empty output %d->%d", this, + i * port->blocks + j, remap); + max_out = SPA_MIN(max_out, this->empty_size / port->stride); + } + } + } else { + for (j = 0; j < port->blocks; j++) { + bd = &buf->buf->datas[j]; + + bd->chunk->offset = 0; + bd->chunk->size = 0; + if (port->is_monitor) { + float volume; + uint32_t mon_max; + + remap = n_mon_datas++; + volume = this->props.monitor.mute ? 0.0f : this->props.monitor.volumes[remap]; + if (this->monitor_channel_volumes) + volume *= this->props.channel.mute ? 0.0f : + this->props.channel.volumes[remap]; + + mon_max = SPA_MIN(bd->maxsize / port->stride, max_in); + + volume_process(&this->volume, bd->data, src_datas[remap], + volume, mon_max); + + bd->chunk->size = mon_max * port->stride; + + spa_log_trace_fp(this->log, "%p: monitor %d %d", this, + remap, mon_max); + + dequeue_buffer(this, port, buf); + io->status = SPA_STATUS_HAVE_DATA; + io->buffer_id = buf->id; + res |= SPA_STATUS_HAVE_DATA; + } else if (SPA_UNLIKELY(port->is_control)) { + spa_log_trace_fp(this->log, "%p: control %d", this, j); + } else { + remap = n_dst_datas++; + dst_datas[remap] = SPA_PTROFF(bd->data, + this->out_offset * port->stride, void); + max_out = SPA_MIN(max_out, bd->maxsize / port->stride); + + spa_log_trace_fp(this->log, "%p: output %d offs:%d %d->%d", this, + max_out, this->out_offset, + i * port->blocks + j, remap); + } + } + } + } - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_log_trace_fp(this->log, "%p: process %d %d", this, this->n_links, this->n_nodes); + /* calculate how many samples at most we are going to consume. If we're + * draining, we consume as much as we can. Otherwise we consume what is + * left. */ + if (SPA_UNLIKELY(draining)) + n_samples = SPA_MIN(max_in, this->quantum_limit); + else { + n_samples = max_in - SPA_MIN(max_in, this->in_offset); + } + /* we only need to output the remaining samples */ + n_out = max_out - SPA_MIN(max_out, this->out_offset); + + resample_passthrough = resample_is_passthrough(this); + + /* calculate how many samples we are going to consume. */ + if (this->direction == SPA_DIRECTION_INPUT) { + if (!in_avail || this->drained) { + /* no input, ask for more, update rate-match first */ + resample_update_rate_match(this, resample_passthrough, n_out, 0); + spa_log_trace_fp(this->log, "%p: no input drained:%d", this, this->drained); + res |= this->drained ? SPA_STATUS_DRAINED : SPA_STATUS_NEED_DATA; + return res; + } + /* else figure out how much input samples we need to consume */ + n_samples = SPA_MIN(n_samples, + resample_get_in_size(this, resample_passthrough, n_out)); + } else { + /* in merge mode we consume one duration of samples */ + n_samples = SPA_MIN(n_samples, quant_samples); + flush_in = true; + } + + mix_passthrough = SPA_FLAG_IS_SET(this->mix.flags, CHANNELMIX_FLAG_IDENTITY) && + (ctrlport == NULL || ctrlport->ctrl == NULL); - while (1) { - res = SPA_STATUS_OK; - ready = 0; - for (i = 0; i < this->n_nodes; i++) { - r = spa_node_process(this->nodes[i]); + out_passthrough = dir->conv.is_passthrough; + if (in_passthrough && mix_passthrough && resample_passthrough) + out_passthrough = false; + + if (out_passthrough && dir->need_remap) { + for (i = 0; i < dir->conv.n_channels; i++) { + remap_dst_datas[i] = dst_datas[dir->remap[i]]; + spa_log_trace_fp(this->log, "%p: output remap %d -> %d", this, i, dir->remap[i]); + } + dst_remap = (void **)remap_dst_datas; + } else { + dst_remap = (void **)dst_datas; + } - spa_log_trace_fp(this->log, "%p: process %d %d: %s", - this, i, r, r < 0 ? spa_strerror(r) : "ok"); + dir = &this->dir[SPA_DIRECTION_INPUT]; + if (!in_passthrough) { + if (mix_passthrough && resample_passthrough && out_passthrough) + out_datas = (void **)dst_remap; + else + out_datas = (void **)this->tmp_datas[(tmp++) & 1]; - if (SPA_UNLIKELY(r < 0)) - return r; + if (dir->need_remap) { + for (i = 0; i < dir->conv.n_channels; i++) { + remap_src_datas[i] = out_datas[dir->remap[i]]; + spa_log_trace_fp(this->log, "%p: input remap %d -> %d", this, dir->remap[i], i); + } + } else { + for (i = 0; i < dir->conv.n_channels; i++) + remap_src_datas[i] = out_datas[i]; + } - if (r & SPA_STATUS_HAVE_DATA) - ready++; + spa_log_trace_fp(this->log, "%p: input convert %d", this, n_samples); + convert_process(&dir->conv, remap_src_datas, src_datas, n_samples); + } else { + if (dir->need_remap) { + for (i = 0; i < dir->conv.n_channels; i++) { + remap_src_datas[dir->remap[i]] = (void *)src_datas[i]; + spa_log_trace_fp(this->log, "%p: input remap %d -> %d", this, dir->remap[i], i); + } + out_datas = (void **)remap_src_datas; + } else { + out_datas = (void **)src_datas; + } + } - if (SPA_UNLIKELY(i == 0)) - res |= r & SPA_STATUS_NEED_DATA; - if (SPA_UNLIKELY(i == this->n_nodes-1)) - res |= r & (SPA_STATUS_HAVE_DATA | SPA_STATUS_DRAINED); + if (!mix_passthrough) { + in_datas = (const void**)out_datas; + if (resample_passthrough && out_passthrough) { + out_datas = (void **)dst_remap; + n_samples = SPA_MIN(n_samples, n_out); + } else { + out_datas = (void **)this->tmp_datas[(tmp++) & 1]; } - if (res & SPA_STATUS_HAVE_DATA) - break; - if (ready == 0) - break; + spa_log_trace_fp(this->log, "%p: channelmix %d %d %d", this, n_samples, + resample_passthrough, out_passthrough); + if (ctrlport != NULL && ctrlport->ctrl != NULL) { + if (channelmix_process_control(this, ctrlport, out_datas, + in_datas, n_samples) == 1) { + ctrlio->status = SPA_STATUS_OK; + ctrlport->ctrl = NULL; + } + } else { + channelmix_process(&this->mix, out_datas, in_datas, n_samples); + } + } + if (!resample_passthrough) { + uint32_t in_len, out_len; + + in_datas = (const void**)out_datas; + if (out_passthrough) + out_datas = (void **)dst_remap; + else + out_datas = (void **)this->tmp_datas[(tmp++) & 1]; + + in_len = n_samples; + out_len = n_out; + resample_process(&this->resample, in_datas, &in_len, out_datas, &out_len); + spa_log_trace_fp(this->log, "%p: resample %d/%d -> %d/%d %d", this, + n_samples, in_len, n_out, out_len, out_passthrough); + this->in_offset += in_len; + n_samples = out_len; + } else { + n_samples = SPA_MIN(n_samples, n_out); + this->in_offset += n_samples; + } + this->out_offset += n_samples; + + if (!out_passthrough) { + dir = &this->dir[SPA_DIRECTION_OUTPUT]; + if (dir->need_remap) { + for (i = 0; i < dir->conv.n_channels; i++) { + remap_dst_datas[dir->remap[i]] = out_datas[i]; + spa_log_trace_fp(this->log, "%p: output remap %d -> %d", this, i, dir->remap[i]); + } + in_datas = (const void**)remap_dst_datas; + } else { + in_datas = (const void**)out_datas; + } + spa_log_trace_fp(this->log, "%p: output convert %d", this, n_samples); + convert_process(&dir->conv, dst_datas, in_datas, n_samples); + } + + spa_log_trace_fp(this->log, "%d/%d %d/%d %d->%d", this->in_offset, max_in, + this->out_offset, max_out, n_samples, n_out); + + dir = &this->dir[SPA_DIRECTION_INPUT]; + if (SPA_LIKELY(this->in_offset >= max_in || flush_in)) { + /* return input buffers */ + for (i = 0; i < dir->n_ports; i++) { + port = GET_IN_PORT(this, i); + if (port->is_control) + continue; + if (SPA_UNLIKELY((io = port->io) == NULL)) + continue; + spa_log_trace_fp(this->log, "return: input %d %d", port->id, io->buffer_id); + if (!draining) + io->status = SPA_STATUS_NEED_DATA; + } + this->in_offset = 0; + max_in = 0; + res |= SPA_STATUS_NEED_DATA; } - spa_log_trace_fp(this->log, "%p: process result: %d", this, res); + dir = &this->dir[SPA_DIRECTION_OUTPUT]; + if (SPA_LIKELY(n_samples > 0 && (this->out_offset >= max_out || flush_out))) { + /* queue output buffers */ + for (i = 0; i < dir->n_ports; i++) { + port = GET_OUT_PORT(this, i); + if (SPA_UNLIKELY(port->is_monitor || port->is_control)) + continue; + if (SPA_UNLIKELY((io = port->io) == NULL)) + continue; + + if (SPA_UNLIKELY((buf = out_bufs[i]) == NULL)) + continue; + + dequeue_buffer(this, port, buf); + + for (j = 0; j < port->blocks; j++) { + bd = &buf->buf->datas[j]; + bd->chunk->size = this->out_offset * port->stride; + SPA_FLAG_UPDATE(bd->chunk->flags, SPA_CHUNK_FLAG_EMPTY, in_empty); + spa_log_trace_fp(this->log, "out: %d %d %d", this->out_offset, + port->stride, bd->chunk->size); + } + io->status = SPA_STATUS_HAVE_DATA; + io->buffer_id = buf->id; + } + res |= SPA_STATUS_HAVE_DATA; + this->drained = draining; + this->out_offset = 0; + } + else if (n_samples == 0 && this->peaks) { + for (i = 0; i < dir->n_ports; i++) { + port = GET_OUT_PORT(this, i); + if (port->is_monitor || port->is_control) + continue; + if (SPA_UNLIKELY((io = port->io) == NULL)) + continue; + + io->status = SPA_STATUS_HAVE_DATA; + io->buffer_id = SPA_ID_INVALID; + res |= SPA_STATUS_HAVE_DATA; + spa_log_trace_fp(this->log, "%p: no output buffer", this); + } + } + if (resample_update_rate_match(this, resample_passthrough, + max_out - this->out_offset, + max_in - this->in_offset) > 0) + res |= SPA_STATUS_NEED_DATA; return res; } @@ -1261,44 +2740,36 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void static int impl_clear(struct spa_handle *handle) { struct impl *this; + uint32_t i; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; - clean_convert(this); - - spa_handle_clear(this->hnd_merger); - spa_handle_clear(this->hnd_convert_in); - spa_handle_clear(this->hnd_channelmix); - spa_handle_clear(this->hnd_resample); - spa_handle_clear(this->hnd_convert_out); - spa_handle_clear(this->hnd_splitter); + for (i = 0; i < MAX_PORTS; i++) + free(this->dir[SPA_DIRECTION_INPUT].ports[i]); + for (i = 0; i < MAX_PORTS; i++) + free(this->dir[SPA_DIRECTION_OUTPUT].ports[i]); + free(this->empty); + free(this->scratch); + free(this->tmp[0]); + free(this->tmp[1]); + + if (this->resample.free) + resample_free(&this->resample); + if (this->dir[0].conv.free) + convert_free(&this->dir[0].conv); + if (this->dir[1].conv.free) + convert_free(&this->dir[1].conv); return 0; } -extern const struct spa_handle_factory spa_fmtconvert_factory; -extern const struct spa_handle_factory spa_channelmix_factory; -extern const struct spa_handle_factory spa_resample_factory; -extern const struct spa_handle_factory spa_splitter_factory; -extern const struct spa_handle_factory spa_merger_factory; - static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { - size_t size; - - size = sizeof(struct impl); - size += spa_handle_factory_get_size(&spa_merger_factory, params); - size += spa_handle_factory_get_size(&spa_fmtconvert_factory, params); - size += spa_handle_factory_get_size(&spa_channelmix_factory, params); - size += spa_handle_factory_get_size(&spa_resample_factory, params); - size += spa_handle_factory_get_size(&spa_fmtconvert_factory, params); - size += spa_handle_factory_get_size(&spa_splitter_factory, params); - - return size; + return sizeof(struct impl); } static int @@ -1309,8 +2780,7 @@ impl_init(const struct spa_handle_factory *factory, uint32_t n_support) { struct impl *this; - size_t size; - void *iface; + uint32_t i; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); @@ -1324,8 +2794,40 @@ impl_init(const struct spa_handle_factory *factory, spa_log_topic_init(this->log, log_topic); this->cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); - if (this->cpu) - this->max_align = spa_cpu_get_max_align(this->cpu); + if (this->cpu) { + this->cpu_flags = spa_cpu_get_flags(this->cpu); + this->max_align = SPA_MIN(MAX_ALIGN, spa_cpu_get_max_align(this->cpu)); + } + + props_reset(&this->props); + + this->mix.options = CHANNELMIX_OPTION_UPMIX; + this->mix.upmix = CHANNELMIX_UPMIX_PSD; + this->mix.log = this->log; + this->mix.lfe_cutoff = 150.0f; + this->mix.fc_cutoff = 12000.0f; + this->mix.rear_delay = 12.0f; + this->mix.widen = 0.0f; + + for (i = 0; info && i < info->n_items; i++) { + const char *k = info->items[i].key; + const char *s = info->items[i].value; + if (spa_streq(k, "clock.quantum-limit")) + spa_atou32(s, &this->quantum_limit, 0); + else if (spa_streq(k, "resample.peaks")) + this->peaks = spa_atob(s); + else if (spa_streq(k, "factory.mode")) { + if (spa_streq(s, "merge")) + this->direction = SPA_DIRECTION_OUTPUT; + else + this->direction = SPA_DIRECTION_INPUT; + } + else + audioconvert_set_param(this, k, s); + } + + this->dir[SPA_DIRECTION_INPUT].latency = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); + this->dir[SPA_DIRECTION_OUTPUT].latency = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, @@ -1347,67 +2849,21 @@ impl_init(const struct spa_handle_factory *factory, 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->info.params = this->params; - this->info.n_params = 4; - - this->hnd_merger = SPA_PTROFF(this, sizeof(struct impl), struct spa_handle); - spa_handle_factory_init(&spa_merger_factory, - this->hnd_merger, - info, support, n_support); - size = spa_handle_factory_get_size(&spa_merger_factory, info); - - this->hnd_convert_in = SPA_PTROFF(this->hnd_merger, size, struct spa_handle); - spa_handle_factory_init(&spa_fmtconvert_factory, - this->hnd_convert_in, - info, support, n_support); - size = spa_handle_factory_get_size(&spa_fmtconvert_factory, info); - - this->hnd_channelmix = SPA_PTROFF(this->hnd_convert_in, size, struct spa_handle); - spa_handle_factory_init(&spa_channelmix_factory, - this->hnd_channelmix, - info, support, n_support); - size = spa_handle_factory_get_size(&spa_channelmix_factory, info); - - this->hnd_resample = SPA_PTROFF(this->hnd_channelmix, size, struct spa_handle); - spa_handle_factory_init(&spa_resample_factory, - this->hnd_resample, - info, support, n_support); - size = spa_handle_factory_get_size(&spa_resample_factory, info); - - this->hnd_convert_out = SPA_PTROFF(this->hnd_resample, size, struct spa_handle); - spa_handle_factory_init(&spa_fmtconvert_factory, - this->hnd_convert_out, - info, support, n_support); - size = spa_handle_factory_get_size(&spa_fmtconvert_factory, info); - - this->hnd_splitter = SPA_PTROFF(this->hnd_convert_out, size, struct spa_handle); - spa_handle_factory_init(&spa_splitter_factory, - this->hnd_splitter, - info, support, n_support); - - spa_handle_get_interface(this->hnd_merger, SPA_TYPE_INTERFACE_Node, &iface); - this->merger = iface; - spa_handle_get_interface(this->hnd_convert_in, SPA_TYPE_INTERFACE_Node, &iface); - this->convert_in = iface; - spa_handle_get_interface(this->hnd_channelmix, SPA_TYPE_INTERFACE_Node, &iface); - this->channelmix = iface; - spa_handle_get_interface(this->hnd_resample, SPA_TYPE_INTERFACE_Node, &iface); - this->resample = iface; - spa_handle_get_interface(this->hnd_convert_out, SPA_TYPE_INTERFACE_Node, &iface); - this->convert_out = iface; - spa_handle_get_interface(this->hnd_splitter, SPA_TYPE_INTERFACE_Node, &iface); - this->splitter = iface; - - reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_convert, SPA_DIRECTION_OUTPUT, false, NULL); - reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_convert, SPA_DIRECTION_INPUT, false, NULL); - - spa_node_add_listener(this->channelmix, - &this->listener[0], &channelmix_events, this); + this->info.n_params = N_NODE_PARAMS; + + this->volume.cpu_flags = this->cpu_flags; + volume_init(&this->volume); + + this->rate_scale = 1.0; + + reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_convert, SPA_DIRECTION_INPUT, false, false, NULL); + reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_convert, SPA_DIRECTION_OUTPUT, false, false, NULL); return 0; } static const struct spa_interface_info impl_interfaces[] = { - { SPA_TYPE_INTERFACE_Node, }, + {SPA_TYPE_INTERFACE_Node,}, }; static int diff --git a/spa/plugins/audioconvert/benchmark-fmt-ops.c b/spa/plugins/audioconvert/benchmark-fmt-ops.c index 0f26ac4c4c48f08462d3a9d857580a43b5e157e2..491b0730bb08294bb63d1b6c796db42d2a96feb7 100644 --- a/spa/plugins/audioconvert/benchmark-fmt-ops.c +++ b/spa/plugins/audioconvert/benchmark-fmt-ops.c @@ -271,18 +271,18 @@ static void test_s24_32_f32(void) static void test_interleave(void) { - run_test("test_interleave_8", "c", false, true, conv_interleave_8_c); - run_test("test_interleave_16", "c", false, true, conv_interleave_16_c); - run_test("test_interleave_24", "c", false, true, conv_interleave_24_c); - run_test("test_interleave_32", "c", false, true, conv_interleave_32_c); + run_test("test_8d_to_8", "c", false, true, conv_8d_to_8_c); + run_test("test_16d_to_16", "c", false, true, conv_16d_to_16_c); + run_test("test_24d_to_24", "c", false, true, conv_24d_to_24_c); + run_test("test_32d_to_32", "c", false, true, conv_32d_to_32_c); } static void test_deinterleave(void) { - run_test("test_deinterleave_8", "c", true, false, conv_deinterleave_8_c); - run_test("test_deinterleave_16", "c", true, false, conv_deinterleave_16_c); - run_test("test_deinterleave_24", "c", true, false, conv_deinterleave_24_c); - run_test("test_deinterleave_32", "c", true, false, conv_deinterleave_32_c); + run_test("test_8_to_8d", "c", true, false, conv_8_to_8d_c); + run_test("test_16_to_16d", "c", true, false, conv_16_to_16d_c); + run_test("test_24_to_24d", "c", true, false, conv_24_to_24d_c); + run_test("test_32_to_32d", "c", true, false, conv_32_to_32d_c); } static int compare_func(const void *_a, const void *_b) diff --git a/spa/plugins/audioconvert/biquad.c b/spa/plugins/audioconvert/biquad.c index 12d6b2c5056afba6ee717a086f75e845d4157e9b..409b6734a815bdee5b18421c1adc1ec2ad225791 100644 --- a/spa/plugins/audioconvert/biquad.c +++ b/spa/plugins/audioconvert/biquad.c @@ -9,8 +9,6 @@ */ -#include "config.h" - #include <spa/utils/defs.h> #include <math.h> diff --git a/spa/plugins/audioconvert/channelmix-ops-c.c b/spa/plugins/audioconvert/channelmix-ops-c.c index 1a6ff626562d4d29943094594021491d0e8e6a7b..9a28830042ad2f5fd3610304e46f6a9df7359c9d 100644 --- a/spa/plugins/audioconvert/channelmix-ops-c.c +++ b/spa/plugins/audioconvert/channelmix-ops-c.c @@ -177,6 +177,7 @@ void channelmix_f32_2_4_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { + uint32_t i, n_dst = mix->dst_chan; float **d = (float **)dst; const float **s = (const float **)src; const float v0 = mix->matrix[0][0]; @@ -184,10 +185,25 @@ channelmix_f32_2_4_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const float v2 = mix->matrix[2][0]; const float v3 = mix->matrix[3][1]; - vol_c(d[0], s[0], v0, n_samples); - vol_c(d[1], s[1], v1, n_samples); - vol_c(d[2], s[0], v2, n_samples); - vol_c(d[3], s[1], v3, n_samples); + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + for (i = 0; i < n_dst; i++) + clear_c(d[i], n_samples); + } + else { + vol_c(d[0], s[0], v0, n_samples); + vol_c(d[1], s[1], v1, n_samples); + if (mix->upmix != CHANNELMIX_UPMIX_PSD) { + vol_c(d[2], s[0], v2, n_samples); + vol_c(d[3], s[1], v3, n_samples); + } else { + sub_c(d[2], s[0], s[1], n_samples); + + delay_convolve_run(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, + mix->taps, mix->n_taps, d[3], d[2], -v3, n_samples); + delay_convolve_run(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, + mix->taps, mix->n_taps, d[2], d[2], v2, n_samples); + } + } } #define MASK_3_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE) @@ -294,6 +310,32 @@ channelmix_f32_2_7p1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], } } +/* FL+FR+FC+LFE -> FL+FR */ +void +channelmix_f32_3p1_2_c(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + uint32_t n; + float **d = (float **) dst; + const float **s = (const float **) src; + const float v0 = mix->matrix[0][0]; + const float v1 = mix->matrix[1][1]; + const float clev = (mix->matrix[0][2] + mix->matrix[1][2]) * 0.5f; + const float llev = (mix->matrix[0][3] + mix->matrix[1][3]) * 0.5f; + + if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { + clear_c(d[0], n_samples); + clear_c(d[1], n_samples); + } + else { + for (n = 0; n < n_samples; n++) { + const float ctr = clev * s[2][n] + llev * s[3][n]; + d[0][n] = s[0][n] * v0 + ctr; + d[1][n] = s[1][n] * v1 + ctr; + } + } +} + /* FL+FR+FC+LFE+SL+SR -> FL+FR */ void channelmix_f32_5p1_2_c(struct channelmix *mix, void * SPA_RESTRICT dst[], @@ -356,13 +398,9 @@ void channelmix_f32_5p1_4_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { - uint32_t i, n, n_dst = mix->dst_chan; + uint32_t i, n_dst = mix->dst_chan; float **d = (float **) dst; const float **s = (const float **) src; - const float clev = mix->matrix[0][2]; - const float llev = mix->matrix[0][3]; - const float v0 = mix->matrix[0][0]; - const float v1 = mix->matrix[1][1]; const float v4 = mix->matrix[2][4]; const float v5 = mix->matrix[3][5]; @@ -371,11 +409,8 @@ channelmix_f32_5p1_4_c(struct channelmix *mix, void * SPA_RESTRICT dst[], clear_c(d[i], n_samples); } else { - for (n = 0; n < n_samples; n++) { - const float ctr = s[2][n] * clev + s[3][n] * llev; - d[0][n] = s[0][n] * v0 + ctr; - d[1][n] = s[1][n] * v1 + ctr; - } + channelmix_f32_3p1_2_c(mix, dst, src, n_samples); + vol_c(d[2], s[4], v4, n_samples); vol_c(d[3], s[5], v5, n_samples); } diff --git a/spa/plugins/audioconvert/channelmix-ops-sse.c b/spa/plugins/audioconvert/channelmix-ops-sse.c index f3ef7c06eb27139aa7c1171d3968bb4abcaf163f..37a02e22648b102a5b16f91c058f6c5b9426cfed 100644 --- a/spa/plugins/audioconvert/channelmix-ops-sse.c +++ b/spa/plugins/audioconvert/channelmix-ops-sse.c @@ -26,113 +26,104 @@ #include <xmmintrin.h> -void channelmix_copy_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], - const void * SPA_RESTRICT src[], uint32_t n_samples) +static inline void clear_sse(float *d, uint32_t n_samples) { - uint32_t i, n, unrolled, n_dst = mix->dst_chan; - float **d = (float **)dst; - const float **s = (const float **)src; + memset(d, 0, n_samples * sizeof(float)); +} - if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { - for (i = 0; i < n_dst; i++) - memset(d[i], 0, n_samples * sizeof(float)); - } - else if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_IDENTITY)) { - for (i = 0; i < n_dst; i++) - spa_memcpy(d[i], s[i], n_samples * sizeof(float)); - } - else { - for (i = 0; i < n_dst; i++) { - float *di = d[i]; - const float *si = s[i]; - __m128 t[4]; - const __m128 vol = _mm_set1_ps(mix->matrix[i][i]); +static inline void copy_sse(float *d, const float *s, uint32_t n_samples) +{ + spa_memcpy(d, s, n_samples * sizeof(float)); +} - if (SPA_IS_ALIGNED(di, 16) && - SPA_IS_ALIGNED(si, 16)) - unrolled = n_samples & ~15; - else - unrolled = 0; +static inline void vol_sse(float *d, const float *s, float vol, uint32_t n_samples) +{ + uint32_t n, unrolled; + if (vol == 0.0f) { + clear_sse(d, n_samples); + } else if (vol == 1.0f) { + copy_sse(d, s, n_samples); + } else { + __m128 t[4]; + const __m128 v = _mm_set1_ps(vol); + + if (SPA_IS_ALIGNED(d, 16) && + SPA_IS_ALIGNED(s, 16)) + unrolled = n_samples & ~15; + else + unrolled = 0; - for(n = 0; n < unrolled; n += 16) { - t[0] = _mm_load_ps(&si[n]); - t[1] = _mm_load_ps(&si[n+4]); - t[2] = _mm_load_ps(&si[n+8]); - t[3] = _mm_load_ps(&si[n+12]); - _mm_store_ps(&di[n], _mm_mul_ps(t[0], vol)); - _mm_store_ps(&di[n+4], _mm_mul_ps(t[1], vol)); - _mm_store_ps(&di[n+8], _mm_mul_ps(t[2], vol)); - _mm_store_ps(&di[n+12], _mm_mul_ps(t[3], vol)); - } - for(; n < n_samples; n++) - _mm_store_ss(&di[n], _mm_mul_ss(_mm_load_ss(&si[n]), vol)); + for(n = 0; n < unrolled; n += 16) { + t[0] = _mm_load_ps(&s[n]); + t[1] = _mm_load_ps(&s[n+4]); + t[2] = _mm_load_ps(&s[n+8]); + t[3] = _mm_load_ps(&s[n+12]); + _mm_store_ps(&d[n], _mm_mul_ps(t[0], v)); + _mm_store_ps(&d[n+4], _mm_mul_ps(t[1], v)); + _mm_store_ps(&d[n+8], _mm_mul_ps(t[2], v)); + _mm_store_ps(&d[n+12], _mm_mul_ps(t[3], v)); } + for(; n < n_samples; n++) + _mm_store_ss(&d[n], _mm_mul_ss(_mm_load_ss(&s[n]), v)); } } -void -channelmix_f32_2_4_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], +void channelmix_copy_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { - uint32_t i, n, unrolled, n_dst = mix->dst_chan; + uint32_t i, n_dst = mix->dst_chan; float **d = (float **)dst; const float **s = (const float **)src; - const float m00 = mix->matrix[0][0]; - const float m11 = mix->matrix[1][1]; - __m128 in; - const float *sFL = s[0], *sFR = s[1]; - float *dFL = d[0], *dFR = d[1], *dRL = d[2], *dRR = d[3]; + for (i = 0; i < n_dst; i++) + vol_sse(d[i], s[i], mix->matrix[i][i], n_samples); +} - if (SPA_IS_ALIGNED(sFL, 16) && - SPA_IS_ALIGNED(sFR, 16) && - SPA_IS_ALIGNED(dFL, 16) && - SPA_IS_ALIGNED(dFR, 16) && - SPA_IS_ALIGNED(dRL, 16) && - SPA_IS_ALIGNED(dRR, 16)) - unrolled = n_samples & ~3; - else - unrolled = 0; +/* FL+FR+FC+LFE -> FL+FR */ +void +channelmix_f32_3p1_2_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], + const void * SPA_RESTRICT src[], uint32_t n_samples) +{ + float **d = (float **) dst; + const float **s = (const float **) src; + const float m0 = mix->matrix[0][0]; + const float m1 = mix->matrix[1][1]; + const float m2 = (mix->matrix[0][2] + mix->matrix[1][2]) * 0.5f; + const float m3 = (mix->matrix[0][3] + mix->matrix[1][3]) * 0.5f; - if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { - for (i = 0; i < n_dst; i++) - memset(d[i], 0, n_samples * sizeof(float)); - } - else if (m00 == 1.0f && m11 == 1.0f) { - for(n = 0; n < unrolled; n += 4) { - in = _mm_load_ps(&sFL[n]); - _mm_store_ps(&dFL[n], in); - _mm_store_ps(&dRL[n], in); - in = _mm_load_ps(&sFR[n]); - _mm_store_ps(&dFR[n], in); - _mm_store_ps(&dRR[n], in); - } - for(; n < n_samples; n++) { - in = _mm_load_ss(&sFL[n]); - _mm_store_ss(&dFL[n], in); - _mm_store_ss(&dRL[n], in); - in = _mm_load_ss(&sFR[n]); - _mm_store_ss(&dFR[n], in); - _mm_store_ss(&dRR[n], in); - } + if (m0 == 0.0f && m1 == 0.0f && m2 == 0.0f && m3 == 0.0f) { + clear_sse(d[0], n_samples); + clear_sse(d[1], n_samples); } else { - const __m128 v0 = _mm_set1_ps(m00); - const __m128 v1 = _mm_set1_ps(m11); + uint32_t n, unrolled; + const __m128 v0 = _mm_set1_ps(m0); + const __m128 v1 = _mm_set1_ps(m1); + const __m128 clev = _mm_set1_ps(m2); + const __m128 llev = _mm_set1_ps(m3); + __m128 ctr; + + if (SPA_IS_ALIGNED(s[0], 16) && + SPA_IS_ALIGNED(s[1], 16) && + SPA_IS_ALIGNED(s[2], 16) && + SPA_IS_ALIGNED(s[3], 16) && + SPA_IS_ALIGNED(d[0], 16) && + SPA_IS_ALIGNED(d[1], 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + for(n = 0; n < unrolled; n += 4) { - in = _mm_mul_ps(_mm_load_ps(&sFL[n]), v0); - _mm_store_ps(&dFL[n], in); - _mm_store_ps(&dRL[n], in); - in = _mm_mul_ps(_mm_load_ps(&sFR[n]), v1); - _mm_store_ps(&dFR[n], in); - _mm_store_ps(&dRR[n], in); + ctr = _mm_add_ps( + _mm_mul_ps(_mm_load_ps(&s[2][n]), clev), + _mm_mul_ps(_mm_load_ps(&s[3][n]), llev)); + _mm_store_ps(&d[0][n], _mm_add_ps(_mm_mul_ps(_mm_load_ps(&s[0][n]), v0), ctr)); + _mm_store_ps(&d[1][n], _mm_add_ps(_mm_mul_ps(_mm_load_ps(&s[1][n]), v1), ctr)); } for(; n < n_samples; n++) { - in = _mm_mul_ss(_mm_load_ss(&sFL[n]), v0); - _mm_store_ss(&dFL[n], in); - _mm_store_ss(&dRL[n], in); - in = _mm_mul_ss(_mm_load_ss(&sFR[n]), v1); - _mm_store_ss(&dFR[n], in); - _mm_store_ss(&dRR[n], in); + ctr = _mm_add_ss(_mm_mul_ss(_mm_load_ss(&s[2][n]), clev), + _mm_mul_ss(_mm_load_ss(&s[3][n]), llev)); + _mm_store_ss(&d[0][n], _mm_add_ss(_mm_mul_ss(_mm_load_ss(&s[0][n]), v0), ctr)); + _mm_store_ss(&d[1][n], _mm_add_ss(_mm_mul_ss(_mm_load_ss(&s[1][n]), v1), ctr)); } } } @@ -152,77 +143,49 @@ channelmix_f32_5p1_2_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], const __m128 slev0 = _mm_set1_ps(mix->matrix[0][4]); const __m128 slev1 = _mm_set1_ps(mix->matrix[1][5]); __m128 in, ctr; - const float *sFL = s[0], *sFR = s[1], *sFC = s[2], *sLFE = s[3], *sSL = s[4], *sSR = s[5]; - float *dFL = d[0], *dFR = d[1]; - if (SPA_IS_ALIGNED(sFL, 16) && - SPA_IS_ALIGNED(sFR, 16) && - SPA_IS_ALIGNED(sFC, 16) && - SPA_IS_ALIGNED(sLFE, 16) && - SPA_IS_ALIGNED(sSL, 16) && - SPA_IS_ALIGNED(sSR, 16) && - SPA_IS_ALIGNED(dFL, 16) && - SPA_IS_ALIGNED(dFR, 16)) + if (SPA_IS_ALIGNED(s[0], 16) && + SPA_IS_ALIGNED(s[1], 16) && + SPA_IS_ALIGNED(s[2], 16) && + SPA_IS_ALIGNED(s[3], 16) && + SPA_IS_ALIGNED(s[4], 16) && + SPA_IS_ALIGNED(s[5], 16) && + SPA_IS_ALIGNED(d[0], 16) && + SPA_IS_ALIGNED(d[1], 16)) unrolled = n_samples & ~3; else unrolled = 0; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { - memset(dFL, 0, n_samples * sizeof(float)); - memset(dFR, 0, n_samples * sizeof(float)); - } - else if (m00 == 1.0f && m11 == 1.0f) { - for(n = 0; n < unrolled; n += 4) { - ctr = _mm_mul_ps(_mm_load_ps(&sFC[n]), clev); - ctr = _mm_add_ps(ctr, _mm_mul_ps(_mm_load_ps(&sLFE[n]), llev)); - in = _mm_mul_ps(_mm_load_ps(&sSL[n]), slev0); - in = _mm_add_ps(in, ctr); - in = _mm_add_ps(in, _mm_load_ps(&sFL[n])); - _mm_store_ps(&dFL[n], in); - in = _mm_mul_ps(_mm_load_ps(&sSR[n]), slev1); - in = _mm_add_ps(in, ctr); - in = _mm_add_ps(in, _mm_load_ps(&sFR[n])); - _mm_store_ps(&dFR[n], in); - } - for(; n < n_samples; n++) { - ctr = _mm_mul_ss(_mm_load_ss(&sFC[n]), clev); - ctr = _mm_add_ss(ctr, _mm_mul_ss(_mm_load_ss(&sLFE[n]), llev)); - in = _mm_mul_ss(_mm_load_ss(&sSL[n]), slev0); - in = _mm_add_ss(in, ctr); - in = _mm_add_ss(in, _mm_load_ss(&sFL[n])); - _mm_store_ss(&dFL[n], in); - in = _mm_mul_ss(_mm_load_ss(&sSR[n]), slev1); - in = _mm_add_ss(in, ctr); - in = _mm_add_ss(in, _mm_load_ss(&sFR[n])); - _mm_store_ss(&dFR[n], in); - } + clear_sse(d[0], n_samples); + clear_sse(d[1], n_samples); } else { const __m128 v0 = _mm_set1_ps(m00); const __m128 v1 = _mm_set1_ps(m11); for(n = 0; n < unrolled; n += 4) { - ctr = _mm_mul_ps(_mm_load_ps(&sFC[n]), clev); - ctr = _mm_add_ps(ctr, _mm_mul_ps(_mm_load_ps(&sLFE[n]), llev)); - in = _mm_mul_ps(_mm_load_ps(&sSL[n]), slev0); + ctr = _mm_add_ps(_mm_mul_ps(_mm_load_ps(&s[2][n]), clev), + _mm_mul_ps(_mm_load_ps(&s[3][n]), llev)); + in = _mm_mul_ps(_mm_load_ps(&s[4][n]), slev0); in = _mm_add_ps(in, ctr); - in = _mm_add_ps(in, _mm_mul_ps(_mm_load_ps(&sFL[n]), v0)); - _mm_store_ps(&dFL[n], in); - in = _mm_mul_ps(_mm_load_ps(&sSR[n]), slev1); + in = _mm_add_ps(in, _mm_mul_ps(_mm_load_ps(&s[0][n]), v0)); + _mm_store_ps(&d[0][n], in); + in = _mm_mul_ps(_mm_load_ps(&s[5][n]), slev1); in = _mm_add_ps(in, ctr); - in = _mm_add_ps(in, _mm_mul_ps(_mm_load_ps(&sFR[n]), v1)); - _mm_store_ps(&dFR[n], in); + in = _mm_add_ps(in, _mm_mul_ps(_mm_load_ps(&s[1][n]), v1)); + _mm_store_ps(&d[1][n], in); } for(; n < n_samples; n++) { - ctr = _mm_mul_ss(_mm_load_ss(&sFC[n]), clev); - ctr = _mm_add_ss(ctr, _mm_mul_ss(_mm_load_ss(&sLFE[n]), llev)); - in = _mm_mul_ss(_mm_load_ss(&sSL[n]), slev0); + ctr = _mm_mul_ss(_mm_load_ss(&s[2][n]), clev); + ctr = _mm_add_ss(ctr, _mm_mul_ss(_mm_load_ss(&s[3][n]), llev)); + in = _mm_mul_ss(_mm_load_ss(&s[4][n]), slev0); in = _mm_add_ss(in, ctr); - in = _mm_add_ss(in, _mm_mul_ss(_mm_load_ss(&sFL[n]), v0)); - _mm_store_ss(&dFL[n], in); - in = _mm_mul_ss(_mm_load_ss(&sSR[n]), slev1); + in = _mm_add_ss(in, _mm_mul_ss(_mm_load_ss(&s[0][n]), v0)); + _mm_store_ss(&d[0][n], in); + in = _mm_mul_ss(_mm_load_ss(&s[5][n]), slev1); in = _mm_add_ss(in, ctr); - in = _mm_add_ss(in, _mm_mul_ss(_mm_load_ss(&sFR[n]), v1)); - _mm_store_ss(&dFR[n], in); + in = _mm_add_ss(in, _mm_mul_ss(_mm_load_ss(&s[1][n]), v1)); + _mm_store_ss(&d[1][n], in); } } } @@ -235,73 +198,51 @@ channelmix_f32_5p1_3p1_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], uint32_t i, n, unrolled, n_dst = mix->dst_chan; float **d = (float **) dst; const float **s = (const float **) src; - const __m128 v0 = _mm_set1_ps(mix->matrix[0][0]); - const __m128 v1 = _mm_set1_ps(mix->matrix[1][1]); - const __m128 slev0 = _mm_set1_ps(mix->matrix[0][4]); - const __m128 slev1 = _mm_set1_ps(mix->matrix[1][5]); - const __m128 v2 = _mm_set1_ps(mix->matrix[2][2]); - const __m128 v3 = _mm_set1_ps(mix->matrix[3][3]); - __m128 avg[2]; - const float *sFL = s[0], *sFR = s[1], *sFC = s[2], *sLFE = s[3], *sSL = s[4], *sSR = s[5]; - float *dFL = d[0], *dFR = d[1], *dFC = d[2], *dLFE = d[3]; - if (SPA_IS_ALIGNED(sFL, 16) && - SPA_IS_ALIGNED(sFR, 16) && - SPA_IS_ALIGNED(sFC, 16) && - SPA_IS_ALIGNED(sLFE, 16) && - SPA_IS_ALIGNED(sSL, 16) && - SPA_IS_ALIGNED(sSR, 16) && - SPA_IS_ALIGNED(dFL, 16) && - SPA_IS_ALIGNED(dFR, 16) && - SPA_IS_ALIGNED(dFC, 16) && - SPA_IS_ALIGNED(dLFE, 16)) - unrolled = n_samples & ~7; + if (SPA_IS_ALIGNED(s[0], 16) && + SPA_IS_ALIGNED(s[1], 16) && + SPA_IS_ALIGNED(s[2], 16) && + SPA_IS_ALIGNED(s[3], 16) && + SPA_IS_ALIGNED(s[4], 16) && + SPA_IS_ALIGNED(s[5], 16) && + SPA_IS_ALIGNED(d[0], 16) && + SPA_IS_ALIGNED(d[1], 16) && + SPA_IS_ALIGNED(d[2], 16) && + SPA_IS_ALIGNED(d[3], 16)) + unrolled = n_samples & ~3; else unrolled = 0; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { for (i = 0; i < n_dst; i++) - memset(d[i], 0, n_samples * sizeof(float)); + clear_sse(d[i], n_samples); } else { - for(n = 0; n < unrolled; n += 8) { - avg[0] = _mm_add_ps( - _mm_mul_ps(_mm_load_ps(&sFL[n]), v0), - _mm_mul_ps(_mm_load_ps(&sSL[n]), slev0)); - avg[1] = _mm_add_ps( - _mm_mul_ps(_mm_load_ps(&sFL[n+4]), v0), - _mm_mul_ps(_mm_load_ps(&sSL[n+4]), slev0)); - _mm_store_ps(&dFL[n], avg[0]); - _mm_store_ps(&dFL[n+4], avg[1]); + const __m128 v0 = _mm_set1_ps(mix->matrix[0][0]); + const __m128 v1 = _mm_set1_ps(mix->matrix[1][1]); + const __m128 slev0 = _mm_set1_ps(mix->matrix[0][4]); + const __m128 slev1 = _mm_set1_ps(mix->matrix[1][5]); - avg[0] = _mm_add_ps( - _mm_mul_ps(_mm_load_ps(&sFR[n]), v1), - _mm_mul_ps(_mm_load_ps(&sSR[n]), slev1)); - avg[1] = _mm_add_ps( - _mm_mul_ps(_mm_load_ps(&sFR[n+4]), v1), - _mm_mul_ps(_mm_load_ps(&sSR[n+4]), slev1)); - _mm_store_ps(&dFR[n], avg[0]); - _mm_store_ps(&dFR[n+4], avg[1]); + for(n = 0; n < unrolled; n += 4) { + _mm_store_ps(&d[0][n], _mm_add_ps( + _mm_mul_ps(_mm_load_ps(&s[0][n]), v0), + _mm_mul_ps(_mm_load_ps(&s[4][n]), slev0))); - _mm_store_ps(&dFC[n], _mm_mul_ps(_mm_load_ps(&sFC[n]), v2)); - _mm_store_ps(&dFC[n+4], _mm_mul_ps(_mm_load_ps(&sFC[n+4]), v2)); - _mm_store_ps(&dLFE[n], _mm_mul_ps(_mm_load_ps(&sLFE[n]), v3)); - _mm_store_ps(&dLFE[n+4], _mm_mul_ps(_mm_load_ps(&sLFE[n+4]), v3)); + _mm_store_ps(&d[1][n], _mm_add_ps( + _mm_mul_ps(_mm_load_ps(&s[1][n]), v1), + _mm_mul_ps(_mm_load_ps(&s[5][n]), slev1))); } for(; n < n_samples; n++) { - avg[0] = _mm_add_ss( - _mm_mul_ss(_mm_load_ss(&sFL[n]), v0), - _mm_mul_ss(_mm_load_ss(&sSL[n]), slev0)); - _mm_store_ss(&dFL[n], avg[0]); - - avg[0] = _mm_add_ss( - _mm_mul_ss(_mm_load_ss(&sFR[n]), v1), - _mm_mul_ss(_mm_load_ss(&sSR[n]), slev1)); - _mm_store_ss(&dFR[n], avg[0]); + _mm_store_ss(&d[0][n], _mm_add_ss( + _mm_mul_ss(_mm_load_ss(&s[0][n]), v0), + _mm_mul_ss(_mm_load_ss(&s[4][n]), slev0))); - _mm_store_ss(&dFC[n], _mm_mul_ss(_mm_load_ss(&sFC[n]), v2)); - _mm_store_ss(&dLFE[n], _mm_mul_ss(_mm_load_ss(&sLFE[n]), v3)); + _mm_store_ss(&d[1][n], _mm_add_ss( + _mm_mul_ss(_mm_load_ss(&s[1][n]), v1), + _mm_mul_ss(_mm_load_ss(&s[5][n]), slev1))); } + vol_sse(d[2], s[2], mix->matrix[2][2], n_samples); + vol_sse(d[3], s[3], mix->matrix[3][3], n_samples); } } @@ -310,76 +251,20 @@ void channelmix_f32_5p1_4_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { - uint32_t i, n, unrolled, n_dst = mix->dst_chan; + uint32_t i, n_dst = mix->dst_chan; float **d = (float **) dst; const float **s = (const float **) src; - const __m128 clev = _mm_set1_ps(mix->matrix[0][2]); - const __m128 llev = _mm_set1_ps(mix->matrix[0][3]); - const float m00 = mix->matrix[0][0]; - const float m11 = mix->matrix[1][1]; - const float m24 = mix->matrix[2][4]; - const float m35 = mix->matrix[3][5]; - __m128 ctr; - const float *sFL = s[0], *sFR = s[1], *sFC = s[2], *sLFE = s[3], *sSL = s[4], *sSR = s[5]; - float *dFL = d[0], *dFR = d[1], *dRL = d[2], *dRR = d[3]; - - if (SPA_IS_ALIGNED(sFL, 16) && - SPA_IS_ALIGNED(sFR, 16) && - SPA_IS_ALIGNED(sFC, 16) && - SPA_IS_ALIGNED(sLFE, 16) && - SPA_IS_ALIGNED(sSL, 16) && - SPA_IS_ALIGNED(sSR, 16) && - SPA_IS_ALIGNED(dFL, 16) && - SPA_IS_ALIGNED(dFR, 16) && - SPA_IS_ALIGNED(dRL, 16) && - SPA_IS_ALIGNED(dRR, 16)) - unrolled = n_samples & ~3; - else - unrolled = 0; + const float v4 = mix->matrix[2][4]; + const float v5 = mix->matrix[3][5]; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { for (i = 0; i < n_dst; i++) - memset(d[i], 0, n_samples * sizeof(float)); - } - else if (m00 == 1.0f && m11 == 1.0f && m24 == 1.0f && m35 == 1.0f) { - for(n = 0; n < unrolled; n += 4) { - ctr = _mm_mul_ps(_mm_load_ps(&sFC[n]), clev); - ctr = _mm_add_ps(ctr, _mm_mul_ps(_mm_load_ps(&sLFE[n]), llev)); - _mm_store_ps(&dFL[n], _mm_add_ps(_mm_load_ps(&sFL[n]), ctr)); - _mm_store_ps(&dFR[n], _mm_add_ps(_mm_load_ps(&sFR[n]), ctr)); - _mm_store_ps(&dRL[n], _mm_load_ps(&sSL[n])); - _mm_store_ps(&dRR[n], _mm_load_ps(&sSR[n])); - } - for(; n < n_samples; n++) { - ctr = _mm_mul_ss(_mm_load_ss(&sFC[n]), clev); - ctr = _mm_add_ss(ctr, _mm_mul_ss(_mm_load_ss(&sLFE[n]), llev)); - _mm_store_ss(&dFL[n], _mm_add_ss(_mm_load_ss(&sFL[n]), ctr)); - _mm_store_ss(&dFR[n], _mm_add_ss(_mm_load_ss(&sFR[n]), ctr)); - _mm_store_ss(&dRL[n], _mm_load_ss(&sSL[n])); - _mm_store_ss(&dRR[n], _mm_load_ss(&sSR[n])); - } + clear_sse(d[i], n_samples); } else { - const __m128 v0 = _mm_set1_ps(m00); - const __m128 v1 = _mm_set1_ps(m11); - const __m128 v4 = _mm_set1_ps(m24); - const __m128 v5 = _mm_set1_ps(m35); + channelmix_f32_3p1_2_sse(mix, dst, src, n_samples); - for(n = 0; n < unrolled; n += 4) { - ctr = _mm_mul_ps(_mm_load_ps(&sFC[n]), clev); - ctr = _mm_add_ps(ctr, _mm_mul_ps(_mm_load_ps(&sLFE[n]), llev)); - _mm_store_ps(&dFL[n], _mm_mul_ps(_mm_add_ps(_mm_load_ps(&sFL[n]), ctr), v0)); - _mm_store_ps(&dFR[n], _mm_mul_ps(_mm_add_ps(_mm_load_ps(&sFR[n]), ctr), v1)); - _mm_store_ps(&dRL[n], _mm_mul_ps(_mm_load_ps(&sSL[n]), v4)); - _mm_store_ps(&dRR[n], _mm_mul_ps(_mm_load_ps(&sSR[n]), v5)); - } - for(; n < n_samples; n++) { - ctr = _mm_mul_ss(_mm_load_ss(&sFC[n]), clev); - ctr = _mm_add_ss(ctr, _mm_mul_ss(_mm_load_ss(&sLFE[n]), llev)); - _mm_store_ss(&dFL[n], _mm_mul_ss(_mm_add_ss(_mm_load_ss(&sFL[n]), ctr), v0)); - _mm_store_ss(&dFR[n], _mm_mul_ss(_mm_add_ss(_mm_load_ss(&sFR[n]), ctr), v1)); - _mm_store_ss(&dRL[n], _mm_mul_ss(_mm_load_ss(&sSL[n]), v4)); - _mm_store_ss(&dRR[n], _mm_mul_ss(_mm_load_ss(&sSR[n]), v5)); - } + vol_sse(d[2], s[4], v4, n_samples); + vol_sse(d[3], s[5], v5, n_samples); } } diff --git a/spa/plugins/audioconvert/channelmix-ops.c b/spa/plugins/audioconvert/channelmix-ops.c index 8b6abf0d8eb041fa7c1e9c6c814572121af2d28d..54e094fe679871efd4297b4158c0e3430bc1bbfb 100644 --- a/spa/plugins/audioconvert/channelmix-ops.c +++ b/spa/plugins/audioconvert/channelmix-ops.c @@ -37,9 +37,6 @@ #include "channelmix-ops.h" #include "hilbert.h" -#undef SPA_LOG_TOPIC_DEFAULT -#define SPA_LOG_TOPIC_DEFAULT log_topic -struct spa_log_topic *log_topic = &SPA_LOG_TOPIC(0, "spa.channelmix"); #define _M(ch) (1UL << SPA_AUDIO_CHANNEL_ ## ch) #define MASK_MONO _M(FC)|_M(MONO)|_M(UNKNOWN) @@ -55,6 +52,9 @@ struct spa_log_topic *log_topic = &SPA_LOG_TOPIC(0, "spa.channelmix"); typedef void (*channelmix_func_t) (struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples); +#define MAKE(sc,sm,dc,dm,func,...) \ + { sc, sm, dc, dm, func, #func, __VA_ARGS__ } + static const struct channelmix_info { uint32_t src_chan; uint64_t src_mask; @@ -62,50 +62,53 @@ static const struct channelmix_info { uint64_t dst_mask; channelmix_func_t process; - uint32_t cpu_flags; const char *name; + + uint32_t cpu_flags; } channelmix_table[] = { #if defined (HAVE_SSE) - { 2, MASK_MONO, 2, MASK_MONO, channelmix_copy_sse, SPA_CPU_FLAG_SSE, "copy_sse" }, - { 2, MASK_STEREO, 2, MASK_STEREO, channelmix_copy_sse, SPA_CPU_FLAG_SSE, "copy_sse" }, - { EQ, 0, EQ, 0, channelmix_copy_sse, SPA_CPU_FLAG_SSE, "copy_sse" }, + MAKE(2, MASK_MONO, 2, MASK_MONO, channelmix_copy_sse, SPA_CPU_FLAG_SSE), + MAKE(2, MASK_STEREO, 2, MASK_STEREO, channelmix_copy_sse, SPA_CPU_FLAG_SSE), + MAKE(EQ, 0, EQ, 0, channelmix_copy_sse, SPA_CPU_FLAG_SSE), #endif - { 2, MASK_MONO, 2, MASK_MONO, channelmix_copy_c, 0, "copy_c" }, - { 2, MASK_STEREO, 2, MASK_STEREO, channelmix_copy_c, 0, "copy_c" }, - { EQ, 0, EQ, 0, channelmix_copy_c, 0 }, - - { 1, MASK_MONO, 2, MASK_STEREO, channelmix_f32_1_2_c, 0, "f32_1_2_c" }, - { 2, MASK_STEREO, 1, MASK_MONO, channelmix_f32_2_1_c, 0, "f32_2_1_c" }, - { 4, MASK_QUAD, 1, MASK_MONO, channelmix_f32_4_1_c, 0, "f32_4_1_c" }, - { 4, MASK_3_1, 1, MASK_MONO, channelmix_f32_4_1_c, 0, "f32_4_1_c" }, + MAKE(2, MASK_MONO, 2, MASK_MONO, channelmix_copy_c), + MAKE(2, MASK_STEREO, 2, MASK_STEREO, channelmix_copy_c), + MAKE(EQ, 0, EQ, 0, channelmix_copy_c), + + MAKE(1, MASK_MONO, 2, MASK_STEREO, channelmix_f32_1_2_c), + MAKE(2, MASK_STEREO, 1, MASK_MONO, channelmix_f32_2_1_c), + MAKE(4, MASK_QUAD, 1, MASK_MONO, channelmix_f32_4_1_c), + MAKE(4, MASK_3_1, 1, MASK_MONO, channelmix_f32_4_1_c), + MAKE(2, MASK_STEREO, 4, MASK_QUAD, channelmix_f32_2_4_c), + MAKE(2, MASK_STEREO, 4, MASK_3_1, channelmix_f32_2_3p1_c), + MAKE(2, MASK_STEREO, 6, MASK_5_1, channelmix_f32_2_5p1_c), + MAKE(2, MASK_STEREO, 8, MASK_7_1, channelmix_f32_2_7p1_c), #if defined (HAVE_SSE) - { 2, MASK_STEREO, 4, MASK_QUAD, channelmix_f32_2_4_sse, SPA_CPU_FLAG_SSE, "f32_2_4_sse" }, + MAKE(4, MASK_3_1, 2, MASK_STEREO, channelmix_f32_3p1_2_sse, SPA_CPU_FLAG_SSE), #endif - { 2, MASK_STEREO, 4, MASK_QUAD, channelmix_f32_2_4_c, 0, "f32_2_4_c" }, - { 2, MASK_STEREO, 4, MASK_3_1, channelmix_f32_2_3p1_c, 0, "f32_2_3p1_c" }, - { 2, MASK_STEREO, 6, MASK_5_1, channelmix_f32_2_5p1_c, 0, "f32_2_5p1_c" }, - { 2, MASK_STEREO, 8, MASK_7_1, channelmix_f32_2_7p1_c, 0, "f32_2_7p1_c" }, + MAKE(4, MASK_3_1, 2, MASK_STEREO, channelmix_f32_3p1_2_c), #if defined (HAVE_SSE) - { 6, MASK_5_1, 2, MASK_STEREO, channelmix_f32_5p1_2_sse, SPA_CPU_FLAG_SSE, "f32_5p1_2_sse" }, + MAKE(6, MASK_5_1, 2, MASK_STEREO, channelmix_f32_5p1_2_sse, SPA_CPU_FLAG_SSE), #endif - { 6, MASK_5_1, 2, MASK_STEREO, channelmix_f32_5p1_2_c, 0, "f32_5p1_2_c" }, + MAKE(6, MASK_5_1, 2, MASK_STEREO, channelmix_f32_5p1_2_c), #if defined (HAVE_SSE) - { 6, MASK_5_1, 4, MASK_QUAD, channelmix_f32_5p1_4_sse, SPA_CPU_FLAG_SSE, "f32_5p1_4_sse" }, + MAKE(6, MASK_5_1, 4, MASK_QUAD, channelmix_f32_5p1_4_sse, SPA_CPU_FLAG_SSE), #endif - { 6, MASK_5_1, 4, MASK_QUAD, channelmix_f32_5p1_4_c, 0, "f32_5p1_4_c" }, + MAKE(6, MASK_5_1, 4, MASK_QUAD, channelmix_f32_5p1_4_c), #if defined (HAVE_SSE) - { 6, MASK_5_1, 4, MASK_3_1, channelmix_f32_5p1_3p1_sse, SPA_CPU_FLAG_SSE, "f32_5p1_3p1_sse" }, + MAKE(6, MASK_5_1, 4, MASK_3_1, channelmix_f32_5p1_3p1_sse, SPA_CPU_FLAG_SSE), #endif - { 6, MASK_5_1, 4, MASK_3_1, channelmix_f32_5p1_3p1_c, 0, "f32_5p1_3p1_c" }, + MAKE(6, MASK_5_1, 4, MASK_3_1, channelmix_f32_5p1_3p1_c), - { 8, MASK_7_1, 2, MASK_STEREO, channelmix_f32_7p1_2_c, 0, "f32_7p1_2_c" }, - { 8, MASK_7_1, 4, MASK_QUAD, channelmix_f32_7p1_4_c, 0, "f32_7p1_4_c" }, - { 8, MASK_7_1, 4, MASK_3_1, channelmix_f32_7p1_3p1_c, 0, "f32_7p1_3p1_c" }, + MAKE(8, MASK_7_1, 2, MASK_STEREO, channelmix_f32_7p1_2_c), + MAKE(8, MASK_7_1, 4, MASK_QUAD, channelmix_f32_7p1_4_c), + MAKE(8, MASK_7_1, 4, MASK_3_1, channelmix_f32_7p1_3p1_c), - { ANY, 0, ANY, 0, channelmix_f32_n_m_c, 0, "f32_n_m_c" }, + MAKE(ANY, 0, ANY, 0, channelmix_f32_n_m_c), }; +#undef MAKE #define MATCH_CHAN(a,b) ((a) == ANY || (a) == (b)) #define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) @@ -197,6 +200,7 @@ static int make_matrix(struct channelmix *mix) matrix[i][i]= 1.0f; } src_mask = dst_mask = ~0LU; + filter_fc = filter_lfe = true; goto done; } else { spa_log_debug(mix->log, "matching channels"); @@ -395,6 +399,15 @@ static int make_matrix(struct channelmix *mix) spa_log_debug(mix->log, "unassigned upmix %08"PRIx64" lfe:%f", unassigned, mix->lfe_cutoff); + if (unassigned & STEREO) { + if ((src_mask & FRONT) == FRONT) { + spa_log_debug(mix->log, "produce STEREO from FC"); + _MATRIX(FL,FC) += clev; + _MATRIX(FR,FC) += clev; + } else { + spa_log_warn(mix->log, "can't produce STEREO"); + } + } if (unassigned & FRONT) { if ((src_mask & STEREO) == STEREO) { spa_log_debug(mix->log, "produce FC from STEREO"); @@ -426,12 +439,15 @@ static int make_matrix(struct channelmix *mix) _MATRIX(SR,RR) += 1.0f; } else if ((src_mask & STEREO) == STEREO) { spa_log_debug(mix->log, "produce SIDE from STEREO"); - _MATRIX(SL,FL) += 1.0f; - _MATRIX(SR,FR) += 1.0f; - } else if ((src_mask & FRONT) == FRONT) { + _MATRIX(SL,FL) += slev; + _MATRIX(SR,FR) += slev; + } else if ((src_mask & FRONT) == FRONT && + mix->upmix == CHANNELMIX_UPMIX_SIMPLE) { spa_log_debug(mix->log, "produce SIDE from FC"); _MATRIX(SL,FC) += clev; _MATRIX(SR,FC) += clev; + } else { + spa_log_debug(mix->log, "won't produce SIDE"); } } if (unassigned & REAR) { @@ -441,12 +457,15 @@ static int make_matrix(struct channelmix *mix) _MATRIX(RR,SR) += 1.0f; } else if ((src_mask & STEREO) == STEREO) { spa_log_debug(mix->log, "produce REAR from STEREO"); - _MATRIX(RL,FL) += 1.0f; - _MATRIX(RR,FR) += 1.0f; - } else if ((src_mask & FRONT) == FRONT) { + _MATRIX(RL,FL) += slev; + _MATRIX(RR,FR) += slev; + } else if ((src_mask & FRONT) == FRONT && + mix->upmix == CHANNELMIX_UPMIX_SIMPLE) { spa_log_debug(mix->log, "produce REAR from FC"); _MATRIX(RL,FC) += clev; _MATRIX(RR,FC) += clev; + } else { + spa_log_debug(mix->log, "won't produce SIDE"); } } @@ -551,6 +570,10 @@ int channelmix_init(struct channelmix *mix) { const struct channelmix_info *info; + if (mix->src_chan > SPA_AUDIO_MAX_CHANNELS || + mix->dst_chan > SPA_AUDIO_MAX_CHANNELS) + return -EINVAL; + info = find_channelmix_info(mix->src_chan, mix->src_mask, mix->dst_chan, mix->dst_mask, mix->cpu_flags); if (info == NULL) @@ -561,6 +584,7 @@ int channelmix_init(struct channelmix *mix) mix->set_volume = impl_channelmix_set_volume; mix->cpu_flags = info->cpu_flags; mix->delay = mix->rear_delay * mix->freq / 1000.0f; + mix->func_name = info->name; spa_log_debug(mix->log, "selected %s delay:%d options:%08x", info->name, mix->delay, mix->options); diff --git a/spa/plugins/audioconvert/channelmix-ops.h b/spa/plugins/audioconvert/channelmix-ops.h index f9a17976e123338286c3ce3ccf730a8d357dac19..89737c315fc090a5d003080cfc9bd49bc98c9296 100644 --- a/spa/plugins/audioconvert/channelmix-ops.h +++ b/spa/plugins/audioconvert/channelmix-ops.h @@ -29,10 +29,6 @@ #include <spa/utils/string.h> #include <spa/param/audio/raw.h> -#undef SPA_LOG_TOPIC_DEFAULT -#define SPA_LOG_TOPIC_DEFAULT log_topic -extern struct spa_log_topic *log_topic; - #include "crossover.h" #include "delay.h" @@ -47,8 +43,6 @@ extern struct spa_log_topic *log_topic; #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 BUFFER_SIZE 4096 - #define BUFFER_SIZE 4096 #define MAX_TAPS 255 @@ -62,12 +56,13 @@ struct channelmix { #define CHANNELMIX_OPTION_NORMALIZE (1<<1) /**< normalize volumes */ #define CHANNELMIX_OPTION_UPMIX (1<<2) /**< do simple upmixing */ uint32_t options; -#define CHANNELMIX_UPMIX_NONE (0) /**< disable upmixing */ -#define CHANNELMIX_UPMIX_SIMPLE (1) /**< simple upmixing */ -#define CHANNELMIX_UPMIX_PSD (2) /**< Passive Surround Decoding upmixing */ +#define CHANNELMIX_UPMIX_NONE 0 /**< disable upmixing */ +#define CHANNELMIX_UPMIX_SIMPLE 1 /**< simple upmixing */ +#define CHANNELMIX_UPMIX_PSD 2 /**< Passive Surround Decoding upmixing */ uint32_t upmix; struct spa_log *log; + const char *func_name; #define CHANNELMIX_FLAG_ZERO (1<<0) /**< all zero components */ #define CHANNELMIX_FLAG_IDENTITY (1<<1) /**< identity matrix */ @@ -142,6 +137,7 @@ DEFINE_FUNCTION(f32_2_4, c); DEFINE_FUNCTION(f32_2_3p1, c); DEFINE_FUNCTION(f32_2_5p1, c); DEFINE_FUNCTION(f32_2_7p1, c); +DEFINE_FUNCTION(f32_3p1_2, c); DEFINE_FUNCTION(f32_5p1_2, c); DEFINE_FUNCTION(f32_5p1_3p1, c); DEFINE_FUNCTION(f32_5p1_4, c); @@ -151,9 +147,11 @@ DEFINE_FUNCTION(f32_7p1_4, c); #if defined (HAVE_SSE) DEFINE_FUNCTION(copy, sse); -DEFINE_FUNCTION(f32_2_4, sse); +DEFINE_FUNCTION(f32_3p1_2, sse); DEFINE_FUNCTION(f32_5p1_2, sse); DEFINE_FUNCTION(f32_5p1_3p1, sse); DEFINE_FUNCTION(f32_5p1_4, sse); DEFINE_FUNCTION(f32_7p1_4, sse); #endif + +#undef DEFINE_FUNCTION diff --git a/spa/plugins/audioconvert/channelmix.c b/spa/plugins/audioconvert/channelmix.c deleted file mode 100644 index 7f3964874193412b9967f7559c1c81097b98cc77..0000000000000000000000000000000000000000 --- a/spa/plugins/audioconvert/channelmix.c +++ /dev/null @@ -1,1786 +0,0 @@ -/* Spa - * - * Copyright © 2018 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include <errno.h> -#include <string.h> -#include <stdio.h> - -#include <spa/support/plugin.h> -#include <spa/support/log.h> -#include <spa/support/cpu.h> -#include <spa/utils/list.h> -#include <spa/utils/names.h> -#include <spa/utils/json.h> -#include <spa/utils/string.h> -#include <spa/node/keys.h> -#include <spa/node/node.h> -#include <spa/node/io.h> -#include <spa/node/utils.h> -#include <spa/param/audio/format-utils.h> -#include <spa/param/param.h> -#include <spa/pod/filter.h> -#include <spa/debug/types.h> - -#include "channelmix-ops.h" - - -#define DEFAULT_RATE 48000 -#define DEFAULT_CHANNELS 2 - -#define MAX_BUFFERS 32 -#define MAX_DATAS SPA_AUDIO_MAX_CHANNELS -#define MAX_ALIGN CHANNELMIX_OPS_MAX_ALIGN - -#define DEFAULT_CONTROL_BUFFER_SIZE 32768 - -struct impl; - -#define DEFAULT_MUTE false -#define DEFAULT_VOLUME 1.0f - -struct volumes { - bool mute; - uint32_t n_volumes; - float volumes[SPA_AUDIO_MAX_CHANNELS]; -}; - -static void init_volumes(struct volumes *vol) -{ - uint32_t i; - vol->mute = DEFAULT_MUTE; - vol->n_volumes = 0; - for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) - vol->volumes[i] = DEFAULT_VOLUME; -} - -struct props { - float volume; - uint32_t n_channels; - uint32_t channel_map[SPA_AUDIO_MAX_CHANNELS]; - struct volumes channel; - struct volumes soft; - struct volumes monitor; - unsigned int have_soft_volume:1; - unsigned int disabled:1; -}; - -static void props_reset(struct props *props) -{ - uint32_t i; - props->volume = DEFAULT_VOLUME; - props->n_channels = 0; - for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) - props->channel_map[i] = SPA_AUDIO_CHANNEL_UNKNOWN; - init_volumes(&props->channel); - init_volumes(&props->soft); - init_volumes(&props->monitor); -} - -struct buffer { - uint32_t id; -#define BUFFER_FLAG_OUT (1 << 0) - uint32_t flags; - struct spa_list link; - struct spa_buffer *outbuf; - struct spa_meta_header *h; - void *datas[MAX_DATAS]; -}; - -struct port { - uint32_t direction; - uint32_t id; - - uint64_t info_all; - struct spa_port_info info; -#define IDX_EnumFormat 0 -#define IDX_Meta 1 -#define IDX_IO 2 -#define IDX_Format 3 -#define IDX_Buffers 4 - struct spa_param_info params[5]; - - struct spa_io_buffers *io; - - bool have_format; - struct spa_audio_info format; - uint32_t stride; - uint32_t blocks; - uint32_t size; - - struct buffer buffers[MAX_BUFFERS]; - uint32_t n_buffers; - - struct spa_list queue; - - struct spa_pod_sequence *ctrl; - uint32_t ctrl_offset; -}; - -struct impl { - struct spa_handle handle; - struct spa_node node; - - struct spa_log *log; - struct spa_cpu *cpu; - uint32_t quantum_limit; - - enum spa_direction direction; - struct spa_io_position *io_position; - - struct spa_hook_list hooks; - - uint64_t info_all; - struct spa_node_info info; - struct props props; -#define IDX_PropInfo 0 -#define IDX_Props 1 - struct spa_param_info params[2]; - - - struct port control_port; - struct port in_port; - struct port out_port; - - struct channelmix mix; - unsigned int started:1; - unsigned int is_passthrough:1; - uint32_t cpu_flags; - uint32_t max_align; -}; - -#define IS_CONTROL_PORT(this,d,id) (id == 1 && d == SPA_DIRECTION_INPUT) -#define IS_DATA_PORT(this,d,id) (id == 0) - -#define CHECK_PORT(this,d,id) (IS_CONTROL_PORT(this,d,id) || IS_DATA_PORT(this,d,id)) -#define GET_CONTROL_PORT(this,id) (&this->control_port) -#define GET_IN_PORT(this,id) (&this->in_port) -#define GET_OUT_PORT(this,id) (&this->out_port) -#define GET_PORT(this,d,id) (IS_CONTROL_PORT(this,d,id) ? GET_CONTROL_PORT(this,id) : (d == SPA_DIRECTION_INPUT ? GET_IN_PORT(this,id) : GET_OUT_PORT(this,id))) - -#define _MASK(ch) (1ULL << SPA_AUDIO_CHANNEL_ ## ch) -#define STEREO (_MASK(FL)|_MASK(FR)) - -static void emit_info(struct impl *this, bool full) -{ - uint64_t old = full ? this->info.change_mask : 0; - if (full) - this->info.change_mask = this->info_all; - if (this->info.change_mask) { - spa_node_emit_info(&this->hooks, &this->info); - this->info.change_mask = old; - } -} - -static void emit_props_changed(struct impl *this) -{ - this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; - this->params[IDX_Props].flags ^= SPA_PARAM_INFO_SERIAL; - emit_info(this, false); -} - -static uint64_t default_mask(uint32_t channels) -{ - uint64_t mask = 0; - switch (channels) { - case 7: - case 8: - mask |= _MASK(RL); - mask |= _MASK(RR); - SPA_FALLTHROUGH - case 5: - case 6: - mask |= _MASK(SL); - mask |= _MASK(SR); - if ((channels & 1) == 0) - mask |= _MASK(LFE); - SPA_FALLTHROUGH - case 3: - mask |= _MASK(FC); - SPA_FALLTHROUGH - case 2: - mask |= _MASK(FL); - mask |= _MASK(FR); - break; - case 1: - mask |= _MASK(MONO); - break; - case 4: - mask |= _MASK(FL); - mask |= _MASK(FR); - mask |= _MASK(RL); - mask |= _MASK(RR); - break; - } - return mask; -} - -static void fix_volumes(struct volumes *vols, uint32_t channels) -{ - float s; - uint32_t i; - if (vols->n_volumes > 0) { - s = 0.0f; - for (i = 0; i < vols->n_volumes; i++) - s += vols->volumes[i]; - s /= vols->n_volumes; - } else { - s = 1.0f; - } - vols->n_volumes = channels; - for (i = 0; i < vols->n_volumes; i++) - vols->volumes[i] = s; -} - -static int remap_volumes(struct impl *this, const struct spa_audio_info *info) -{ - struct props *p = &this->props; - uint32_t i, j, target = info->info.raw.channels; - - for (i = 0; i < p->n_channels; i++) { - for (j = i; j < target; j++) { - spa_log_debug(this->log, "%d %d: %d <-> %d", i, j, - p->channel_map[i], info->info.raw.position[j]); - if (p->channel_map[i] != info->info.raw.position[j]) - continue; - if (i != j) { - SPA_SWAP(p->channel_map[i], p->channel_map[j]); - SPA_SWAP(p->channel.volumes[i], p->channel.volumes[j]); - SPA_SWAP(p->soft.volumes[i], p->soft.volumes[j]); - SPA_SWAP(p->monitor.volumes[i], p->monitor.volumes[j]); - } - break; - } - } - p->n_channels = target; - for (i = 0; i < p->n_channels; i++) - p->channel_map[i] = info->info.raw.position[i]; - - if (target == 0) - return 0; - if (p->channel.n_volumes != target) - fix_volumes(&p->channel, target); - if (p->soft.n_volumes != target) - fix_volumes(&p->soft, target); - if (p->monitor.n_volumes != target) - fix_volumes(&p->monitor, target); - - return 1; -} - -static void set_volume(struct impl *this) -{ - struct volumes *vol; - - if (this->mix.set_volume == NULL || - this->props.disabled) - return; - - if (this->props.have_soft_volume) - vol = &this->props.soft; - else - vol = &this->props.channel; - - channelmix_set_volume(&this->mix, this->props.volume, vol->mute, - vol->n_volumes, vol->volumes); -} - -static int setup_convert(struct impl *this, - enum spa_direction direction, - const struct spa_audio_info *info) -{ - const struct spa_audio_info *src_info, *dst_info; - uint32_t i, src_chan, dst_chan, p; - uint64_t src_mask, dst_mask; - int res; - - if (direction == SPA_DIRECTION_INPUT) { - src_info = info; - dst_info = &GET_OUT_PORT(this, 0)->format; - } else { - src_info = &GET_IN_PORT(this, 0)->format; - dst_info = info; - } - - src_chan = src_info->info.raw.channels; - dst_chan = dst_info->info.raw.channels; - - for (i = 0, src_mask = 0; i < src_chan; i++) { - p = src_info->info.raw.position[i]; - src_mask |= 1ULL << (p < 64 ? p : 0); - } - for (i = 0, dst_mask = 0; i < dst_chan; i++) { - p = dst_info->info.raw.position[i]; - dst_mask |= 1ULL << (p < 64 ? p : 0); - } - - if (src_mask & 1) - src_mask = default_mask(src_chan); - if (dst_mask & 1) - dst_mask = default_mask(dst_chan); - - spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d %08"PRIx64":%08"PRIx64, this, - spa_debug_type_find_name(spa_type_audio_format, src_info->info.raw.format), - src_chan, - src_info->info.raw.rate, - spa_debug_type_find_name(spa_type_audio_format, dst_info->info.raw.format), - dst_chan, - dst_info->info.raw.rate, - src_mask, dst_mask); - - if (src_info->info.raw.rate != dst_info->info.raw.rate) - return -EINVAL; - - this->mix.src_chan = src_chan; - this->mix.src_mask = src_mask; - this->mix.dst_chan = dst_chan; - this->mix.dst_mask = dst_mask; - this->mix.cpu_flags = this->cpu_flags; - this->mix.log = this->log; - this->mix.freq = src_info->info.raw.rate; - - if ((res = channelmix_init(&this->mix)) < 0) - return res; - - remap_volumes(this, this->direction == SPA_DIRECTION_INPUT ? src_info : dst_info); - set_volume(this); - - emit_props_changed(this); - - this->is_passthrough = SPA_FLAG_IS_SET(this->mix.flags, CHANNELMIX_FLAG_IDENTITY); - if (!this->is_passthrough && this->props.disabled) - return -EINVAL; - - spa_log_debug(this->log, "%p: got channelmix features %08x:%08x flags:%08x passthrough:%d", - this, this->cpu_flags, this->mix.cpu_flags, - this->mix.flags, this->is_passthrough); - - - 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[4096]; - struct spa_result_node_params result; - uint32_t count = 0; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(num != 0, -EINVAL); - - result.id = id; - result.next = start; - next: - result.index = result.next++; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - switch (id) { - case SPA_PARAM_PropInfo: - { - struct props *p = &this->props; - - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_volume), - SPA_PROP_INFO_description, SPA_POD_String("Volume"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0)); - break; - case 1: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_mute), - SPA_PROP_INFO_description, SPA_POD_String("Mute"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->channel.mute)); - break; - case 2: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_channelVolumes), - SPA_PROP_INFO_description, SPA_POD_String("Channel Volumes"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0), - SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); - break; - case 3: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_channelMap), - SPA_PROP_INFO_description, SPA_POD_String("Channel Map"), - SPA_PROP_INFO_type, SPA_POD_Id(SPA_AUDIO_CHANNEL_UNKNOWN), - SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); - break; - case 4: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_softMute), - SPA_PROP_INFO_description, SPA_POD_String("Soft Mute"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->soft.mute)); - break; - case 5: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_softVolumes), - SPA_PROP_INFO_description, SPA_POD_String("Soft Volumes"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0), - SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); - break; - case 6: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_monitorMute), - SPA_PROP_INFO_description, SPA_POD_String("Monitor Mute"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->monitor.mute)); - break; - case 7: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_monitorVolumes), - SPA_PROP_INFO_description, SPA_POD_String("Monitor Volumes"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0), - SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); - break; - case 8: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.disable"), - SPA_PROP_INFO_description, SPA_POD_String("Disable Channel mixing"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->disabled), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 9: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.normalize"), - SPA_PROP_INFO_description, SPA_POD_String("Normalize Volumes"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( - SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_NORMALIZE)), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 10: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.mix-lfe"), - SPA_PROP_INFO_description, SPA_POD_String("Mix LFE into channels"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( - SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_MIX_LFE)), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 11: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.upmix"), - SPA_PROP_INFO_description, SPA_POD_String("Enable upmixing"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( - SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_UPMIX)), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 12: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.lfe-cutoff"), - SPA_PROP_INFO_description, SPA_POD_String("LFE cutoff frequency (Hz)"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( - this->mix.lfe_cutoff, 0.0, 1000.0), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 13: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.fc-cutoff"), - SPA_PROP_INFO_description, SPA_POD_String("FC cutoff frequency (Hz)"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( - this->mix.fc_cutoff, 0.0, 48000.0), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 14: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.rear-delay"), - SPA_PROP_INFO_description, SPA_POD_String("Rear channels delay (ms)"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( - this->mix.rear_delay, 0.0, 1000.0), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 15: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.stereo-widen"), - SPA_PROP_INFO_description, SPA_POD_String("Stereo widen"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( - this->mix.widen, 0.0, 1.0), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 16: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.hilbert-taps"), - SPA_PROP_INFO_description, SPA_POD_String("Taps for phase shift of rear"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int( - this->mix.hilbert_taps, 0, MAX_TAPS), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 17: - { - struct spa_pod_frame f[2]; - uint32_t i; - spa_pod_builder_push_object(&b, &f[0], - SPA_TYPE_OBJECT_PropInfo, id); - spa_pod_builder_add(&b, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.upmix-method"), - SPA_PROP_INFO_description, SPA_POD_String("Upmix Method to use"), - SPA_PROP_INFO_type, SPA_POD_String( - channelmix_upmix_info[this->mix.upmix].label), - 0); - spa_pod_builder_prop(&b, SPA_PROP_INFO_labels, 0); - spa_pod_builder_push_struct(&b, &f[1]); - for (i = 0; i < SPA_N_ELEMENTS(channelmix_upmix_info); i++) { - spa_pod_builder_string(&b, channelmix_upmix_info[i].label); - spa_pod_builder_string(&b, channelmix_upmix_info[i].description); - } - spa_pod_builder_pop(&b, &f[1]); - spa_pod_builder_add(&b, - SPA_PROP_INFO_params, SPA_POD_Bool(true), - 0); - param = spa_pod_builder_pop(&b, &f[0]); - break; - } - default: - return 0; - } - break; - } - case SPA_PARAM_Props: - { - struct props *p = &this->props; - struct spa_pod_frame f[2]; - - switch (result.index) { - case 0: - spa_pod_builder_push_object(&b, &f[0], - SPA_TYPE_OBJECT_Props, id); - spa_pod_builder_add(&b, - SPA_PROP_volume, SPA_POD_Float(p->volume), - SPA_PROP_mute, SPA_POD_Bool(p->channel.mute), - SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float), - SPA_TYPE_Float, - p->channel.n_volumes, - p->channel.volumes), - SPA_PROP_channelMap, SPA_POD_Array(sizeof(uint32_t), - SPA_TYPE_Id, - p->n_channels, - p->channel_map), - SPA_PROP_softMute, SPA_POD_Bool(p->soft.mute), - SPA_PROP_softVolumes, SPA_POD_Array(sizeof(float), - SPA_TYPE_Float, - p->soft.n_volumes, - p->soft.volumes), - SPA_PROP_monitorMute, SPA_POD_Bool(p->monitor.mute), - SPA_PROP_monitorVolumes, SPA_POD_Array(sizeof(float), - SPA_TYPE_Float, - p->monitor.n_volumes, - p->monitor.volumes), - 0); - spa_pod_builder_prop(&b, SPA_PROP_params, 0); - spa_pod_builder_push_struct(&b, &f[1]); - spa_pod_builder_string(&b, "channelmix.disable"); - spa_pod_builder_bool(&b, this->props.disabled); - spa_pod_builder_string(&b, "channelmix.normalize"); - spa_pod_builder_bool(&b, SPA_FLAG_IS_SET(this->mix.options, - CHANNELMIX_OPTION_NORMALIZE)); - spa_pod_builder_string(&b, "channelmix.mix-lfe"); - spa_pod_builder_bool(&b, SPA_FLAG_IS_SET(this->mix.options, - CHANNELMIX_OPTION_MIX_LFE)); - spa_pod_builder_string(&b, "channelmix.upmix"); - spa_pod_builder_bool(&b, SPA_FLAG_IS_SET(this->mix.options, - CHANNELMIX_OPTION_UPMIX)); - spa_pod_builder_string(&b, "channelmix.lfe-cutoff"); - spa_pod_builder_float(&b, this->mix.lfe_cutoff); - spa_pod_builder_string(&b, "channelmix.fc-cutoff"); - spa_pod_builder_float(&b, this->mix.fc_cutoff); - spa_pod_builder_string(&b, "channelmix.rear-delay"); - spa_pod_builder_float(&b, this->mix.rear_delay); - spa_pod_builder_string(&b, "channelmix.stereo-widen"); - spa_pod_builder_float(&b, this->mix.widen); - spa_pod_builder_string(&b, "channelmix.hilbert-taps"); - spa_pod_builder_int(&b, this->mix.hilbert_taps); - spa_pod_builder_string(&b, "channelmix.upmix-method"); - spa_pod_builder_string(&b, channelmix_upmix_info[this->mix.upmix].label); - spa_pod_builder_pop(&b, &f[1]); - param = spa_pod_builder_pop(&b, &f[0]); - break; - default: - return 0; - } - break; - } - default: - return -ENOENT; - } - - if (spa_pod_filter(&b, &result.param, param, filter) < 0) - goto next; - - spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - - if (++count != num) - goto next; - - return 0; -} - -static int channelmix_set_param(struct impl *this, const char *k, const char *s) -{ - if (spa_streq(k, "channelmix.disable")) - this->props.disabled = spa_atob(s); - else if (spa_streq(k, "channelmix.normalize")) - SPA_FLAG_UPDATE(this->mix.options, CHANNELMIX_OPTION_NORMALIZE, spa_atob(s)); - else if (spa_streq(k, "channelmix.mix-lfe")) - SPA_FLAG_UPDATE(this->mix.options, CHANNELMIX_OPTION_MIX_LFE, spa_atob(s)); - else if (spa_streq(k, "channelmix.upmix")) - SPA_FLAG_UPDATE(this->mix.options, CHANNELMIX_OPTION_UPMIX, spa_atob(s)); - else if (spa_streq(k, "channelmix.lfe-cutoff")) - spa_atof(s, &this->mix.lfe_cutoff); - else if (spa_streq(k, "channelmix.fc-cutoff")) - spa_atof(s, &this->mix.fc_cutoff); - else if (spa_streq(k, "channelmix.rear-delay")) - spa_atof(s, &this->mix.rear_delay); - else if (spa_streq(k, "channelmix.stereo-widen")) - spa_atof(s, &this->mix.widen); - else if (spa_streq(k, "channelmix.hilbert-taps")) - spa_atou32(s, &this->mix.hilbert_taps, 0); - else if (spa_streq(k, "channelmix.upmix-method")) - this->mix.upmix = channelmix_upmix_from_label(s); - else - return 0; - return 1; -} - -static int parse_prop_params(struct impl *this, struct spa_pod *params) -{ - struct spa_pod_parser prs; - struct spa_pod_frame f; - int changed = 0; - - spa_pod_parser_pod(&prs, params); - if (spa_pod_parser_push_struct(&prs, &f) < 0) - return 0; - - while (true) { - const char *name; - struct spa_pod *pod; - char value[512], buf[128]; - - if (spa_pod_parser_get_string(&prs, &name) < 0) - break; - - if (spa_pod_parser_get_pod(&prs, &pod) < 0) - break; - - if (spa_pod_is_string(pod)) { - spa_pod_copy_string(pod, sizeof(value), value); - } else if (spa_pod_is_float(pod)) { - snprintf(value, sizeof(value), "%s", - spa_json_format_float(buf, sizeof(buf), - SPA_POD_VALUE(struct spa_pod_float, pod))); - } else if (spa_pod_is_int(pod)) { - snprintf(value, sizeof(value), "%d", - SPA_POD_VALUE(struct spa_pod_int, pod)); - } else if (spa_pod_is_bool(pod)) { - snprintf(value, sizeof(value), "%s", - SPA_POD_VALUE(struct spa_pod_bool, pod) ? - "true" : "false"); - } else - continue; - - spa_log_info(this->log, "key:'%s' val:'%s'", name, value); - changed += channelmix_set_param(this, name, value); - } - if (changed && !this->props.disabled) - channelmix_init(&this->mix); - return changed; -} - -static int apply_props(struct impl *this, const struct spa_pod *param) -{ - struct spa_pod_prop *prop; - struct spa_pod_object *obj = (struct spa_pod_object *) param; - struct props *p = &this->props; - int changed = 0; - bool have_channel_volume = false; - bool have_soft_volume = false; - uint32_t n; - - if (param == NULL) - return 0; - - SPA_POD_OBJECT_FOREACH(obj, prop) { - switch (prop->key) { - case SPA_PROP_volume: - if (spa_pod_get_float(&prop->value, &p->volume) == 0) - changed++; - break; - case SPA_PROP_mute: - if (spa_pod_get_bool(&prop->value, &p->channel.mute) == 0) { - changed++; - have_channel_volume = true; - } - break; - case SPA_PROP_channelVolumes: - if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - p->channel.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) { - p->channel.n_volumes = n; - changed++; - have_channel_volume = true; - } - break; - case SPA_PROP_channelMap: - if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Id, - p->channel_map, SPA_AUDIO_MAX_CHANNELS)) > 0) { - p->n_channels = n; - changed++; - } - break; - case SPA_PROP_softMute: - if (spa_pod_get_bool(&prop->value, &p->soft.mute) == 0) { - changed++; - have_soft_volume = true; - } - break; - case SPA_PROP_softVolumes: - if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - p->soft.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) { - p->soft.n_volumes = n; - changed++; - have_soft_volume = true; - } - break; - case SPA_PROP_monitorMute: - if (spa_pod_get_bool(&prop->value, &p->monitor.mute) == 0) - changed++; - break; - case SPA_PROP_monitorVolumes: - if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - p->monitor.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) { - p->monitor.n_volumes = n; - changed++; - } - break; - case SPA_PROP_params: - changed += parse_prop_params(this, &prop->value); - break; - default: - break; - } - } - - if (changed) { - struct port *port = GET_PORT(this, this->direction, 0); - if (have_soft_volume) - p->have_soft_volume = true; - else if (have_channel_volume) - p->have_soft_volume = false; - - if (port->have_format) - remap_volumes(this, &port->format); - - set_volume(this); - } - return changed; -} - -static int apply_midi(struct impl *this, const struct spa_pod *value) -{ - const uint8_t *val = SPA_POD_BODY(value); - uint32_t size = SPA_POD_BODY_SIZE(value); - struct props *p = &this->props; - - if (size < 3) - return -EINVAL; - - if ((val[0] & 0xf0) != 0xb0 || val[1] != 7) - return 0; - - p->volume = val[2] / 127.0; - set_volume(this); - return 1; -} - -static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_log_debug(this->log, "%p: io %d %p/%zd", this, id, data, size); - - switch (id) { - case SPA_IO_Position: - this->io_position = data; - break; - default: - return -ENOENT; - } - return 0; -} - -static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - switch (id) { - case SPA_PARAM_Props: - if (apply_props(this, param) > 0) - emit_props_changed(this); - break; - default: - return -ENOENT; - } - return 0; -} - -static int impl_node_send_command(void *object, const struct spa_command *command) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(command != NULL, -EINVAL); - - switch (SPA_NODE_COMMAND_ID(command)) { - case SPA_NODE_COMMAND_Start: - this->started = true; - break; - case SPA_NODE_COMMAND_Suspend: - case SPA_NODE_COMMAND_Flush: - case SPA_NODE_COMMAND_Pause: - this->started = false; - break; - default: - return -ENOTSUP; - } - return 0; -} - -static void emit_port_info(struct impl *this, struct port *port, bool full) -{ - uint64_t old = full ? port->info.change_mask : 0; - if (full) - port->info.change_mask = port->info_all; - if (port->info.change_mask) { - spa_node_emit_port_info(&this->hooks, - port->direction, port->id, &port->info); - port->info.change_mask = old; - } -} - -static int -impl_node_add_listener(void *object, - struct spa_hook *listener, - const struct spa_node_events *events, - void *data) -{ - struct impl *this = object; - struct spa_hook_list save; - struct spa_dict_item items[2]; - uint32_t n_items = 0; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_hook_list_isolate(&this->hooks, &save, listener, events, data); - - emit_info(this, true); - emit_port_info(this, GET_IN_PORT(this, 0), true); - emit_port_info(this, GET_OUT_PORT(this, 0), true); - - struct port *control_port = GET_CONTROL_PORT(this, 1); - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "control"); - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"); - control_port->info.props = &SPA_DICT_INIT(items, n_items); - emit_port_info(this, control_port, true); - - spa_hook_list_join(&this->hooks, &save); - - return 0; -} - -static int -impl_node_set_callbacks(void *object, - const struct spa_node_callbacks *callbacks, - void *user_data) -{ - return 0; -} - -static int impl_node_add_port(void *object, - enum spa_direction direction, uint32_t port_id, - const struct spa_dict *props) -{ - return -ENOTSUP; -} - -static int impl_node_remove_port(void *object, - enum spa_direction direction, uint32_t port_id) -{ - return -ENOTSUP; -} - -static int port_enum_formats(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t index, - struct spa_pod **param, - struct spa_pod_builder *builder) -{ - struct impl *this = object; - - switch (index) { - case 0: - if (IS_CONTROL_PORT(this, direction, port_id)) { - *param = spa_pod_builder_add_object(builder, - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); - } else { - struct spa_pod_frame f; - struct port *other; - int32_t channels, min = 1, max = INT32_MAX; - - other = GET_PORT(this, SPA_DIRECTION_REVERSE(direction), 0); - - spa_pod_builder_push_object(builder, &f, - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); - spa_pod_builder_add(builder, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_F32P), - 0); - - if (other->have_format) { - channels = other->format.info.raw.channels; - if (this->props.disabled) - min = max = channels; - - spa_pod_builder_add(builder, - SPA_FORMAT_AUDIO_rate, SPA_POD_Int(other->format.info.raw.rate), - SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int( - channels, min, max), - 0); - } else { - uint32_t rate = this->io_position ? - this->io_position->clock.rate.denom : DEFAULT_RATE; - channels = DEFAULT_CHANNELS; - - spa_pod_builder_add(builder, - SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(rate, 0, INT32_MAX), - SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int( - channels, min, max), - 0); - } - *param = spa_pod_builder_pop(builder, &f); - } - break; - default: - return 0; - } - return 1; -} - -static int -impl_node_port_enum_params(void *object, int seq, - enum spa_direction direction, uint32_t port_id, - uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) -{ - struct impl *this = object; - struct port *port, *other; - struct spa_pod *param; - struct spa_pod_builder b = { 0 }; - uint8_t buffer[1024]; - struct spa_result_node_params result; - uint32_t count = 0; - int res; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(num != 0, -EINVAL); - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - port = GET_PORT(this, direction, port_id); - other = GET_PORT(this, SPA_DIRECTION_REVERSE(direction), port_id); - - spa_log_debug(this->log, "%p: enum params port %d.%d %d %u", - this, direction, port_id, seq, id); - - result.id = id; - result.next = start; - next: - result.index = result.next++; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - switch (id) { - case SPA_PARAM_EnumFormat: - if ((res = port_enum_formats(this, direction, port_id, - result.index, ¶m, &b)) <= 0) - return res; - break; - - case SPA_PARAM_Format: - if (!port->have_format) - return -EIO; - if (result.index > 0) - return 0; - - if (IS_CONTROL_PORT(this, direction, port_id)) - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); - else - param = spa_format_audio_raw_build(&b, id, &port->format.info.raw); - break; - - case SPA_PARAM_Buffers: - { - uint32_t buffers, size; - - if (!port->have_format) - return -EIO; - if (result.index > 0) - return 0; - - if (IS_CONTROL_PORT(this, direction, port_id)) { - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamBuffers, id, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), - SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( - DEFAULT_CONTROL_BUFFER_SIZE, - 1024, - INT32_MAX), - SPA_PARAM_BUFFERS_stride, SPA_POD_Int(1)); - } else { - if (other->n_buffers > 0) { - buffers = other->n_buffers; - size = other->size / other->stride; - } else { - buffers = 1; - size = this->quantum_limit; - } - - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamBuffers, id, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(buffers, 1, MAX_BUFFERS), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->blocks), - SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( - size * port->stride, - 16 * port->stride, - INT32_MAX), - SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->stride)); - } - break; - } - case SPA_PARAM_Meta: - switch (result.index) { - case 0: - if (IS_CONTROL_PORT(this, direction, port_id)) - return -EINVAL; - - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamMeta, id, - SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), - SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); - break; - default: - return 0; - } - break; - - case SPA_PARAM_IO: - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamIO, id, - SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), - SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); - break; - default: - return 0; - } - break; - default: - return -ENOENT; - } - - if (spa_pod_filter(&b, &result.param, param, filter) < 0) - goto next; - - spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - - if (++count != num) - goto next; - - return 0; -} - -static int clear_buffers(struct impl *this, struct port *port) -{ - if (port->n_buffers > 0) { - spa_log_debug(this->log, "%p: clear buffers %p", this, port); - port->n_buffers = 0; - spa_list_init(&port->queue); - } - return 0; -} - -static int port_set_format(void *object, - enum spa_direction direction, - uint32_t port_id, - uint32_t flags, - const struct spa_pod *format) -{ - struct impl *this = object; - struct port *port, *other; - int res = 0; - - port = GET_PORT(this, direction, port_id); - other = GET_PORT(this, SPA_DIRECTION_REVERSE(direction), port_id); - - if (format == NULL) { - if (port->have_format) { - port->have_format = false; - clear_buffers(this, port); - if (this->mix.process) - channelmix_free(&this->mix); - } - } else { - struct spa_audio_info info = { 0 }; - - if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) - return res; - - if (IS_CONTROL_PORT(this, direction, port_id)) { - if (info.media_type != SPA_MEDIA_TYPE_application || - info.media_subtype != SPA_MEDIA_SUBTYPE_control) - return -EINVAL; - } else { - if (info.media_type != SPA_MEDIA_TYPE_audio || - info.media_subtype != SPA_MEDIA_SUBTYPE_raw) - return -EINVAL; - - if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) - return -EINVAL; - - if (info.info.raw.format != SPA_AUDIO_FORMAT_F32P) - return -EINVAL; - - port->stride = sizeof(float); - port->blocks = info.info.raw.channels; - - if (other->have_format) { - if ((res = setup_convert(this, direction, &info)) < 0) - return res; - } - } - port->format = info; - port->have_format = true; - - spa_log_debug(this->log, "%p: set format on port %d %d", this, port_id, res); - } - - port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - if (port->have_format) { - port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); - port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); - } else { - port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - } - emit_port_info(this, port, false); - - return res; -} - -static int -impl_node_port_set_param(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - switch (id) { - case SPA_PARAM_Format: - return port_set_format(object, direction, port_id, flags, param); - default: - break; - } - - return -ENOENT; -} - -static int -impl_node_port_use_buffers(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t flags, - struct spa_buffer **buffers, uint32_t n_buffers) -{ - struct impl *this = object; - struct port *port; - uint32_t i, j, size = SPA_ID_INVALID; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - port = GET_PORT(this, direction, port_id); - - if (IS_DATA_PORT(this, direction, port_id)) - spa_return_val_if_fail(port->have_format, -EIO); - - spa_log_debug(this->log, "%p: use buffers %d on port %d", this, n_buffers, port_id); - - clear_buffers(this, port); - - for (i = 0; i < n_buffers; i++) { - struct buffer *b; - uint32_t n_datas = buffers[i]->n_datas; - struct spa_data *d = buffers[i]->datas; - - b = &port->buffers[i]; - b->id = i; - b->flags = 0; - b->outbuf = buffers[i]; - b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); - - for (j = 0; j < n_datas; j++) { - if (size == SPA_ID_INVALID) - size = d[j].maxsize; - else if (size != d[j].maxsize) - return -EINVAL; - - if (d[j].data == NULL) { - spa_log_error(this->log, "%p: invalid memory on buffer %p", this, - buffers[i]); - return -EINVAL; - } - if (!SPA_IS_ALIGNED(d[j].data, this->max_align)) { - spa_log_warn(this->log, "%p: memory %d on buffer %d not aligned", - this, j, i); - } - b->datas[j] = d[j].data; - if (direction == SPA_DIRECTION_OUTPUT && - !SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_DYNAMIC)) - this->is_passthrough = false; - } - if (direction == SPA_DIRECTION_OUTPUT) - spa_list_append(&port->queue, &b->link); - else - SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); - } - port->n_buffers = n_buffers; - port->size = size; - - return 0; -} - -static int -impl_node_port_set_io(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t id, void *data, size_t size) -{ - struct impl *this = object; - struct port *port; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - port = GET_PORT(this, direction, port_id); - - switch (id) { - case SPA_IO_Buffers: - port->io = data; - break; - default: - return -ENOENT; - } - return 0; -} - -static void recycle_buffer(struct impl *this, uint32_t id) -{ - struct port *port = GET_OUT_PORT(this, 0); - struct buffer *b = &port->buffers[id]; - - if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { - spa_list_append(&port->queue, &b->link); - SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); - spa_log_trace_fp(this->log, "%p: recycle buffer %d", this, id); - } -} - -static struct buffer *dequeue_buffer(struct impl *this, struct port *port) -{ - struct buffer *b; - - if (spa_list_is_empty(&port->queue)) - return NULL; - - b = spa_list_first(&port->queue, struct buffer, link); - spa_list_remove(&b->link); - SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); - - return b; -} - - -static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); - - recycle_buffer(this, buffer_id); - - return 0; -} - -static int channelmix_process_control(struct impl *this, struct port *ctrlport, - void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - struct spa_pod_control *c, *prev = NULL; - uint32_t avail_samples = n_samples; - uint32_t i; - const float **s = (const float **)src; - float **d = (float **)dst; - - SPA_POD_SEQUENCE_FOREACH(ctrlport->ctrl, c) { - uint32_t chunk; - - if (avail_samples == 0) - return 0; - - /* ignore old control offsets */ - if (c->offset <= ctrlport->ctrl_offset) { - prev = c; - continue; - } - - switch (c->type) { - case SPA_CONTROL_Midi: - { - if (prev) - apply_midi(this, &prev->value); - break; - } - case SPA_CONTROL_Properties: - { - if (prev) - apply_props(this, &prev->value); - break; - } - default: - continue; - } - - chunk = SPA_MIN(avail_samples, c->offset - ctrlport->ctrl_offset); - - spa_log_trace_fp(this->log, "%p: process %d %d", this, - c->offset, chunk); - - channelmix_process(&this->mix, dst, src, chunk); - for (i = 0; i < this->mix.src_chan; i++) - s[i] += chunk; - for (i = 0; i < this->mix.dst_chan; i++) - d[i] += chunk; - - avail_samples -= chunk; - ctrlport->ctrl_offset += chunk; - - prev = c; - } - - /* when we get here we run out of control points but still have some - * remaining samples */ - spa_log_trace_fp(this->log, "%p: remain %d", this, avail_samples); - if (avail_samples > 0) - channelmix_process(&this->mix, dst, src, avail_samples); - - return 1; -} - -static int impl_node_process(void *object) -{ - struct impl *this = object; - struct port *outport, *inport, *ctrlport; - struct spa_io_buffers *outio, *inio, *ctrlio; - struct buffer *sbuf, *dbuf; - struct spa_pod_sequence *ctrl = NULL; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - outport = GET_OUT_PORT(this, 0); - inport = GET_IN_PORT(this, 0); - ctrlport = GET_CONTROL_PORT(this, 1); - - outio = outport->io; - inio = inport->io; - ctrlio = ctrlport->io; - - spa_return_val_if_fail(outio != NULL, -EIO); - spa_return_val_if_fail(inio != NULL, -EIO); - - spa_log_trace_fp(this->log, "%p: status %p %d %d -> %p %d %d", this, - inio, inio->status, inio->buffer_id, - outio, outio->status, outio->buffer_id); - - if (SPA_UNLIKELY(outio->status == SPA_STATUS_HAVE_DATA)) - return SPA_STATUS_HAVE_DATA; - /* recycle */ - if (SPA_LIKELY(outio->buffer_id < outport->n_buffers)) { - recycle_buffer(this, outio->buffer_id); - outio->buffer_id = SPA_ID_INVALID; - } - if (SPA_UNLIKELY(inio->status != SPA_STATUS_HAVE_DATA)) - return outio->status = inio->status; - - if (SPA_UNLIKELY(inio->buffer_id >= inport->n_buffers)) - return inio->status = -EINVAL; - - if (SPA_UNLIKELY((dbuf = dequeue_buffer(this, outport)) == NULL)) - return outio->status = -EPIPE; - - sbuf = &inport->buffers[inio->buffer_id]; - - if (ctrlio != NULL && - ctrlio->status == SPA_STATUS_HAVE_DATA && - ctrlio->buffer_id < ctrlport->n_buffers) { - struct buffer *cbuf = &ctrlport->buffers[ctrlio->buffer_id]; - struct spa_data *d = &cbuf->outbuf->datas[0]; - - ctrl = spa_pod_from_data(d->data, d->maxsize, d->chunk->offset, d->chunk->size); - if (ctrl && !spa_pod_is_sequence(&ctrl->pod)) - ctrl = NULL; - if (ctrl != ctrlport->ctrl) { - ctrlport->ctrl = ctrl; - ctrlport->ctrl_offset = 0; - } - } - - { - uint32_t i, n_samples; - struct spa_buffer *sb = sbuf->outbuf, *db = dbuf->outbuf; - uint32_t n_src_datas = sb->n_datas; - uint32_t n_dst_datas = db->n_datas; - const void *src_datas[n_src_datas]; - void *dst_datas[n_dst_datas]; - bool is_passthrough; - - is_passthrough = this->is_passthrough && - SPA_FLAG_IS_SET(this->mix.flags, CHANNELMIX_FLAG_IDENTITY) && - ctrlport->ctrl == NULL; - - n_samples = sb->datas[0].chunk->size / inport->stride; - - for (i = 0; i < n_src_datas; i++) - src_datas[i] = sb->datas[i].data; - for (i = 0; i < n_dst_datas; i++) { - dst_datas[i] = is_passthrough ? (void*)src_datas[i] : dbuf->datas[i]; - db->datas[i].data = dst_datas[i]; - db->datas[i].chunk->size = n_samples * outport->stride; - } - - spa_log_trace_fp(this->log, "%p: n_src:%d n_dst:%d n_samples:%d p:%d", - this, n_src_datas, n_dst_datas, n_samples, is_passthrough); - - if (!is_passthrough) { - if (ctrlport->ctrl != NULL) { - /* if return value is 1, the sequence has been processed */ - if (channelmix_process_control(this, ctrlport, dst_datas, - src_datas, n_samples) == 1) { - ctrlio->status = SPA_STATUS_OK; - ctrlport->ctrl = NULL; - } - } else { - channelmix_process(&this->mix, dst_datas, - src_datas, n_samples); - } - } - } - - outio->status = SPA_STATUS_HAVE_DATA; - outio->buffer_id = dbuf->id; - - inio->status = SPA_STATUS_NEED_DATA; - - return SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA; -} - -static const struct spa_node_methods impl_node = { - SPA_VERSION_NODE_METHODS, - .add_listener = impl_node_add_listener, - .set_callbacks = impl_node_set_callbacks, - .enum_params = impl_node_enum_params, - .set_param = impl_node_set_param, - .set_io = impl_node_set_io, - .send_command = impl_node_send_command, - .add_port = impl_node_add_port, - .remove_port = impl_node_remove_port, - .port_enum_params = impl_node_port_enum_params, - .port_set_param = impl_node_port_set_param, - .port_use_buffers = impl_node_port_use_buffers, - .port_set_io = impl_node_port_set_io, - .port_reuse_buffer = impl_node_port_reuse_buffer, - .process = impl_node_process, -}; - -static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) -{ - struct impl *this; - - spa_return_val_if_fail(handle != NULL, -EINVAL); - spa_return_val_if_fail(interface != NULL, -EINVAL); - - this = (struct impl *) handle; - - if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) - *interface = &this->node; - else - return -ENOENT; - - return 0; -} - -static int impl_clear(struct spa_handle *handle) -{ - return 0; -} - -static size_t -impl_get_size(const struct spa_handle_factory *factory, - const struct spa_dict *params) -{ - 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, - const struct spa_dict *info, - const struct spa_support *support, - uint32_t n_support) -{ - struct impl *this; - struct port *port; - uint32_t i; - - spa_return_val_if_fail(factory != NULL, -EINVAL); - spa_return_val_if_fail(handle != NULL, -EINVAL); - - handle->get_interface = impl_get_interface; - handle->clear = impl_clear; - - 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 (this->cpu) { - this->cpu_flags = spa_cpu_get_flags(this->cpu); - this->max_align = SPA_MIN(MAX_ALIGN, spa_cpu_get_max_align(this->cpu)); - } - - spa_hook_list_init(&this->hooks); - - props_reset(&this->props); - - this->mix.options = CHANNELMIX_OPTION_UPMIX; - this->mix.upmix = CHANNELMIX_UPMIX_PSD; - this->mix.log = this->log; - this->mix.lfe_cutoff = 150.0f; - this->mix.fc_cutoff = 12000.0f; - this->mix.rear_delay = 12.0f; - this->mix.widen = 0.0f; - - for (i = 0; info && i < info->n_items; i++) { - const char *k = info->items[i].key; - const char *s = info->items[i].value; - if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) - this->props.n_channels = parse_position(this->props.channel_map, s, strlen(s)); - else if (spa_streq(k, "clock.quantum-limit")) - spa_atou32(s, &this->quantum_limit, 0); - else if (spa_streq(k, "factory.mode")) { - if (spa_streq(s, "merge")) - this->direction = SPA_DIRECTION_OUTPUT; - else - this->direction = SPA_DIRECTION_INPUT; - } - else - channelmix_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->node.iface = SPA_INTERFACE_INIT( - SPA_TYPE_INTERFACE_Node, - SPA_VERSION_NODE, - &impl_node, 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; - this->info.max_input_ports = 2; - this->info.max_output_ports = 1; - 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->info.params = this->params; - this->info.n_params = 2; - - port = GET_OUT_PORT(this, 0); - port->direction = SPA_DIRECTION_OUTPUT; - port->id = 0; - port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | - SPA_PORT_CHANGE_MASK_PARAMS; - port->info = SPA_PORT_INFO_INIT(); - port->info.flags = SPA_PORT_FLAG_DYNAMIC_DATA; - port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); - port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); - port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); - port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - port->info.params = port->params; - port->info.n_params = 5; - spa_list_init(&port->queue); - - port = GET_IN_PORT(this, 0); - port->direction = SPA_DIRECTION_INPUT; - port->id = 0; - port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | - SPA_PORT_CHANGE_MASK_PARAMS; - port->info = SPA_PORT_INFO_INIT(); - port->info.flags = SPA_PORT_FLAG_NO_REF | - SPA_PORT_FLAG_DYNAMIC_DATA; - port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); - port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); - port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); - port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - port->info.params = port->params; - port->info.n_params = 0; - spa_list_init(&port->queue); - - port = GET_CONTROL_PORT(this, 1); - port->direction = SPA_DIRECTION_INPUT; - port->id = 1; - port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | - SPA_PORT_CHANGE_MASK_PROPS | - SPA_PORT_CHANGE_MASK_PARAMS; - port->info = SPA_PORT_INFO_INIT(); - port->info.flags = SPA_PORT_FLAG_NO_REF | - SPA_PORT_FLAG_DYNAMIC_DATA; - port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); - port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, 0); - port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); - port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - port->info.params = port->params; - port->info.n_params = 4; - spa_list_init(&port->queue); - - return 0; -} - -static const struct spa_interface_info impl_interfaces[] = { - {SPA_TYPE_INTERFACE_Node,}, -}; - -static int -impl_enum_interface_info(const struct spa_handle_factory *factory, - const struct spa_interface_info **info, - uint32_t *index) -{ - spa_return_val_if_fail(factory != NULL, -EINVAL); - spa_return_val_if_fail(info != NULL, -EINVAL); - spa_return_val_if_fail(index != NULL, -EINVAL); - - switch (*index) { - case 0: - *info = &impl_interfaces[*index]; - break; - default: - return 0; - } - (*index)++; - return 1; -} - -const struct spa_handle_factory spa_channelmix_factory = { - SPA_VERSION_HANDLE_FACTORY, - SPA_NAME_AUDIO_PROCESS_CHANNELMIX, - NULL, - impl_get_size, - impl_init, - impl_enum_interface_info, -}; diff --git a/spa/plugins/audioconvert/crossover.c b/spa/plugins/audioconvert/crossover.c index 1ce1d221d8161611cb74b5d779baaec5930138b5..7575833cdbe5e96759144865ce92b3224008706c 100644 --- a/spa/plugins/audioconvert/crossover.c +++ b/spa/plugins/audioconvert/crossover.c @@ -3,8 +3,6 @@ * found in the LICENSE file. */ -#include "config.h" - #include <float.h> #include <string.h> diff --git a/spa/plugins/audioconvert/fmt-ops-avx2.c b/spa/plugins/audioconvert/fmt-ops-avx2.c index 065fa997ebc938716f137a47b105a127a577e5a5..723aea369bc67f5959c7e5b99fe30707d902464e 100644 --- a/spa/plugins/audioconvert/fmt-ops-avx2.c +++ b/spa/plugins/audioconvert/fmt-ops-avx2.c @@ -34,6 +34,15 @@ # define _mm256_setr_m128i(v0, v1) _mm256_set_m128i((v1), (v0)) #endif +#define _MM_CLAMP_PS(r,min,max) \ + _mm_min_ps(_mm_max_ps(r, min), max) + +#define _MM256_CLAMP_PS(r,min,max) \ + _mm256_min_ps(_mm256_max_ps(r, min), max) + +#define _MM_CLAMP_SS(r,min,max) \ + _mm_min_ss(_mm_max_ss(r, min), max) + static void conv_s16_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) @@ -41,7 +50,7 @@ conv_s16_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA const int16_t *s = src; float *d0 = dst[0]; uint32_t n, unrolled; - __m256i in; + __m256i in = _mm256_setzero_si256(); __m256 out, factor = _mm256_set1_ps(1.0f / S16_SCALE); if (SPA_LIKELY(SPA_IS_ALIGNED(d0, 32))) @@ -67,7 +76,7 @@ conv_s16_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA } for(; n < n_samples; n++) { __m128 out, factor = _mm_set1_ps(1.0f / S16_SCALE); - out = _mm_cvtsi32_ss(out, s[0]); + out = _mm_cvtsi32_ss(factor, s[0]); out = _mm_mul_ss(out, factor); _mm_store_ss(&d0[n], out); s += n_channels; @@ -133,9 +142,9 @@ conv_s16_to_f32d_2_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const v } for(; n < n_samples; n++) { __m128 out[4], factor = _mm_set1_ps(1.0f / S16_SCALE); - out[0] = _mm_cvtsi32_ss(out[0], s[0]); + out[0] = _mm_cvtsi32_ss(factor, s[0]); out[0] = _mm_mul_ss(out[0], factor); - out[1] = _mm_cvtsi32_ss(out[1], s[1]); + out[1] = _mm_cvtsi32_ss(factor, s[1]); out[1] = _mm_mul_ss(out[1], factor); _mm_store_ss(&d0[n], out[0]); _mm_store_ss(&d1[n], out[1]); @@ -147,7 +156,7 @@ void conv_s24_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { - const uint8_t *s = src; + const int24_t *s = src; float *d0 = dst[0]; uint32_t n, unrolled; __m128i in; @@ -164,21 +173,21 @@ conv_s24_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA for(n = 0; n < unrolled; n += 4) { in = _mm_setr_epi32( *((uint32_t*)&s[0 * n_channels]), - *((uint32_t*)&s[3 * n_channels]), - *((uint32_t*)&s[6 * n_channels]), - *((uint32_t*)&s[9 * n_channels])); + *((uint32_t*)&s[1 * n_channels]), + *((uint32_t*)&s[2 * n_channels]), + *((uint32_t*)&s[3 * n_channels])); in = _mm_slli_epi32(in, 8); in = _mm_srai_epi32(in, 8); out = _mm_cvtepi32_ps(in); out = _mm_mul_ps(out, factor); _mm_store_ps(&d0[n], out); - s += 12 * n_channels; + s += 4 * n_channels; } for(; n < n_samples; n++) { - out = _mm_cvtsi32_ss(out, read_s24(s)); + out = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); out = _mm_mul_ss(out, factor); _mm_store_ss(&d0[n], out); - s += 3 * n_channels; + s += n_channels; } } @@ -186,7 +195,7 @@ static void conv_s24_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { - const uint8_t *s = src; + const int24_t *s = src; float *d0 = dst[0], *d1 = dst[1]; uint32_t n, unrolled; __m128i in[2]; @@ -205,14 +214,14 @@ conv_s24_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA for(n = 0; n < unrolled; n += 4) { in[0] = _mm_setr_epi32( *((uint32_t*)&s[0 + 0*n_channels]), - *((uint32_t*)&s[0 + 3*n_channels]), - *((uint32_t*)&s[0 + 6*n_channels]), - *((uint32_t*)&s[0 + 9*n_channels])); + *((uint32_t*)&s[0 + 1*n_channels]), + *((uint32_t*)&s[0 + 2*n_channels]), + *((uint32_t*)&s[0 + 3*n_channels])); in[1] = _mm_setr_epi32( - *((uint32_t*)&s[3 + 0*n_channels]), - *((uint32_t*)&s[3 + 3*n_channels]), - *((uint32_t*)&s[3 + 6*n_channels]), - *((uint32_t*)&s[3 + 9*n_channels])); + *((uint32_t*)&s[1 + 0*n_channels]), + *((uint32_t*)&s[1 + 1*n_channels]), + *((uint32_t*)&s[1 + 2*n_channels]), + *((uint32_t*)&s[1 + 3*n_channels])); in[0] = _mm_slli_epi32(in[0], 8); in[1] = _mm_slli_epi32(in[1], 8); @@ -229,23 +238,23 @@ conv_s24_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA _mm_store_ps(&d0[n], out[0]); _mm_store_ps(&d1[n], out[1]); - s += 12 * n_channels; + s += 4 * n_channels; } for(; n < n_samples; n++) { - out[0] = _mm_cvtsi32_ss(out[0], read_s24(s)); - out[1] = _mm_cvtsi32_ss(out[1], read_s24(s+3)); + out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); + out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+1))); out[0] = _mm_mul_ss(out[0], factor); out[1] = _mm_mul_ss(out[1], factor); _mm_store_ss(&d0[n], out[0]); _mm_store_ss(&d1[n], out[1]); - s += 3 * n_channels; + s += n_channels; } } static void conv_s24_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { - const uint8_t *s = src; + const int24_t *s = src; float *d0 = dst[0], *d1 = dst[1], *d2 = dst[2], *d3 = dst[3]; uint32_t n, unrolled; __m128i in[4]; @@ -266,24 +275,24 @@ conv_s24_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA for(n = 0; n < unrolled; n += 4) { in[0] = _mm_setr_epi32( *((uint32_t*)&s[0 + 0*n_channels]), - *((uint32_t*)&s[0 + 3*n_channels]), - *((uint32_t*)&s[0 + 6*n_channels]), - *((uint32_t*)&s[0 + 9*n_channels])); + *((uint32_t*)&s[0 + 1*n_channels]), + *((uint32_t*)&s[0 + 2*n_channels]), + *((uint32_t*)&s[0 + 3*n_channels])); in[1] = _mm_setr_epi32( - *((uint32_t*)&s[3 + 0*n_channels]), - *((uint32_t*)&s[3 + 3*n_channels]), - *((uint32_t*)&s[3 + 6*n_channels]), - *((uint32_t*)&s[3 + 9*n_channels])); + *((uint32_t*)&s[1 + 0*n_channels]), + *((uint32_t*)&s[1 + 1*n_channels]), + *((uint32_t*)&s[1 + 2*n_channels]), + *((uint32_t*)&s[1 + 3*n_channels])); in[2] = _mm_setr_epi32( - *((uint32_t*)&s[6 + 0*n_channels]), - *((uint32_t*)&s[6 + 3*n_channels]), - *((uint32_t*)&s[6 + 6*n_channels]), - *((uint32_t*)&s[6 + 9*n_channels])); + *((uint32_t*)&s[2 + 0*n_channels]), + *((uint32_t*)&s[2 + 1*n_channels]), + *((uint32_t*)&s[2 + 2*n_channels]), + *((uint32_t*)&s[2 + 3*n_channels])); in[3] = _mm_setr_epi32( - *((uint32_t*)&s[9 + 0*n_channels]), - *((uint32_t*)&s[9 + 3*n_channels]), - *((uint32_t*)&s[9 + 6*n_channels]), - *((uint32_t*)&s[9 + 9*n_channels])); + *((uint32_t*)&s[3 + 0*n_channels]), + *((uint32_t*)&s[3 + 1*n_channels]), + *((uint32_t*)&s[3 + 2*n_channels]), + *((uint32_t*)&s[3 + 3*n_channels])); in[0] = _mm_slli_epi32(in[0], 8); in[1] = _mm_slli_epi32(in[1], 8); @@ -310,13 +319,13 @@ conv_s24_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA _mm_store_ps(&d2[n], out[2]); _mm_store_ps(&d3[n], out[3]); - s += 12 * n_channels; + s += 4 * n_channels; } for(; n < n_samples; n++) { - out[0] = _mm_cvtsi32_ss(out[0], read_s24(s)); - out[1] = _mm_cvtsi32_ss(out[1], read_s24(s+3)); - out[2] = _mm_cvtsi32_ss(out[2], read_s24(s+6)); - out[3] = _mm_cvtsi32_ss(out[3], read_s24(s+9)); + out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); + out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+1))); + out[2] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+2))); + out[3] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+3))); out[0] = _mm_mul_ss(out[0], factor); out[1] = _mm_mul_ss(out[1], factor); out[2] = _mm_mul_ss(out[2], factor); @@ -325,7 +334,7 @@ conv_s24_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA _mm_store_ss(&d1[n], out[1]); _mm_store_ss(&d2[n], out[2]); _mm_store_ss(&d3[n], out[3]); - s += 3 * n_channels; + s += n_channels; } } @@ -373,11 +382,6 @@ conv_s32_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA in[2] = _mm256_i64gather_epi64((long long int *)&s[0*n_channels], mask3, 4); in[3] = _mm256_i64gather_epi64((long long int *)&s[0*n_channels], mask4, 4); - in[0] = _mm256_srai_epi32(in[0], 8); /* a0 b0 c0 d0 a4 b4 c4 d4 */ - in[1] = _mm256_srai_epi32(in[1], 8); /* a1 b1 c1 d1 a5 b5 c5 d5 */ - in[2] = _mm256_srai_epi32(in[2], 8); /* a2 b2 c2 d2 a6 b6 c6 d6 */ - in[3] = _mm256_srai_epi32(in[3], 8); /* a3 b3 c3 d3 a7 b7 c7 d7 */ - t[0] = _mm256_unpacklo_epi32(in[0], in[1]); /* a0 a1 b0 b1 a4 a5 b4 b5 */ t[1] = _mm256_unpackhi_epi32(in[0], in[1]); /* c0 c1 d0 d1 c4 c5 d4 d5 */ t[2] = _mm256_unpacklo_epi32(in[2], in[3]); /* a2 a3 b2 b3 a6 a7 b6 b7 */ @@ -387,6 +391,11 @@ conv_s32_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA in[2] = _mm256_unpacklo_epi64(t[1], t[3]); /* c0 c1 c2 c3 c4 c5 c6 c7 */ in[3] = _mm256_unpackhi_epi64(t[1], t[3]); /* d0 d1 d2 d3 d4 d5 d6 d7 */ + in[0] = _mm256_srai_epi32(in[0], 8); + in[1] = _mm256_srai_epi32(in[1], 8); + in[2] = _mm256_srai_epi32(in[2], 8); + in[3] = _mm256_srai_epi32(in[3], 8); + out[0] = _mm256_cvtepi32_ps(in[0]); out[1] = _mm256_cvtepi32_ps(in[1]); out[2] = _mm256_cvtepi32_ps(in[2]); @@ -406,10 +415,10 @@ conv_s32_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA } for(; n < n_samples; n++) { __m128 out[4], factor = _mm_set1_ps(1.0f / S24_SCALE); - out[0] = _mm_cvtsi32_ss(out[0], s[0]>>8); - out[1] = _mm_cvtsi32_ss(out[1], s[1]>>8); - out[2] = _mm_cvtsi32_ss(out[2], s[2]>>8); - out[3] = _mm_cvtsi32_ss(out[3], s[3]>>8); + out[0] = _mm_cvtsi32_ss(factor, s[0] >> 8); + out[1] = _mm_cvtsi32_ss(factor, s[1] >> 8); + out[2] = _mm_cvtsi32_ss(factor, s[2] >> 8); + out[3] = _mm_cvtsi32_ss(factor, s[3] >> 8); out[0] = _mm_mul_ss(out[0], factor); out[1] = _mm_mul_ss(out[1], factor); out[2] = _mm_mul_ss(out[2], factor); @@ -445,15 +454,15 @@ conv_s32_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA in[0] = _mm256_i64gather_epi64((long long int *)s, mask1, 4); in[1] = _mm256_i64gather_epi64((long long int *)s, mask2, 4); - in[0] = _mm256_srai_epi32(in[0], 8); - in[1] = _mm256_srai_epi32(in[1], 8); - t[0] = _mm256_permutevar8x32_epi32(in[0], perm); t[1] = _mm256_permutevar8x32_epi32(in[1], perm); in[0] = _mm256_permute2x128_si256(t[0], t[1], 0 | (2 << 4)); in[1] = _mm256_permute2x128_si256(t[0], t[1], 1 | (3 << 4)); + in[0] = _mm256_srai_epi32(in[0], 8); + in[1] = _mm256_srai_epi32(in[1], 8); + out[0] = _mm256_cvtepi32_ps(in[0]); out[1] = _mm256_cvtepi32_ps(in[1]); @@ -467,8 +476,8 @@ conv_s32_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA } for(; n < n_samples; n++) { __m128 out[2], factor = _mm_set1_ps(1.0f / S24_SCALE); - out[0] = _mm_cvtsi32_ss(out[0], s[0]>>8); - out[1] = _mm_cvtsi32_ss(out[1], s[1]>>8); + out[0] = _mm_cvtsi32_ss(factor, s[0] >> 8); + out[1] = _mm_cvtsi32_ss(factor, s[1] >> 8); out[0] = _mm_mul_ss(out[0], factor); out[1] = _mm_mul_ss(out[1], factor); _mm_store_ss(&d0[n], out[0]); @@ -518,7 +527,7 @@ conv_s32_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA } for(; n < n_samples; n++) { __m128 out, factor = _mm_set1_ps(1.0f / S24_SCALE); - out = _mm_cvtsi32_ss(out, s[0]>>8); + out = _mm_cvtsi32_ss(factor, s[0] >> 8); out = _mm_mul_ss(out, factor); _mm_store_ss(&d0[n], out); s += n_channels; @@ -549,8 +558,9 @@ conv_f32d_to_s32_1s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R uint32_t n, unrolled; __m128 in[1]; __m128i out[4]; - __m128 scale = _mm_set1_ps(S32_SCALE); - __m128 int_min = _mm_set1_ps(S32_MIN); + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_max = _mm_set1_ps(S24_MAX); + __m128 int_min = _mm_set1_ps(S24_MIN); if (SPA_IS_ALIGNED(s0, 16)) unrolled = n_samples & ~3; @@ -559,8 +569,9 @@ conv_f32d_to_s32_1s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R for(n = 0; n < unrolled; n += 4) { in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), scale); - in[0] = _mm_min_ps(in[0], int_min); + in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); out[0] = _mm_cvtps_epi32(in[0]); + out[0] = _mm_slli_epi32(out[0], 8); out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1)); out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2)); out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3)); @@ -574,8 +585,8 @@ conv_f32d_to_s32_1s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R for(; n < n_samples; n++) { in[0] = _mm_load_ss(&s0[n]); in[0] = _mm_mul_ss(in[0], scale); - in[0] = _mm_min_ss(in[0], int_min); - *d = _mm_cvtss_si32(in[0]); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + *d = _mm_cvtss_si32(in[0]) << 8; d += n_channels; } } @@ -589,8 +600,9 @@ conv_f32d_to_s32_2s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R uint32_t n, unrolled; __m256 in[2]; __m256i out[2], t[2]; - __m256 scale = _mm256_set1_ps(S32_SCALE); - __m256 int_min = _mm256_set1_ps(S32_MIN); + __m256 scale = _mm256_set1_ps(S24_SCALE); + __m256 int_min = _mm256_set1_ps(S24_MIN); + __m256 int_max = _mm256_set1_ps(S24_MAX); if (SPA_IS_ALIGNED(s0, 32) && SPA_IS_ALIGNED(s1, 32)) @@ -602,11 +614,13 @@ conv_f32d_to_s32_2s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n]), scale); in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n]), scale); - in[0] = _mm256_min_ps(in[0], int_min); - in[1] = _mm256_min_ps(in[1], int_min); + in[0] = _MM256_CLAMP_PS(in[0], int_min, int_max); + in[1] = _MM256_CLAMP_PS(in[1], int_min, int_max); out[0] = _mm256_cvtps_epi32(in[0]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ out[1] = _mm256_cvtps_epi32(in[1]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ + out[0] = _mm256_slli_epi32(out[0], 8); + out[1] = _mm256_slli_epi32(out[1], 8); t[0] = _mm256_unpacklo_epi32(out[0], out[1]); /* a0 b0 a1 b1 a4 b4 a5 b5 */ t[1] = _mm256_unpackhi_epi32(out[0], out[1]); /* a2 b2 a3 b3 a6 b6 a7 b7 */ @@ -635,8 +649,9 @@ conv_f32d_to_s32_2s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R for(; n < n_samples; n++) { __m128 in[2]; __m128i out[2]; - __m128 scale = _mm_set1_ps(S32_SCALE); - __m128 int_min = _mm_set1_ps(S32_MIN); + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_min = _mm_set1_ps(S24_MIN); + __m128 int_max = _mm_set1_ps(S24_MAX); in[0] = _mm_load_ss(&s0[n]); in[1] = _mm_load_ss(&s1[n]); @@ -644,8 +659,9 @@ conv_f32d_to_s32_2s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R in[0] = _mm_unpacklo_ps(in[0], in[1]); in[0] = _mm_mul_ps(in[0], scale); - in[0] = _mm_min_ps(in[0], int_min); + in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); out[0] = _mm_cvtps_epi32(in[0]); + out[0] = _mm_slli_epi32(out[0], 8); _mm_storel_epi64((__m128i*)d, out[0]); d += n_channels; } @@ -660,8 +676,9 @@ conv_f32d_to_s32_4s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R uint32_t n, unrolled; __m256 in[4]; __m256i out[4], t[4]; - __m256 scale = _mm256_set1_ps(S32_SCALE); - __m256 int_min = _mm256_set1_ps(S32_MIN); + __m256 scale = _mm256_set1_ps(S24_SCALE); + __m256 int_min = _mm256_set1_ps(S24_MIN); + __m256 int_max = _mm256_set1_ps(S24_MAX); if (SPA_IS_ALIGNED(s0, 32) && SPA_IS_ALIGNED(s1, 32) && @@ -677,15 +694,19 @@ conv_f32d_to_s32_4s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R in[2] = _mm256_mul_ps(_mm256_load_ps(&s2[n]), scale); in[3] = _mm256_mul_ps(_mm256_load_ps(&s3[n]), scale); - in[0] = _mm256_min_ps(in[0], int_min); - in[1] = _mm256_min_ps(in[1], int_min); - in[2] = _mm256_min_ps(in[2], int_min); - in[3] = _mm256_min_ps(in[3], int_min); + in[0] = _MM256_CLAMP_PS(in[0], int_min, int_max); + in[1] = _MM256_CLAMP_PS(in[1], int_min, int_max); + in[2] = _MM256_CLAMP_PS(in[2], int_min, int_max); + in[3] = _MM256_CLAMP_PS(in[3], int_min, int_max); out[0] = _mm256_cvtps_epi32(in[0]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ out[1] = _mm256_cvtps_epi32(in[1]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ out[2] = _mm256_cvtps_epi32(in[2]); /* c0 c1 c2 c3 c4 c5 c6 c7 */ out[3] = _mm256_cvtps_epi32(in[3]); /* d0 d1 d2 d3 d4 d5 d6 d7 */ + out[0] = _mm256_slli_epi32(out[0], 8); + out[1] = _mm256_slli_epi32(out[1], 8); + out[2] = _mm256_slli_epi32(out[2], 8); + out[3] = _mm256_slli_epi32(out[3], 8); t[0] = _mm256_unpacklo_epi32(out[0], out[1]); /* a0 b0 a1 b1 a4 b4 a5 b5 */ t[1] = _mm256_unpackhi_epi32(out[0], out[1]); /* a2 b2 a3 b3 a6 b6 a7 b7 */ @@ -710,8 +731,9 @@ conv_f32d_to_s32_4s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R for(; n < n_samples; n++) { __m128 in[4]; __m128i out[4]; - __m128 scale = _mm_set1_ps(S32_SCALE); - __m128 int_min = _mm_set1_ps(S32_MIN); + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_min = _mm_set1_ps(S24_MIN); + __m128 int_max = _mm_set1_ps(S24_MAX); in[0] = _mm_load_ss(&s0[n]); in[1] = _mm_load_ss(&s1[n]); @@ -723,8 +745,9 @@ conv_f32d_to_s32_4s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R in[0] = _mm_unpacklo_ps(in[0], in[1]); in[0] = _mm_mul_ps(in[0], scale); - in[0] = _mm_min_ps(in[0], int_min); + in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); out[0] = _mm_cvtps_epi32(in[0]); + out[0] = _mm_slli_epi32(out[0], 8); _mm_storeu_si128((__m128i*)d, out[0]); d += n_channels; } @@ -754,8 +777,9 @@ conv_f32d_to_s16_1s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R uint32_t n, unrolled; __m128 in[2]; __m128i out[2]; - __m128 int_max = _mm_set1_ps(S16_MAX_F); - __m128 int_min = _mm_sub_ps(_mm_setzero_ps(), int_max); + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); if (SPA_IS_ALIGNED(s0, 16)) unrolled = n_samples & ~7; @@ -763,8 +787,8 @@ conv_f32d_to_s16_1s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R unrolled = 0; for(n = 0; n < unrolled; n += 8) { - in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), int_max); - in[1] = _mm_mul_ps(_mm_load_ps(&s0[n+4]), int_max); + in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), int_scale); + in[1] = _mm_mul_ps(_mm_load_ps(&s0[n+4]), int_scale); out[0] = _mm_cvtps_epi32(in[0]); out[1] = _mm_cvtps_epi32(in[1]); out[0] = _mm_packs_epi32(out[0], out[1]); @@ -780,8 +804,8 @@ conv_f32d_to_s16_1s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R d += 8*n_channels; } for(; n < n_samples; n++) { - in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_max); - in[0] = _mm_min_ss(int_max, _mm_max_ss(in[0], int_min)); + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); *d = _mm_cvtss_si32(in[0]); d += n_channels; } @@ -796,7 +820,7 @@ conv_f32d_to_s16_2s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R uint32_t n, unrolled; __m256 in[2]; __m256i out[4], t[2]; - __m256 int_max = _mm256_set1_ps(S16_MAX_F); + __m256 int_scale = _mm256_set1_ps(S16_SCALE); if (SPA_IS_ALIGNED(s0, 32) && SPA_IS_ALIGNED(s1, 32)) @@ -805,8 +829,8 @@ conv_f32d_to_s16_2s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R unrolled = 0; for(n = 0; n < unrolled; n += 8) { - in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n+0]), int_max); - in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n+0]), int_max); + in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n+0]), int_scale); + in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n+0]), int_scale); out[0] = _mm256_cvtps_epi32(in[0]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ out[1] = _mm256_cvtps_epi32(in[1]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ @@ -829,13 +853,14 @@ conv_f32d_to_s16_2s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R } for(; n < n_samples; n++) { __m128 in[2]; - __m128 int_max = _mm_set1_ps(S16_MAX_F); - __m128 int_min = _mm_sub_ps(_mm_setzero_ps(), int_max); - - in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_max); - in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_max); - in[0] = _mm_min_ss(int_max, _mm_max_ss(in[0], int_min)); - in[1] = _mm_min_ss(int_max, _mm_max_ss(in[1], int_min)); + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); + + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); d[0] = _mm_cvtss_si32(in[0]); d[1] = _mm_cvtss_si32(in[1]); d += n_channels; @@ -851,7 +876,7 @@ conv_f32d_to_s16_4s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R uint32_t n, unrolled; __m256 in[4]; __m256i out[4], t[4]; - __m256 int_max = _mm256_set1_ps(S16_MAX_F); + __m256 int_scale = _mm256_set1_ps(S16_SCALE); if (SPA_IS_ALIGNED(s0, 32) && SPA_IS_ALIGNED(s1, 32) && @@ -862,10 +887,10 @@ conv_f32d_to_s16_4s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R unrolled = 0; for(n = 0; n < unrolled; n += 8) { - in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n]), int_max); - in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n]), int_max); - in[2] = _mm256_mul_ps(_mm256_load_ps(&s2[n]), int_max); - in[3] = _mm256_mul_ps(_mm256_load_ps(&s3[n]), int_max); + in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n]), int_scale); + in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n]), int_scale); + in[2] = _mm256_mul_ps(_mm256_load_ps(&s2[n]), int_scale); + in[3] = _mm256_mul_ps(_mm256_load_ps(&s3[n]), int_scale); t[0] = _mm256_cvtps_epi32(in[0]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ t[1] = _mm256_cvtps_epi32(in[1]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ @@ -905,17 +930,18 @@ conv_f32d_to_s16_4s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R } for(; n < n_samples; n++) { __m128 in[4]; - __m128 int_max = _mm_set1_ps(S16_MAX_F); - __m128 int_min = _mm_sub_ps(_mm_setzero_ps(), int_max); - - in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_max); - in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_max); - in[2] = _mm_mul_ss(_mm_load_ss(&s2[n]), int_max); - in[3] = _mm_mul_ss(_mm_load_ss(&s3[n]), int_max); - in[0] = _mm_min_ss(int_max, _mm_max_ss(in[0], int_min)); - in[1] = _mm_min_ss(int_max, _mm_max_ss(in[1], int_min)); - in[2] = _mm_min_ss(int_max, _mm_max_ss(in[2], int_min)); - in[3] = _mm_min_ss(int_max, _mm_max_ss(in[3], int_min)); + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); + + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); + in[2] = _mm_mul_ss(_mm_load_ss(&s2[n]), int_scale); + in[3] = _mm_mul_ss(_mm_load_ss(&s3[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); + in[2] = _MM_CLAMP_SS(in[2], int_min, int_max); + in[3] = _MM_CLAMP_SS(in[3], int_min, int_max); d[0] = _mm_cvtss_si32(in[0]); d[1] = _mm_cvtss_si32(in[1]); d[2] = _mm_cvtss_si32(in[2]); @@ -948,7 +974,7 @@ conv_f32d_to_s16_4_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const v uint32_t n, unrolled; __m256 in[4]; __m256i out[4], t[4]; - __m256 int_max = _mm256_set1_ps(S16_MAX_F); + __m256 int_scale = _mm256_set1_ps(S16_SCALE); if (SPA_IS_ALIGNED(s0, 32) && SPA_IS_ALIGNED(s1, 32) && @@ -959,10 +985,10 @@ conv_f32d_to_s16_4_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const v unrolled = 0; for(n = 0; n < unrolled; n += 8) { - in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n]), int_max); - in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n]), int_max); - in[2] = _mm256_mul_ps(_mm256_load_ps(&s2[n]), int_max); - in[3] = _mm256_mul_ps(_mm256_load_ps(&s3[n]), int_max); + in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n]), int_scale); + in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n]), int_scale); + in[2] = _mm256_mul_ps(_mm256_load_ps(&s2[n]), int_scale); + in[3] = _mm256_mul_ps(_mm256_load_ps(&s3[n]), int_scale); t[0] = _mm256_cvtps_epi32(in[0]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ t[1] = _mm256_cvtps_epi32(in[1]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ @@ -987,17 +1013,18 @@ conv_f32d_to_s16_4_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const v } for(; n < n_samples; n++) { __m128 in[4]; - __m128 int_max = _mm_set1_ps(S16_MAX_F); - __m128 int_min = _mm_sub_ps(_mm_setzero_ps(), int_max); - - in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_max); - in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_max); - in[2] = _mm_mul_ss(_mm_load_ss(&s2[n]), int_max); - in[3] = _mm_mul_ss(_mm_load_ss(&s3[n]), int_max); - in[0] = _mm_min_ss(int_max, _mm_max_ss(in[0], int_min)); - in[1] = _mm_min_ss(int_max, _mm_max_ss(in[1], int_min)); - in[2] = _mm_min_ss(int_max, _mm_max_ss(in[2], int_min)); - in[3] = _mm_min_ss(int_max, _mm_max_ss(in[3], int_min)); + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); + + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); + in[2] = _mm_mul_ss(_mm_load_ss(&s2[n]), int_scale); + in[3] = _mm_mul_ss(_mm_load_ss(&s3[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); + in[2] = _MM_CLAMP_SS(in[2], int_min, int_max); + in[3] = _MM_CLAMP_SS(in[3], int_min, int_max); d[0] = _mm_cvtss_si32(in[0]); d[1] = _mm_cvtss_si32(in[1]); d[2] = _mm_cvtss_si32(in[2]); @@ -1014,7 +1041,7 @@ conv_f32d_to_s16_2_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const v uint32_t n, unrolled; __m256 in[4]; __m256i out[4], t[4]; - __m256 int_max = _mm256_set1_ps(S16_MAX_F); + __m256 int_scale = _mm256_set1_ps(S16_SCALE); if (SPA_IS_ALIGNED(s0, 32) && SPA_IS_ALIGNED(s1, 32)) @@ -1023,10 +1050,10 @@ conv_f32d_to_s16_2_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const v unrolled = 0; for(n = 0; n < unrolled; n += 16) { - in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n+0]), int_max); - in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n+0]), int_max); - in[2] = _mm256_mul_ps(_mm256_load_ps(&s0[n+8]), int_max); - in[3] = _mm256_mul_ps(_mm256_load_ps(&s1[n+8]), int_max); + in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n+0]), int_scale); + in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n+0]), int_scale); + in[2] = _mm256_mul_ps(_mm256_load_ps(&s0[n+8]), int_scale); + in[3] = _mm256_mul_ps(_mm256_load_ps(&s1[n+8]), int_scale); out[0] = _mm256_cvtps_epi32(in[0]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ out[1] = _mm256_cvtps_epi32(in[1]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ @@ -1048,13 +1075,14 @@ conv_f32d_to_s16_2_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const v } for(; n < n_samples; n++) { __m128 in[4]; - __m128 int_max = _mm_set1_ps(S16_MAX_F); - __m128 int_min = _mm_sub_ps(_mm_setzero_ps(), int_max); - - in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_max); - in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_max); - in[0] = _mm_min_ss(int_max, _mm_max_ss(in[0], int_min)); - in[1] = _mm_min_ss(int_max, _mm_max_ss(in[1], int_min)); + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); + + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); d[0] = _mm_cvtss_si32(in[0]); d[1] = _mm_cvtss_si32(in[1]); d += 2; diff --git a/spa/plugins/audioconvert/fmt-ops-c.c b/spa/plugins/audioconvert/fmt-ops-c.c index 95dfec7c2a0183312e403718b72157e55eff7368..f3d91d0356544404020dc902edc2ad6358f4e9cd 100644 --- a/spa/plugins/audioconvert/fmt-ops-c.c +++ b/spa/plugins/audioconvert/fmt-ops-c.c @@ -33,1499 +33,380 @@ #include "fmt-ops.h" #include "law.h" -void -conv_copy8d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - for (i = 0; i < n_channels; i++) - spa_memcpy(dst[i], src[i], n_samples); -} - -void -conv_copy8_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - spa_memcpy(dst[0], src[0], n_samples * conv->n_channels); -} - - -void -conv_copy16d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - for (i = 0; i < n_channels; i++) - spa_memcpy(dst[i], src[i], n_samples * sizeof(int16_t)); -} - -void -conv_copy16_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - spa_memcpy(dst[0], src[0], n_samples * sizeof(int16_t) * conv->n_channels); -} - -void -conv_copy24d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - for (i = 0; i < n_channels; i++) - spa_memcpy(dst[i], src[i], n_samples * 3); -} - -void -conv_copy24_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - spa_memcpy(dst[0], src[0], n_samples * 3 * conv->n_channels); -} - -void -conv_copy32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - for (i = 0; i < n_channels; i++) - spa_memcpy(dst[i], src[i], n_samples * sizeof(int32_t)); -} - -void -conv_copy32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - spa_memcpy(dst[0], src[0], n_samples * sizeof(int32_t) * conv->n_channels); -} - -void -conv_copy64d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - for (i = 0; i < n_channels; i++) - spa_memcpy(dst[i], src[i], n_samples * sizeof(int64_t)); -} - -void -conv_copy64_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - spa_memcpy(dst[0], src[0], n_samples * sizeof(int64_t) * conv->n_channels); -} - -void -conv_u8d_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, j, n_channels = conv->n_channels; - - for (i = 0; i < n_channels; i++) { - const uint8_t *s = src[i]; - float *d = dst[i]; - - for (j = 0; j < n_samples; j++) - d[j] = U8_TO_F32(s[j]); - } -} - -void -conv_u8_to_f32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const uint8_t *s = src[0]; - float *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) - d[i] = U8_TO_F32(s[i]); -} - -void -conv_u8_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const uint8_t *s = src[0]; - float **d = (float **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = U8_TO_F32(*s++); - } -} - -void -conv_u8d_to_f32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const uint8_t **s = (const uint8_t **) src; - float *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = U8_TO_F32(s[i][j]); - } -} - -void -conv_s8d_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, j, n_channels = conv->n_channels; - - for (i = 0; i < n_channels; i++) { - const int8_t *s = src[i]; - float *d = dst[i]; - for (j = 0; j < n_samples; j++) - d[j] = S8_TO_F32(s[j]); - } -} - -void -conv_s8_to_f32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const int8_t *s = src[0]; - float *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) - d[i] = S8_TO_F32(s[i]); -} - -void -conv_s8_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const int8_t *s = src[0]; - float **d = (float **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = S8_TO_F32(*s++); - } -} - -void -conv_s8d_to_f32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const int8_t **s = (const int8_t **) src; - float *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = S8_TO_F32(s[i][j]); - } -} - -void -conv_alaw_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const uint8_t *s = src[0]; - float **d = (float **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = alaw_to_f32(*s++); - } -} - -void -conv_ulaw_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const uint8_t *s = src[0]; - float **d = (float **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = ulaw_to_f32(*s++); - } -} - -void -conv_u16_to_f32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const uint16_t *s = src[0]; - float *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) - d[i] = U16_TO_F32(s[i]); -} - -void -conv_u16_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const uint16_t *s = src[0]; - float **d = (float **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = U16_TO_F32(*s++); - } -} - -void -conv_s16d_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, j, n_channels = conv->n_channels; - - for (i = 0; i < n_channels; i++) { - const int16_t *s = src[i]; - float *d = dst[i]; - for (j = 0; j < n_samples; j++) - d[j] = S16_TO_F32(s[j]); - } -} - -void -conv_s16_to_f32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const int16_t *s = src[0]; - float *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) - d[i] = S16_TO_F32(s[i]); -} - -void -conv_s16_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const int16_t *s = src[0]; - float **d = (float **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = S16_TO_F32(*s++); - } -} - -void -conv_s16s_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const int16_t *s = src[0]; - float **d = (float **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = S16S_TO_F32(*s++); - } -} - -void -conv_s16d_to_f32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const int16_t **s = (const int16_t **) src; - float *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = S16_TO_F32(s[i][j]); - } -} - -void -conv_u32_to_f32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const uint32_t *s = src[0]; - float *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) - d[i] = U32_TO_F32(s[i]); -} - -void -conv_u32_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const uint32_t *s = src[0]; - float **d = (float **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = U32_TO_F32(*s++); - } -} - -void -conv_s32d_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, j, n_channels = conv->n_channels; - - for (i = 0; i < n_channels; i++) { - const int32_t *s = src[i]; - float *d = dst[i]; - - for (j = 0; j < n_samples; j++) - d[j] = S32_TO_F32(s[j]); - } -} - -void -conv_s32_to_f32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const int32_t *s = src[0]; - float *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) - d[i] = S32_TO_F32(s[i]); -} - -void -conv_s32_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const int32_t *s = src[0]; - float **d = (float **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = S32_TO_F32(*s++); - } -} - -void -conv_s32s_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const int32_t *s = src[0]; - float **d = (float **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = S32S_TO_F32(*s++); - } -} - -void -conv_s32d_to_f32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const int32_t **s = (const int32_t **) src; - float *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = S32_TO_F32(s[i][j]); - } -} - -void -conv_u24_to_f32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const uint8_t *s = src[0]; - float *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) { - d[i] = U24_TO_F32(read_u24(s)); - s += 3; - } -} - -void -conv_u24_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const uint8_t *s = src[0]; - float **d = (float **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) { - d[i][j] = U24_TO_F32(read_u24(s)); - s += 3; - } - } -} - -void -conv_s24d_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, j, n_channels = conv->n_channels; - - for (i = 0; i < n_channels; i++) { - const int8_t *s = src[i]; - float *d = dst[i]; - - for (j = 0; j < n_samples; j++) { - d[j] = S24_TO_F32(read_s24(s)); - s += 3; - } - } -} - -void -conv_s24_to_f32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const int8_t *s = src[0]; - float *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) { - d[i] = S24_TO_F32(read_s24(s)); - s += 3; - } -} - -void -conv_s24_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const uint8_t *s = src[0]; - float **d = (float **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) { - d[i][j] = S24_TO_F32(read_s24(s)); - s += 3; - } - } -} - -void -conv_s24s_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const uint8_t *s = src[0]; - float **d = (float **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) { - d[i][j] = S24_TO_F32(read_s24s(s)); - s += 3; - } - } -} - -void -conv_s24d_to_f32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const uint8_t **s = (const uint8_t **) src; - float *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) { - *d++ = S24_TO_F32(read_s24(&s[i][j*3])); - } - } -} - -void -conv_u24_32_to_f32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const uint32_t *s = src[0]; - float *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) { - d[i] = U24_32_TO_F32(s[i]); - } -} - -void -conv_u24_32_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const uint32_t *s = src[0]; - float **d = (float **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = U24_32_TO_F32(*s++); - } -} - -void -conv_s24_32d_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, j, n_channels = conv->n_channels; - - for (i = 0; i < n_channels; i++) { - const int32_t *s = src[i]; - float *d = dst[i]; - - for (j = 0; j < n_samples; j++) - d[j] = S24_32_TO_F32(s[j]); - } -} - -void -conv_s24_32_to_f32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const int32_t *s = src[0]; - float *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) { - d[i] = S24_32_TO_F32(s[i]); - } -} - -void -conv_s24_32_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const int32_t *s = src[0]; - float **d = (float **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = S24_32_TO_F32(*s++); - } -} - -void -conv_s24_32s_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const int32_t *s = src[0]; - float **d = (float **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = S24_32S_TO_F32(*s++); - } -} - -void -conv_s24_32d_to_f32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const int32_t **s = (const int32_t **) src; - float *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = S24_32_TO_F32(s[i][j]); - } -} - -void -conv_f64d_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, j, n_channels = conv->n_channels; - - for (i = 0; i < n_channels; i++) { - const double *s = src[i]; - float *d = dst[i]; - - for (j = 0; j < n_samples; j++) - d[j] = s[j]; - } -} - -void -conv_f64_to_f32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const double *s = src[0]; - float *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) - d[i] = s[i]; -} - -void -conv_f64_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const double *s = src[0]; - float **d = (float **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = *s++; - } -} - -void -conv_f64s_to_f32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const double *s = src[0]; - float **d = (float **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = bswap_64(*s++); - } -} - -void -conv_f64d_to_f32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const double **s = (const double **) src; - float *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = s[i][j]; - } -} - -void -conv_f32d_to_u8d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, j, n_channels = conv->n_channels; - - for (i = 0; i < n_channels; i++) { - const float *s = src[i]; - uint8_t *d = dst[i]; - - for (j = 0; j < n_samples; j++) - d[j] = F32_TO_U8(s[j]); - } -} - -void -conv_f32_to_u8_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const float *s = src[0]; - uint8_t *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) - d[i] = F32_TO_U8(s[i]); -} - -void -conv_f32_to_u8d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float *s = src[0]; - uint8_t **d = (uint8_t **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = F32_TO_U8(*s++); - } -} - -void -conv_f32d_to_u8_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float **s = (const float **) src; - uint8_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = F32_TO_U8(s[i][j]); - } -} - -void -conv_f32d_to_s8d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, j, n_channels = conv->n_channels; - - for (i = 0; i < n_channels; i++) { - const float *s = src[i]; - int8_t *d = dst[i]; - - for (j = 0; j < n_samples; j++) - d[j] = F32_TO_S8(s[j]); - } -} - -void -conv_f32_to_s8_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const float *s = src[0]; - int8_t *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) - d[i] = F32_TO_S8(s[i]); -} - -void -conv_f32_to_s8d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float *s = src[0]; - int8_t **d = (int8_t **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = F32_TO_S8(*s++); - } -} - -void -conv_f32d_to_s8_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float **s = (const float **) src; - int8_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = F32_TO_S8(s[i][j]); - } -} - -void -conv_f32d_to_alaw_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float **s = (const float **) src; - int8_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = f32_to_alaw(s[i][j]); - } -} - -void -conv_f32d_to_ulaw_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float **s = (const float **) src; - int8_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = f32_to_ulaw(s[i][j]); - } -} - -void -conv_f32_to_u16_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const float *s = src[0]; - uint16_t *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) - d[i] = F32_TO_U16(s[i]); -} -void -conv_f32d_to_u16_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float **s = (const float **) src; - uint16_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = F32_TO_U16(s[i][j]); - } -} - -void -conv_f32d_to_s16d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, j, n_channels = conv->n_channels; - - for (i = 0; i < n_channels; i++) { - const float *s = src[i]; - int16_t *d = dst[i]; - - for (j = 0; j < n_samples; j++) - d[j] = F32_TO_S16(s[j]); - } -} - -void -conv_f32_to_s16_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const float *s = src[0]; - int16_t *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) - d[i] = F32_TO_S16(s[i]); -} - -void -conv_f32_to_s16d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float *s = src[0]; - int16_t **d = (int16_t **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = F32_TO_S16(*s++); - } -} - -void -conv_f32d_to_s16_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float **s = (const float **) src; - int16_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = F32_TO_S16(s[i][j]); - } -} - -void -conv_f32d_to_s16s_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float **s = (const float **) src; - int16_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = F32_TO_S16S(s[i][j]); - } -} - -void -conv_f32_to_u32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const float *s = src[0]; - uint32_t *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) - d[i] = F32_TO_U32(s[i]); -} - -void -conv_f32d_to_u32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float **s = (const float **) src; - uint32_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = F32_TO_U32(s[i][j]); - } -} - -void -conv_f32d_to_s32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, j, n_channels = conv->n_channels; - - for (i = 0; i < n_channels; i++) { - const float *s = src[i]; - int32_t *d = dst[i]; - - for (j = 0; j < n_samples; j++) - d[j] = F32_TO_S32(s[j]); - } -} - -void -conv_f32_to_s32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const float *s = src[0]; - int32_t *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) - d[i] = F32_TO_S32(s[i]); -} - -void -conv_f32_to_s32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float *s = src[0]; - int32_t **d = (int32_t **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = F32_TO_S32(*s++); - } -} - -void -conv_f32d_to_s32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float **s = (const float **) src; - int32_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = F32_TO_S32(s[i][j]); - } -} - -void -conv_f32d_to_s32s_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float **s = (const float **) src; - int32_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = F32_TO_S32S(s[i][j]); - } -} - -void -conv_f32d_to_f64d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, j, n_channels = conv->n_channels; - - for (i = 0; i < n_channels; i++) { - const float *s = src[i]; - double *d = dst[i]; - - for (j = 0; j < n_samples; j++) - d[j] = s[j]; - } -} - -void -conv_f32_to_f64_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const float *s = src[0]; - double *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) - d[i] = s[i]; -} - -void -conv_f32_to_f64d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float *s = src[0]; - double **d = (double **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = *s++; - } -} - -void -conv_f32d_to_f64_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float **s = (const float **) src; - double *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = s[i][j]; - } -} - -void -conv_f32d_to_f64s_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float **s = (const float **) src; - double *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = bswap_32(s[i][j]); - } -} - -void -conv_f32_to_u24_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const float *s = src[0]; - uint8_t *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) { - write_u24(d, F32_TO_U24(s[i])); - d += 3; - } -} - -void -conv_f32d_to_u24_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float **s = (const float **) src; - uint8_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) { - write_u24(d, F32_TO_U24(s[i][j])); - d += 3; - } - } -} - -void -conv_f32d_to_s24d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, j, n_channels = conv->n_channels; - - for (i = 0; i < n_channels; i++) { - const float *s = src[i]; - uint8_t *d = dst[i]; - - for (j = 0; j < n_samples; j++) { - write_s24(d, F32_TO_S24(s[j])); - d += 3; - } - } -} - -void -conv_f32_to_s24_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const float *s = src[0]; - uint8_t *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) { - write_s24(d, F32_TO_S24(s[i])); - d += 3; - } -} - -void -conv_f32_to_s24d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float *s = src[0]; - uint8_t **d = (uint8_t **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) { - write_s24(&d[i][j*3], F32_TO_S24(*s++)); - } - } -} - -void -conv_f32d_to_s24_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float **s = (const float **) src; - uint8_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) { - write_s24(d, F32_TO_S24(s[i][j])); - d += 3; - } - } -} - -void -conv_f32d_to_s24s_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float **s = (const float **) src; - uint8_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) { - write_s24s(d, F32_TO_S24(s[i][j])); - d += 3; - } - } -} - - -void -conv_f32d_to_s24_32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, j, n_channels = conv->n_channels; - - for (i = 0; i < n_channels; i++) { - const float *s = src[i]; - int32_t *d = dst[i]; - - for (j = 0; j < n_samples; j++) - d[j] = F32_TO_S24_32(s[j]); - } -} - -void -conv_f32_to_u24_32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const float *s = src[0]; - uint32_t *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) - d[i] = F32_TO_U24_32(s[i]); -} - -void -conv_f32d_to_u24_32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float **s = (const float **) src; - uint32_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = F32_TO_U24_32(s[i][j]); - } -} - -void -conv_f32_to_s24_32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - uint32_t i, n_channels = conv->n_channels; - const float *s = src[0]; - int32_t *d = dst[0]; - - n_samples *= n_channels; - - for (i = 0; i < n_samples; i++) - d[i] = F32_TO_S24_32(s[i]); -} - -void -conv_f32_to_s24_32d_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float *s = src[0]; - int32_t **d = (int32_t **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = F32_TO_S24_32(*s++); - } -} - -void -conv_f32d_to_s24_32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float **s = (const float **) src; - int32_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = F32_TO_S24_32(s[i][j]); - } -} - -void -conv_f32d_to_s24_32s_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const float **s = (const float **) src; - int32_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = F32_TO_S24_32S(s[i][j]); - } -} - -void -conv_deinterleave_8_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const uint8_t *s = src[0]; - uint8_t **d = (uint8_t **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = *s++; - } -} - -void -conv_deinterleave_16_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const uint16_t *s = src[0]; - uint16_t **d = (uint16_t **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = *s++; - } -} - -void -conv_deinterleave_24_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const uint8_t *s = src[0]; - uint8_t **d = (uint8_t **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) { - write_s24(&d[i][j*3], read_s24(s)); - s += 3; - } - } -} - -void -conv_deinterleave_32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const uint32_t *s = src[0]; - uint32_t **d = (uint32_t **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = *s++; - } -} - -void -conv_deinterleave_32s_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const uint32_t *s = src[0]; - uint32_t **d = (uint32_t **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = bswap_32(*s++); - } -} - -void -conv_deinterleave_64_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const uint64_t *s = src[0]; - uint64_t **d = (uint64_t **) dst; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - d[i][j] = *s++; - } -} - -void -conv_interleave_8_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const int8_t **s = (const int8_t **) src; - uint8_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = s[i][j]; - } -} - -void -conv_interleave_16_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const int16_t **s = (const int16_t **) src; - uint16_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = s[i][j]; - } -} - -void -conv_interleave_24_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const int8_t **s = (const int8_t **) src; - uint8_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) { - write_s24(d, read_s24(&s[i][j*3])); - d += 3; +#define MAKE_COPY(size) \ +void conv_copy ##size## d_c(struct convert *conv, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples) \ +{ \ + uint32_t i, n_channels = conv->n_channels; \ + for (i = 0; i < n_channels; i++) \ + spa_memcpy(dst[i], src[i], n_samples * (size>>3)); \ +} \ +void conv_copy ##size## _c(struct convert *conv, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples) \ +{ \ + spa_memcpy(dst[0], src[0], n_samples * conv->n_channels * (size>>3)); \ +} + +MAKE_COPY(8); +MAKE_COPY(16); +MAKE_COPY(24); +MAKE_COPY(32); +MAKE_COPY(64); + +#define MAKE_D_TO_D(sname,stype,dname,dtype,func) \ +void conv_ ##sname## d_to_ ##dname## d_c(struct convert *conv, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples) \ +{ \ + uint32_t i, j, n_channels = conv->n_channels; \ + for (i = 0; i < n_channels; i++) { \ + const stype *s = src[i]; \ + dtype *d = dst[i]; \ + for (j = 0; j < n_samples; j++) \ + d[j] = func (s[j]); \ + } \ +} + +#define MAKE_I_TO_I(sname,stype,dname,dtype,func) \ +void conv_ ##sname## _to_ ##dname## _c(struct convert *conv, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples) \ +{ \ + uint32_t j; \ + const stype *s = src[0]; \ + dtype *d = dst[0]; \ + n_samples *= conv->n_channels; \ + for (j = 0; j < n_samples; j++) \ + d[j] = func (s[j]); \ +} + +#define MAKE_I_TO_D(sname,stype,dname,dtype,func) \ +void conv_ ##sname## _to_ ##dname## d_c(struct convert *conv, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples) \ +{ \ + const stype *s = src[0]; \ + dtype **d = (dtype**)dst; \ + uint32_t i, j, n_channels = conv->n_channels; \ + for (j = 0; j < n_samples; j++) { \ + for (i = 0; i < n_channels; i++) \ + d[i][j] = func (*s++); \ + } \ +} + +#define MAKE_D_TO_I(sname,stype,dname,dtype,func) \ +void conv_ ##sname## d_to_ ##dname## _c(struct convert *conv, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples) \ +{ \ + const stype **s = (const stype **)src; \ + dtype *d = dst[0]; \ + uint32_t i, j, n_channels = conv->n_channels; \ + for (j = 0; j < n_samples; j++) { \ + for (i = 0; i < n_channels; i++) \ + *d++ = func (s[i][j]); \ + } \ +} + +/* to f32 */ +MAKE_D_TO_D(u8, uint8_t, f32, float, U8_TO_F32); +MAKE_I_TO_I(u8, uint8_t, f32, float, U8_TO_F32); +MAKE_I_TO_D(u8, uint8_t, f32, float, U8_TO_F32); +MAKE_D_TO_I(u8, uint8_t, f32, float, U8_TO_F32); + +MAKE_D_TO_D(s8, int8_t, f32, float, S8_TO_F32); +MAKE_I_TO_I(s8, int8_t, f32, float, S8_TO_F32); +MAKE_I_TO_D(s8, int8_t, f32, float, S8_TO_F32); +MAKE_D_TO_I(s8, int8_t, f32, float, S8_TO_F32); + +MAKE_I_TO_D(alaw, uint8_t, f32, float, alaw_to_f32); +MAKE_I_TO_D(ulaw, uint8_t, f32, float, ulaw_to_f32); + +MAKE_I_TO_I(u16, uint16_t, f32, float, U16_TO_F32); +MAKE_I_TO_D(u16, uint16_t, f32, float, U16_TO_F32); + +MAKE_D_TO_D(s16, int16_t, f32, float, S16_TO_F32); +MAKE_I_TO_I(s16, int16_t, f32, float, S16_TO_F32); +MAKE_I_TO_D(s16, int16_t, f32, float, S16_TO_F32); +MAKE_D_TO_I(s16, int16_t, f32, float, S16_TO_F32); +MAKE_I_TO_D(s16s, uint16_t, f32, float, S16S_TO_F32); + +MAKE_I_TO_I(u32, uint32_t, f32, float, U32_TO_F32); +MAKE_I_TO_D(u32, uint32_t, f32, float, U32_TO_F32); + +MAKE_D_TO_D(s32, int32_t, f32, float, S32_TO_F32); +MAKE_I_TO_I(s32, int32_t, f32, float, S32_TO_F32); +MAKE_I_TO_D(s32, int32_t, f32, float, S32_TO_F32); +MAKE_D_TO_I(s32, int32_t, f32, float, S32_TO_F32); +MAKE_I_TO_D(s32s, uint32_t, f32, float, S32S_TO_F32); + +MAKE_I_TO_I(u24, uint24_t, f32, float, U24_TO_F32); +MAKE_I_TO_D(u24, uint24_t, f32, float, U24_TO_F32); + +MAKE_D_TO_D(s24, int24_t, f32, float, S24_TO_F32); +MAKE_I_TO_I(s24, int24_t, f32, float, S24_TO_F32); +MAKE_I_TO_D(s24, int24_t, f32, float, S24_TO_F32); +MAKE_D_TO_I(s24, int24_t, f32, float, S24_TO_F32); +MAKE_I_TO_D(s24s, int24_t, f32, float, S24S_TO_F32); + +MAKE_I_TO_I(u24_32, uint32_t, f32, float, U24_32_TO_F32); +MAKE_I_TO_D(u24_32, uint32_t, f32, float, U24_32_TO_F32); + +MAKE_D_TO_D(s24_32, int32_t, f32, float, S24_32_TO_F32); +MAKE_I_TO_I(s24_32, int32_t, f32, float, S24_32_TO_F32); +MAKE_I_TO_D(s24_32, int32_t, f32, float, S24_32_TO_F32); +MAKE_D_TO_I(s24_32, int32_t, f32, float, S24_32_TO_F32); +MAKE_I_TO_D(s24_32s, uint32_t, f32, float, S24_32S_TO_F32); + +MAKE_D_TO_D(f64, double, f32, float, (float)); +MAKE_I_TO_I(f64, double, f32, float, (float)); +MAKE_I_TO_D(f64, double, f32, float, (float)); +MAKE_D_TO_I(f64, double, f32, float, (float)); +MAKE_I_TO_D(f64s, uint64_t, f32, float, (float)F64S_TO_F64); + +/* from f32 */ +MAKE_D_TO_D(f32, float, u8, uint8_t, F32_TO_U8); +MAKE_I_TO_I(f32, float, u8, uint8_t, F32_TO_U8); +MAKE_I_TO_D(f32, float, u8, uint8_t, F32_TO_U8); +MAKE_D_TO_I(f32, float, u8, uint8_t, F32_TO_U8); + +MAKE_D_TO_D(f32, float, s8, int8_t, F32_TO_S8); +MAKE_I_TO_I(f32, float, s8, int8_t, F32_TO_S8); +MAKE_I_TO_D(f32, float, s8, int8_t, F32_TO_S8); +MAKE_D_TO_I(f32, float, s8, int8_t, F32_TO_S8); + +MAKE_D_TO_I(f32, float, alaw, uint8_t, f32_to_alaw); +MAKE_D_TO_I(f32, float, ulaw, uint8_t, f32_to_ulaw); + +MAKE_I_TO_I(f32, float, u16, uint16_t, F32_TO_U16); +MAKE_D_TO_I(f32, float, u16, uint16_t, F32_TO_U16); + +MAKE_D_TO_D(f32, float, s16, int16_t, F32_TO_S16); +MAKE_I_TO_I(f32, float, s16, int16_t, F32_TO_S16); +MAKE_I_TO_D(f32, float, s16, int16_t, F32_TO_S16); +MAKE_D_TO_I(f32, float, s16, int16_t, F32_TO_S16); +MAKE_D_TO_I(f32, float, s16s, uint16_t, F32_TO_S16S); + +MAKE_I_TO_I(f32, float, u32, uint32_t, F32_TO_U32); +MAKE_D_TO_I(f32, float, u32, uint32_t, F32_TO_U32); + +MAKE_D_TO_D(f32, float, s32, int32_t, F32_TO_S32); +MAKE_I_TO_I(f32, float, s32, int32_t, F32_TO_S32); +MAKE_I_TO_D(f32, float, s32, int32_t, F32_TO_S32); +MAKE_D_TO_I(f32, float, s32, int32_t, F32_TO_S32); +MAKE_D_TO_I(f32, float, s32s, uint32_t, F32_TO_S32S); + +MAKE_I_TO_I(f32, float, u24, uint24_t, F32_TO_U24); +MAKE_D_TO_I(f32, float, u24, uint24_t, F32_TO_U24); + +MAKE_D_TO_D(f32, float, s24, int24_t, F32_TO_S24); +MAKE_I_TO_I(f32, float, s24, int24_t, F32_TO_S24); +MAKE_I_TO_D(f32, float, s24, int24_t, F32_TO_S24); +MAKE_D_TO_I(f32, float, s24, int24_t, F32_TO_S24); +MAKE_D_TO_I(f32, float, s24s, int24_t, F32_TO_S24S); + +MAKE_I_TO_I(f32, float, u24_32, uint32_t, F32_TO_U24_32); +MAKE_D_TO_I(f32, float, u24_32, uint32_t, F32_TO_U24_32); + +MAKE_D_TO_D(f32, float, s24_32, int32_t, F32_TO_S24_32); +MAKE_I_TO_I(f32, float, s24_32, int32_t, F32_TO_S24_32); +MAKE_I_TO_D(f32, float, s24_32, int32_t, F32_TO_S24_32); +MAKE_D_TO_I(f32, float, s24_32, int32_t, F32_TO_S24_32); +MAKE_D_TO_I(f32, float, s24_32s, uint32_t, F32_TO_S24_32S); + +MAKE_D_TO_D(f32, float, f64, double, (double)); +MAKE_I_TO_I(f32, float, f64, double, (double)); +MAKE_I_TO_D(f32, float, f64, double, (double)); +MAKE_D_TO_I(f32, float, f64, double, (double)); +MAKE_D_TO_I(f32, float, f64s, uint64_t, F64_TO_F64S); + + +static inline int32_t +lcnoise(uint32_t *state) +{ + *state = (*state * 96314165) + 907633515; + return (int32_t)(*state); +} + +static inline void update_noise_c(struct convert *conv, uint32_t n_samples) +{ + uint32_t n; + float *noise = conv->noise, scale = conv->scale; + uint32_t *state = &conv->random[0]; + int32_t *prev = &conv->prev[0], old, new; + + switch (conv->noise_method) { + case NOISE_METHOD_RECTANGULAR: + for (n = 0; n < n_samples; n++) + noise[n] = lcnoise(state) * scale; + break; + case NOISE_METHOD_TRIANGULAR: + for (n = 0; n < n_samples; n++) + noise[n] = (lcnoise(state) - lcnoise(state)) * scale; + break; + case NOISE_METHOD_TRIANGULAR_HF: + old = *prev; + for (n = 0; n < n_samples; n++) { + new = lcnoise(state); + noise[n] = (new - old) * scale; + old = new; } - } -} - -void -conv_interleave_32_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const int32_t **s = (const int32_t **) src; - uint32_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = s[i][j]; - } -} - -void -conv_interleave_32s_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const int32_t **s = (const int32_t **) src; - uint32_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = bswap_32(s[i][j]); - } -} - -void -conv_interleave_64_c(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], - uint32_t n_samples) -{ - const int64_t **s = (const int64_t **) src; - uint64_t *d = dst[0]; - uint32_t i, j, n_channels = conv->n_channels; - - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = s[i][j]; - } -} + *prev = old; + break; + case NOISE_METHOD_PATTERN: + old = *prev; + for (n = 0; n < n_samples; n++) + noise[n] = conv->scale * (1-((old++>>10)&1)); + *prev = old; + break; + } +} + +#define MAKE_D_noise(dname,dtype,func) \ +void conv_f32d_to_ ##dname## d_noise_c(struct convert *conv, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples) \ +{ \ + uint32_t i, j, k, chunk, n_channels = conv->n_channels, noise_size = conv->noise_size; \ + float *noise = conv->noise; \ + update_noise_c(conv, SPA_MIN(n_samples, noise_size)); \ + for (i = 0; i < n_channels; i++) { \ + const float *s = src[i]; \ + dtype *d = dst[i]; \ + for (j = 0; j < n_samples;) { \ + chunk = SPA_MIN(n_samples - j, noise_size); \ + for (k = 0; k < chunk; k++, j++) \ + d[j] = func (s[j], noise[k]); \ + } \ + } \ +} + +#define MAKE_I_noise(dname,dtype,func) \ +void conv_f32d_to_ ##dname## _noise_c(struct convert *conv, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples) \ +{ \ + const float **s = (const float **) src; \ + dtype *d = dst[0]; \ + uint32_t i, j, k, chunk, n_channels = conv->n_channels, noise_size = conv->noise_size; \ + float *noise = conv->noise; \ + update_noise_c(conv, SPA_MIN(n_samples, noise_size)); \ + for (j = 0; j < n_samples;) { \ + chunk = SPA_MIN(n_samples - j, noise_size); \ + for (k = 0; k < chunk; k++, j++) { \ + for (i = 0; i < n_channels; i++) \ + *d++ = func (s[i][j], noise[k]); \ + } \ + } \ +} + +MAKE_D_noise(u8, uint8_t, F32_TO_U8_D); +MAKE_I_noise(u8, uint8_t, F32_TO_U8_D); +MAKE_D_noise(s8, int8_t, F32_TO_S8_D); +MAKE_I_noise(s8, int8_t, F32_TO_S8_D); +MAKE_D_noise(s16, int16_t, F32_TO_S16_D); +MAKE_I_noise(s16, int16_t, F32_TO_S16_D); +MAKE_I_noise(s16s, uint16_t, F32_TO_S16S_D); +MAKE_D_noise(s32, int32_t, F32_TO_S32_D); +MAKE_I_noise(s32, int32_t, F32_TO_S32_D); +MAKE_I_noise(s32s, uint32_t, F32_TO_S32S_D); +MAKE_D_noise(s24, int24_t, F32_TO_S24_D); +MAKE_I_noise(s24, int24_t, F32_TO_S24_D); +MAKE_I_noise(s24s, int24_t, F32_TO_S24_D); +MAKE_D_noise(s24_32, int32_t, F32_TO_S24_32_D); +MAKE_I_noise(s24_32, int32_t, F32_TO_S24_32_D); +MAKE_I_noise(s24_32s, int32_t, F32_TO_S24_32S_D); + +#define SHAPER(type,s,scale,offs,sh,min,max,d) \ +({ \ + type t; \ + float v = s * scale + offs; \ + for (n = 0; n < n_ns; n++) \ + v += sh->e[idx + n] * ns[n]; \ + t = FTOI(type, v, 1.0f, 0.0f, d, min, max); \ + idx = (idx - 1) & NS_MASK; \ + sh->e[idx] = sh->e[idx + NS_MAX] = v - t; \ + t; \ +}) + +#define F32_TO_U8_SH(s,sh,d) SHAPER(uint8_t, s, U8_SCALE, U8_OFFS, sh, U8_MIN, U8_MAX, d) +#define F32_TO_S8_SH(s,sh,d) SHAPER(int8_t, s, S8_SCALE, 0, sh, S8_MIN, S8_MAX, d) +#define F32_TO_S16_SH(s,sh,d) SHAPER(int16_t, s, S16_SCALE, 0, sh, S16_MIN, S16_MAX, d) +#define F32_TO_S16S_SH(s,sh,d) bswap_16(F32_TO_S16_SH(s,sh,d)) + +#define MAKE_D_shaped(dname,dtype,func) \ +void conv_f32d_to_ ##dname## d_shaped_c(struct convert *conv, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples) \ +{ \ + uint32_t i, j, k, chunk, n_channels = conv->n_channels, noise_size = conv->noise_size; \ + const float *noise = conv->noise, *ns = conv->ns; \ + uint32_t n, n_ns = conv->n_ns; \ + update_noise_c(conv, SPA_MIN(n_samples, noise_size)); \ + for (i = 0; i < n_channels; i++) { \ + const float *s = src[i]; \ + dtype *d = dst[i]; \ + struct shaper *sh = &conv->shaper[i]; \ + uint32_t idx = sh->idx; \ + for (j = 0; j < n_samples;) { \ + chunk = SPA_MIN(n_samples - j, noise_size); \ + for (k = 0; k < chunk; k++, j++) \ + d[j] = func (s[j], sh, noise[k]); \ + } \ + sh->idx = idx; \ + } \ +} + +#define MAKE_I_shaped(dname,dtype,func) \ +void conv_f32d_to_ ##dname## _shaped_c(struct convert *conv, \ + void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ + uint32_t n_samples) \ +{ \ + dtype *d0 = dst[0]; \ + uint32_t i, j, k, chunk, n_channels = conv->n_channels, noise_size = conv->noise_size; \ + const float *noise = conv->noise, *ns = conv->ns; \ + uint32_t n, n_ns = conv->n_ns; \ + update_noise_c(conv, SPA_MIN(n_samples, noise_size)); \ + for (i = 0; i < n_channels; i++) { \ + const float *s = src[i]; \ + dtype *d = &d0[i]; \ + struct shaper *sh = &conv->shaper[i]; \ + uint32_t idx = sh->idx; \ + for (j = 0; j < n_samples;) { \ + chunk = SPA_MIN(n_samples - j, noise_size); \ + for (k = 0; k < chunk; k++, j++) \ + d[j*n_channels] = func (s[j], sh, noise[k]); \ + } \ + sh->idx = idx; \ + } \ +} + +MAKE_D_shaped(u8, uint8_t, F32_TO_U8_SH); +MAKE_I_shaped(u8, uint8_t, F32_TO_U8_SH); +MAKE_D_shaped(s8, int8_t, F32_TO_S8_SH); +MAKE_I_shaped(s8, int8_t, F32_TO_S8_SH); +MAKE_D_shaped(s16, int16_t, F32_TO_S16_SH); +MAKE_I_shaped(s16, int16_t, F32_TO_S16_SH); +MAKE_I_shaped(s16s, uint16_t, F32_TO_S16S_SH); + +#define MAKE_DEINTERLEAVE(size1,size2, type,func) \ + MAKE_I_TO_D(size1,type,size2,type,func) + +MAKE_DEINTERLEAVE(8, 8, uint8_t, (uint8_t)); +MAKE_DEINTERLEAVE(16, 16, uint16_t, (uint16_t)); +MAKE_DEINTERLEAVE(24, 24, uint24_t, (uint24_t)); +MAKE_DEINTERLEAVE(32, 32, uint32_t, (uint32_t)); +MAKE_DEINTERLEAVE(32s, 32, uint32_t, bswap_32); +MAKE_DEINTERLEAVE(64, 64, uint64_t, (uint64_t)); + +#define MAKE_INTERLEAVE(size1,size2,type,func) \ + MAKE_D_TO_I(size1,type,size2,type,func) + +MAKE_INTERLEAVE(8, 8, uint8_t, (uint8_t)); +MAKE_INTERLEAVE(16, 16, uint16_t, (uint16_t)); +MAKE_INTERLEAVE(24, 24, uint24_t, (uint24_t)); +MAKE_INTERLEAVE(32, 32, uint32_t, (uint32_t)); +MAKE_INTERLEAVE(32, 32s, uint32_t, bswap_32); +MAKE_INTERLEAVE(64, 64, uint64_t, (uint64_t)); diff --git a/spa/plugins/audioconvert/fmt-ops-neon.c b/spa/plugins/audioconvert/fmt-ops-neon.c index 56c6b628b3de159065e1878759ef35015caadb9d..e6c8b844bb4cce84d1a58bb5aff0354b36e7a5ef 100644 --- a/spa/plugins/audioconvert/fmt-ops-neon.c +++ b/spa/plugins/audioconvert/fmt-ops-neon.c @@ -26,6 +26,8 @@ #include <stdio.h> #include <math.h> +#include <arm_neon.h> + #include "fmt-ops.h" void @@ -289,16 +291,19 @@ conv_f32d_to_s16_2s_neon(void *data, void * SPA_RESTRICT dst, const void * SPA_R #ifdef __aarch64__ asm volatile( + " dup v2.4s, %w[scale]\n" " cmp %[n_samples], #0\n" " beq 2f\n" "1:" " ld1 { v0.4s }, [%[s0]], #16\n" " ld1 { v1.4s }, [%[s1]], #16\n" " subs %[n_samples], %[n_samples], #4\n" - " fcvtzs v0.4s, v0.4s, #31\n" - " fcvtzs v1.4s, v1.4s, #31\n" - " sqrshrn v0.4h, v0.4s, #16\n" - " sqrshrn v1.4h, v1.4s, #16\n" + " sqadd v0.4s, v0.4s, v2.4s\n" + " sqadd v1.4s, v1.4s, v2.4s\n" + " fcvtns v0.4s, v0.4s\n" + " fcvtns v1.4s, v1.4s\n" + " sqxtn v0.4h, v0.4s\n" + " sqxtn v1.4h, v1.4s\n" " st2 { v0.h, v1.h }[0], [%[d]], %[stride]\n" " st2 { v0.h, v1.h }[1], [%[d]], %[stride]\n" " st2 { v0.h, v1.h }[2], [%[d]], %[stride]\n" @@ -311,29 +316,42 @@ conv_f32d_to_s16_2s_neon(void *data, void * SPA_RESTRICT dst, const void * SPA_R " ld1 { v0.s }[0], [%[s0]], #4\n" " ld1 { v2.s }[0], [%[s1]], #4\n" " subs %[remainder], %[remainder], #1\n" - " fcvtzs v0.4s, v0.4s, #31\n" - " fcvtzs v1.4s, v1.4s, #31\n" - " sqrshrn v0.4h, v0.4s, #16\n" - " sqrshrn v1.4h, v1.4s, #16\n" + " sqadd v0.4s, v0.4s, v2.4s\n" + " sqadd v1.4s, v1.4s, v2.4s\n" + " fcvtns v0.4s, v0.4s\n" + " fcvtns v1.4s, v1.4s\n" + " sqxtn v0.4h, v0.4s\n" + " sqxtn v1.4h, v1.4s\n" " st2 { v0.h, v1.h }[0], [%[d]], %[stride]\n" " bne 3b\n" "4:" : [d] "+r" (d), [s0] "+r" (s0), [s1] "+r" (s1), [n_samples] "+r" (n_samples), [remainder] "+r" (remainder) - : [stride] "r" (stride) + : [stride] "r" (stride), + [scale] "r" (15 << 23) : "cc", "v0", "v1"); #else + float32x4_t pos = vdupq_n_f32(0.4999999f / S16_SCALE); + float32x4_t neg = vdupq_n_f32(-0.4999999f / S16_SCALE); + asm volatile( + " veor q2, q2, q2\n" " cmp %[n_samples], #0\n" " beq 2f\n" "1:" " vld1.32 { q0 }, [%[s0]]!\n" " vld1.32 { q1 }, [%[s1]]!\n" " subs %[n_samples], %[n_samples], #4\n" - " vcvt.s32.f32 q0, q0, #31\n" - " vcvt.s32.f32 q1, q1, #31\n" - " vqrshrn.s32 d0, q0, #16\n" - " vqrshrn.s32 d1, q1, #16\n" + " vcgt.f32 q3, q0, q2\n" + " vcgt.f32 q4, q0, q2\n" + " vbsl q3, %q[pos], %q[neg]\n" + " vbsl q4, %q[pos], %q[neg]\n" + " vadd.f32 q0, q0, q3\n" + " vadd.f32 q1, q1, q4\n" + " vcvt.s32.f32 q0, q0, #15\n" + " vcvt.s32.f32 q1, q1, #15\n" + " vqmovn.s32 d0, q0\n" + " vqmovn.s32 d1, q1\n" " vst2.16 { d0[0], d1[0] }, [%[d]], %[stride]\n" " vst2.16 { d0[1], d1[1] }, [%[d]], %[stride]\n" " vst2.16 { d0[2], d1[2] }, [%[d]], %[stride]\n" @@ -346,17 +364,25 @@ conv_f32d_to_s16_2s_neon(void *data, void * SPA_RESTRICT dst, const void * SPA_R " vld1.32 { d0[0] }, [%[s0]]!\n" " vld1.32 { d2[0] }, [%[s1]]!\n" " subs %[remainder], %[remainder], #1\n" - " vcvt.s32.f32 q0, q0, #31\n" - " vcvt.s32.f32 q1, q1, #31\n" - " vqrshrn.s32 d0, q0, #16\n" - " vqrshrn.s32 d1, q1, #16\n" + " vcgt.f32 q3, q0, q2\n" + " vcgt.f32 q4, q0, q2\n" + " vbsl q3, %q[pos], %q[neg]\n" + " vbsl q4, %q[pos], %q[neg]\n" + " vadd.f32 q0, q0, q3\n" + " vadd.f32 q1, q1, q4\n" + " vcvt.s32.f32 q0, q0, #15\n" + " vcvt.s32.f32 q1, q1, #15\n" + " vqmovn.s32 d0, q0\n" + " vqmovn.s32 d1, q1\n" " vst2.16 { d0[0], d1[0] }, [%[d]], %[stride]\n" " bne 3b\n" "4:" : [d] "+r" (d), [s0] "+r" (s0), [s1] "+r" (s1), [n_samples] "+r" (n_samples), [remainder] "+r" (remainder) - : [stride] "r" (stride) - : "cc", "q0", "q1"); + : [stride] "r" (stride), + [pos]"w"(pos), + [neg]"w"(neg) + : "cc", "q0", "q1", "q2", "q3", "q4"); #endif } @@ -372,13 +398,15 @@ conv_f32d_to_s16_1s_neon(void *data, void * SPA_RESTRICT dst, const void * SPA_R #ifdef __aarch64__ asm volatile( + " dup v2.4s, %w[scale]\n" " cmp %[n_samples], #0\n" " beq 2f\n" "1:" " ld1 { v0.4s }, [%[s]], #16\n" " subs %[n_samples], %[n_samples], #4\n" - " fcvtzs v0.4s, v0.4s, #31\n" - " sqrshrn v0.4h, v0.4s, #16\n" + " sqadd v0.4s, v0.4s, v2.4s\n" + " fcvtns v0.4s, v0.4s\n" + " sqxtn v0.4h, v0.4s\n" " st1 { v0.h }[0], [%[d]], %[stride]\n" " st1 { v0.h }[1], [%[d]], %[stride]\n" " st1 { v0.h }[2], [%[d]], %[stride]\n" @@ -390,24 +418,33 @@ conv_f32d_to_s16_1s_neon(void *data, void * SPA_RESTRICT dst, const void * SPA_R "3:" " ld1 { v0.s }[0], [%[s]], #4\n" " subs %[remainder], %[remainder], #1\n" - " fcvtzs v0.4s, v0.4s, #31\n" - " sqrshrn v0.4h, v0.4s, #16\n" + " sqadd v0.4s, v0.4s, v2.4s\n" + " fcvtns v0.4s, v0.4s\n" + " sqxtn v0.4h, v0.4s\n" " st1 { v0.h }[0], [%[d]], %[stride]\n" " bne 3b\n" "4:" : [d] "+r" (d), [s] "+r" (s), [n_samples] "+r" (n_samples), [remainder] "+r" (remainder) - : [stride] "r" (stride) + : [stride] "r" (stride), + [scale] "r" (15 << 23) : "cc", "v0"); #else + float32x4_t pos = vdupq_n_f32(0.4999999f / S16_SCALE); + float32x4_t neg = vdupq_n_f32(-0.4999999f / S16_SCALE); + asm volatile( + " veor q1, q1, q1\n" " cmp %[n_samples], #0\n" " beq 2f\n" "1:" " vld1.32 { q0 }, [%[s]]!\n" " subs %[n_samples], %[n_samples], #4\n" - " vcvt.s32.f32 q0, q0, #31\n" - " vqrshrn.s32 d0, q0, #16\n" + " vcgt.f32 q2, q0, q1\n" + " vbsl q2, %q[pos], %q[neg]\n" + " vadd.f32 q0, q0, q2\n" + " vcvt.s32.f32 q0, q0, #15\n" + " vqmovn.s32 d0, q0\n" " vst1.16 { d0[0] }, [%[d]], %[stride]\n" " vst1.16 { d0[1] }, [%[d]], %[stride]\n" " vst1.16 { d0[2] }, [%[d]], %[stride]\n" @@ -419,15 +456,20 @@ conv_f32d_to_s16_1s_neon(void *data, void * SPA_RESTRICT dst, const void * SPA_R "3:" " vld1.32 { d0[0] }, [%[s]]!\n" " subs %[remainder], %[remainder], #1\n" - " vcvt.s32.f32 q0, q0, #31\n" - " vqrshrn.s32 d0, q0, #16\n" + " vcgt.f32 q2, q0, q1\n" + " vbsl q2, %q[pos], %q[neg]\n" + " vadd.f32 q0, q0, q2\n" + " vcvt.s32.f32 q0, q0, #15\n" + " vqmovn.s32 d0, q0\n" " vst1.16 { d0[0] }, [%[d]], %[stride]\n" " bne 3b\n" "4:" : [d] "+r" (d), [s] "+r" (s), [n_samples] "+r" (n_samples), [remainder] "+r" (remainder) - : [stride] "r" (stride) - : "cc", "q0"); + : [stride] "r" (stride), + [pos]"w"(pos), + [neg]"w"(neg) + : "cc", "q0", "q1", "q2"); #endif } diff --git a/spa/plugins/audioconvert/fmt-ops-sse2.c b/spa/plugins/audioconvert/fmt-ops-sse2.c index 81524d2fd2e8501a44debbe623b5582f4acb0fde..917bfa0dc80c0a36e68d0b9b5179913d846c5ff4 100644 --- a/spa/plugins/audioconvert/fmt-ops-sse2.c +++ b/spa/plugins/audioconvert/fmt-ops-sse2.c @@ -26,6 +26,12 @@ #include <emmintrin.h> +#define _MM_CLAMP_PS(r,min,max) \ + _mm_min_ps(_mm_max_ps(r, min), max) + +#define _MM_CLAMP_SS(r,min,max) \ + _mm_min_ss(_mm_max_ss(r, min), max) + static void conv_s16_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) @@ -33,7 +39,7 @@ conv_s16_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA const int16_t *s = src; float *d0 = dst[0]; uint32_t n, unrolled; - __m128i in; + __m128i in = _mm_setzero_si128(); __m128 out, factor = _mm_set1_ps(1.0f / S16_SCALE); if (SPA_LIKELY(SPA_IS_ALIGNED(d0, 16))) @@ -53,7 +59,7 @@ conv_s16_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA s += 4*n_channels; } for(; n < n_samples; n++) { - out = _mm_cvtsi32_ss(out, s[0]); + out = _mm_cvtsi32_ss(factor, s[0]); out = _mm_mul_ss(out, factor); _mm_store_ss(&d0[n], out); s += n_channels; @@ -118,9 +124,9 @@ conv_s16_to_f32d_2_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const v s += 16; } for(; n < n_samples; n++) { - out[0] = _mm_cvtsi32_ss(out[0], s[0]); + out[0] = _mm_cvtsi32_ss(factor, s[0]); out[0] = _mm_mul_ss(out[0], factor); - out[1] = _mm_cvtsi32_ss(out[1], s[1]); + out[1] = _mm_cvtsi32_ss(factor, s[1]); out[1] = _mm_mul_ss(out[1], factor); _mm_store_ss(&d0[n], out[0]); _mm_store_ss(&d1[n], out[1]); @@ -132,7 +138,7 @@ void conv_s24_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { - const uint8_t *s = src; + const int24_t *s = src; float *d0 = dst[0]; uint32_t n, unrolled; __m128i in; @@ -149,21 +155,21 @@ conv_s24_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA for(n = 0; n < unrolled; n += 4) { in = _mm_setr_epi32( *((uint32_t*)&s[0 * n_channels]), - *((uint32_t*)&s[3 * n_channels]), - *((uint32_t*)&s[6 * n_channels]), - *((uint32_t*)&s[9 * n_channels])); + *((uint32_t*)&s[1 * n_channels]), + *((uint32_t*)&s[2 * n_channels]), + *((uint32_t*)&s[3 * n_channels])); in = _mm_slli_epi32(in, 8); in = _mm_srai_epi32(in, 8); out = _mm_cvtepi32_ps(in); out = _mm_mul_ps(out, factor); _mm_store_ps(&d0[n], out); - s += 12 * n_channels; + s += 4 * n_channels; } for(; n < n_samples; n++) { - out = _mm_cvtsi32_ss(out, read_s24(s)); + out = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); out = _mm_mul_ss(out, factor); _mm_store_ss(&d0[n], out); - s += 3 * n_channels; + s += n_channels; } } @@ -171,7 +177,7 @@ static void conv_s24_to_f32d_2s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { - const uint8_t *s = src; + const int24_t *s = src; float *d0 = dst[0], *d1 = dst[1]; uint32_t n, unrolled; __m128i in[2]; @@ -190,14 +196,14 @@ conv_s24_to_f32d_2s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA for(n = 0; n < unrolled; n += 4) { in[0] = _mm_setr_epi32( *((uint32_t*)&s[0 + 0*n_channels]), - *((uint32_t*)&s[0 + 3*n_channels]), - *((uint32_t*)&s[0 + 6*n_channels]), - *((uint32_t*)&s[0 + 9*n_channels])); + *((uint32_t*)&s[0 + 1*n_channels]), + *((uint32_t*)&s[0 + 2*n_channels]), + *((uint32_t*)&s[0 + 3*n_channels])); in[1] = _mm_setr_epi32( - *((uint32_t*)&s[3 + 0*n_channels]), - *((uint32_t*)&s[3 + 3*n_channels]), - *((uint32_t*)&s[3 + 6*n_channels]), - *((uint32_t*)&s[3 + 9*n_channels])); + *((uint32_t*)&s[1 + 0*n_channels]), + *((uint32_t*)&s[1 + 1*n_channels]), + *((uint32_t*)&s[1 + 2*n_channels]), + *((uint32_t*)&s[1 + 3*n_channels])); in[0] = _mm_slli_epi32(in[0], 8); in[1] = _mm_slli_epi32(in[1], 8); @@ -214,23 +220,23 @@ conv_s24_to_f32d_2s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA _mm_store_ps(&d0[n], out[0]); _mm_store_ps(&d1[n], out[1]); - s += 12 * n_channels; + s += 4 * n_channels; } for(; n < n_samples; n++) { - out[0] = _mm_cvtsi32_ss(out[0], read_s24(s)); - out[1] = _mm_cvtsi32_ss(out[1], read_s24(s+3)); + out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); + out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+1))); out[0] = _mm_mul_ss(out[0], factor); out[1] = _mm_mul_ss(out[1], factor); _mm_store_ss(&d0[n], out[0]); _mm_store_ss(&d1[n], out[1]); - s += 3 * n_channels; + s += n_channels; } } static void conv_s24_to_f32d_4s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { - const uint8_t *s = src; + const int24_t *s = src; float *d0 = dst[0], *d1 = dst[1], *d2 = dst[2], *d3 = dst[3]; uint32_t n, unrolled; __m128i in[4]; @@ -251,24 +257,24 @@ conv_s24_to_f32d_4s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA for(n = 0; n < unrolled; n += 4) { in[0] = _mm_setr_epi32( *((uint32_t*)&s[0 + 0*n_channels]), - *((uint32_t*)&s[0 + 3*n_channels]), - *((uint32_t*)&s[0 + 6*n_channels]), - *((uint32_t*)&s[0 + 9*n_channels])); + *((uint32_t*)&s[0 + 1*n_channels]), + *((uint32_t*)&s[0 + 2*n_channels]), + *((uint32_t*)&s[0 + 3*n_channels])); in[1] = _mm_setr_epi32( - *((uint32_t*)&s[3 + 0*n_channels]), - *((uint32_t*)&s[3 + 3*n_channels]), - *((uint32_t*)&s[3 + 6*n_channels]), - *((uint32_t*)&s[3 + 9*n_channels])); + *((uint32_t*)&s[1 + 0*n_channels]), + *((uint32_t*)&s[1 + 1*n_channels]), + *((uint32_t*)&s[1 + 2*n_channels]), + *((uint32_t*)&s[1 + 3*n_channels])); in[2] = _mm_setr_epi32( - *((uint32_t*)&s[6 + 0*n_channels]), - *((uint32_t*)&s[6 + 3*n_channels]), - *((uint32_t*)&s[6 + 6*n_channels]), - *((uint32_t*)&s[6 + 9*n_channels])); + *((uint32_t*)&s[2 + 0*n_channels]), + *((uint32_t*)&s[2 + 1*n_channels]), + *((uint32_t*)&s[2 + 2*n_channels]), + *((uint32_t*)&s[2 + 3*n_channels])); in[3] = _mm_setr_epi32( - *((uint32_t*)&s[9 + 0*n_channels]), - *((uint32_t*)&s[9 + 3*n_channels]), - *((uint32_t*)&s[9 + 6*n_channels]), - *((uint32_t*)&s[9 + 9*n_channels])); + *((uint32_t*)&s[3 + 0*n_channels]), + *((uint32_t*)&s[3 + 1*n_channels]), + *((uint32_t*)&s[3 + 2*n_channels]), + *((uint32_t*)&s[3 + 3*n_channels])); in[0] = _mm_slli_epi32(in[0], 8); in[1] = _mm_slli_epi32(in[1], 8); @@ -295,13 +301,13 @@ conv_s24_to_f32d_4s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA _mm_store_ps(&d2[n], out[2]); _mm_store_ps(&d3[n], out[3]); - s += 12 * n_channels; + s += 4 * n_channels; } for(; n < n_samples; n++) { - out[0] = _mm_cvtsi32_ss(out[0], read_s24(s)); - out[1] = _mm_cvtsi32_ss(out[1], read_s24(s+3)); - out[2] = _mm_cvtsi32_ss(out[2], read_s24(s+6)); - out[3] = _mm_cvtsi32_ss(out[3], read_s24(s+9)); + out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); + out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+1))); + out[2] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+2))); + out[3] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+3))); out[0] = _mm_mul_ss(out[0], factor); out[1] = _mm_mul_ss(out[1], factor); out[2] = _mm_mul_ss(out[2], factor); @@ -310,7 +316,7 @@ conv_s24_to_f32d_4s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA _mm_store_ss(&d1[n], out[1]); _mm_store_ss(&d2[n], out[2]); _mm_store_ss(&d3[n], out[3]); - s += 3 * n_channels; + s += n_channels; } } @@ -357,7 +363,7 @@ conv_s32_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA s += 4*n_channels; } for(; n < n_samples; n++) { - out = _mm_cvtsi32_ss(out, s[0]>>8); + out = _mm_cvtsi32_ss(factor, s[0]>>8); out = _mm_mul_ss(out, factor); _mm_store_ss(&d0[n], out); s += n_channels; @@ -384,8 +390,9 @@ conv_f32d_to_s32_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R uint32_t n, unrolled; __m128 in[1]; __m128i out[4]; - __m128 scale = _mm_set1_ps(S32_SCALE); - __m128 int_min = _mm_set1_ps(S32_MIN); + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_min = _mm_set1_ps(S24_MIN); + __m128 int_max = _mm_set1_ps(S24_MAX); if (SPA_IS_ALIGNED(s0, 16)) unrolled = n_samples & ~3; @@ -394,8 +401,9 @@ conv_f32d_to_s32_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R for(n = 0; n < unrolled; n += 4) { in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), scale); - in[0] = _mm_min_ps(in[0], int_min); + in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); out[0] = _mm_cvtps_epi32(in[0]); + out[0] = _mm_slli_epi32(out[0], 8); out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1)); out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2)); out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3)); @@ -409,8 +417,8 @@ conv_f32d_to_s32_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R for(; n < n_samples; n++) { in[0] = _mm_load_ss(&s0[n]); in[0] = _mm_mul_ss(in[0], scale); - in[0] = _mm_min_ss(in[0], int_min); - *d = _mm_cvtss_si32(in[0]); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + *d = _mm_cvtss_si32(in[0]) << 8; d += n_channels; } } @@ -424,8 +432,9 @@ conv_f32d_to_s32_2s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R uint32_t n, unrolled; __m128 in[2]; __m128i out[2], t[2]; - __m128 scale = _mm_set1_ps(S32_SCALE); - __m128 int_min = _mm_set1_ps(S32_MIN); + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_min = _mm_set1_ps(S24_MIN); + __m128 int_max = _mm_set1_ps(S24_MAX); if (SPA_IS_ALIGNED(s0, 16) && SPA_IS_ALIGNED(s1, 16)) @@ -437,11 +446,13 @@ conv_f32d_to_s32_2s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), scale); in[1] = _mm_mul_ps(_mm_load_ps(&s1[n]), scale); - in[0] = _mm_min_ps(in[0], int_min); - in[1] = _mm_min_ps(in[1], int_min); + in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); + in[1] = _MM_CLAMP_PS(in[1], int_min, int_max); out[0] = _mm_cvtps_epi32(in[0]); out[1] = _mm_cvtps_epi32(in[1]); + out[0] = _mm_slli_epi32(out[0], 8); + out[1] = _mm_slli_epi32(out[1], 8); t[0] = _mm_unpacklo_epi32(out[0], out[1]); t[1] = _mm_unpackhi_epi32(out[0], out[1]); @@ -459,8 +470,9 @@ conv_f32d_to_s32_2s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R in[0] = _mm_unpacklo_ps(in[0], in[1]); in[0] = _mm_mul_ps(in[0], scale); - in[0] = _mm_min_ps(in[0], int_min); + in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); out[0] = _mm_cvtps_epi32(in[0]); + out[0] = _mm_slli_epi32(out[0], 8); _mm_storel_epi64((__m128i*)d, out[0]); d += n_channels; } @@ -475,8 +487,9 @@ conv_f32d_to_s32_4s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R uint32_t n, unrolled; __m128 in[4]; __m128i out[4]; - __m128 scale = _mm_set1_ps(S32_SCALE); - __m128 int_min = _mm_set1_ps(S32_MIN); + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_min = _mm_set1_ps(S24_MIN); + __m128 int_max = _mm_set1_ps(S24_MAX); if (SPA_IS_ALIGNED(s0, 16) && SPA_IS_ALIGNED(s1, 16) && @@ -492,10 +505,10 @@ conv_f32d_to_s32_4s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R in[2] = _mm_mul_ps(_mm_load_ps(&s2[n]), scale); in[3] = _mm_mul_ps(_mm_load_ps(&s3[n]), scale); - in[0] = _mm_min_ps(in[0], int_min); - in[1] = _mm_min_ps(in[1], int_min); - in[2] = _mm_min_ps(in[2], int_min); - in[3] = _mm_min_ps(in[3], int_min); + in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); + in[1] = _MM_CLAMP_PS(in[1], int_min, int_max); + in[2] = _MM_CLAMP_PS(in[2], int_min, int_max); + in[3] = _MM_CLAMP_PS(in[3], int_min, int_max); _MM_TRANSPOSE4_PS(in[0], in[1], in[2], in[3]); @@ -503,6 +516,10 @@ conv_f32d_to_s32_4s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R out[1] = _mm_cvtps_epi32(in[1]); out[2] = _mm_cvtps_epi32(in[2]); out[3] = _mm_cvtps_epi32(in[3]); + out[0] = _mm_slli_epi32(out[0], 8); + out[1] = _mm_slli_epi32(out[1], 8); + out[2] = _mm_slli_epi32(out[2], 8); + out[3] = _mm_slli_epi32(out[3], 8); _mm_storeu_si128((__m128i*)(d + 0*n_channels), out[0]); _mm_storeu_si128((__m128i*)(d + 1*n_channels), out[1]); @@ -521,8 +538,9 @@ conv_f32d_to_s32_4s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R in[0] = _mm_unpacklo_ps(in[0], in[1]); in[0] = _mm_mul_ps(in[0], scale); - in[0] = _mm_min_ps(in[0], int_min); + in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); out[0] = _mm_cvtps_epi32(in[0]); + out[0] = _mm_slli_epi32(out[0], 8); _mm_storeu_si128((__m128i*)d, out[0]); d += n_channels; } @@ -543,6 +561,133 @@ conv_f32d_to_s32_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const voi conv_f32d_to_s32_1s_sse2(conv, &d[i], &src[i], n_channels, n_samples); } +/* 32 bit xorshift PRNG, see https://en.wikipedia.org/wiki/Xorshift */ +#define _MM_XORSHIFT_EPI32(r) \ +({ \ + __m128i i, t; \ + i = _mm_load_si128((__m128i*)r); \ + t = _mm_slli_epi32(i, 13); \ + i = _mm_xor_si128(i, t); \ + t = _mm_srli_epi32(i, 17); \ + i = _mm_xor_si128(i, t); \ + t = _mm_slli_epi32(i, 5); \ + i = _mm_xor_si128(i, t); \ + _mm_store_si128((__m128i*)r, i); \ + i; \ +}) + + +static inline void update_noise_sse2(struct convert *conv, uint32_t n_samples) +{ + uint32_t n; + const uint32_t *r = SPA_PTR_ALIGN(conv->random, 16, uint32_t); + int32_t *p = SPA_PTR_ALIGN(conv->prev, 16, int32_t), op; + __m128 scale = _mm_set1_ps(conv->scale); + __m128 out[1]; + float *noise = SPA_PTR_ALIGN(conv->noise, 16, float); + __m128i in[1], old[1], new[1]; + + switch (conv->noise_method) { + case DITHER_METHOD_RECTANGULAR: + for (n = 0; n < n_samples; n += 4) { + in[0] = _MM_XORSHIFT_EPI32(r); + out[0] = _mm_cvtepi32_ps(_MM_XORSHIFT_EPI32(r)); + out[0] = _mm_mul_ps(out[0], scale); + _mm_store_ps(&noise[n], out[0]); + } + break; + case DITHER_METHOD_TRIANGULAR: + for (n = 0; n < n_samples; n += 4) { + in[0] = _mm_sub_epi32( _MM_XORSHIFT_EPI32(r), _MM_XORSHIFT_EPI32(r)); + out[0] = _mm_cvtepi32_ps(in[0]); + out[0] = _mm_mul_ps(out[0], scale); + _mm_store_ps(&noise[n], out[0]); + } + break; + case DITHER_METHOD_TRIANGULAR_HF: + old[0] = _mm_load_si128((__m128i*)p); + for (n = 0; n < n_samples; n += 4) { + new[0] = _MM_XORSHIFT_EPI32(r); + in[0] = _mm_sub_epi32(old[0], new[0]); + old[0] = new[0]; + out[0] = _mm_cvtepi32_ps(in[0]); + out[0] = _mm_mul_ps(out[0], scale); + _mm_store_ps(&noise[n], out[0]); + } + _mm_store_si128((__m128i*)p, old[0]); + break; + case NOISE_METHOD_PATTERN: + op = *p; + for (n = 0; n < n_samples; n++) + noise[n] = conv->scale * (1-((op++>>10)&1)); + *p = op; + break; + } +} + +static void +conv_f32d_to_s32_1s_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const float *s = src; + float *noise = SPA_PTR_ALIGN(conv->noise, 16, float); + int32_t *d = dst; + uint32_t n, unrolled; + __m128 in[1]; + __m128i out[4]; + __m128 scale = _mm_set1_ps(S24_SCALE); + __m128 int_min = _mm_set1_ps(S24_MIN); + __m128 int_max = _mm_set1_ps(S24_MAX); + + if (SPA_IS_ALIGNED(s, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + in[0] = _mm_mul_ps(_mm_load_ps(&s[n]), scale); + in[0] = _mm_add_ps(in[0], _mm_load_ps(&noise[n])); + in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); + out[0] = _mm_cvtps_epi32(in[0]); + out[0] = _mm_slli_epi32(out[0], 8); + out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1)); + out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2)); + out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3)); + + d[0*n_channels] = _mm_cvtsi128_si32(out[0]); + d[1*n_channels] = _mm_cvtsi128_si32(out[1]); + d[2*n_channels] = _mm_cvtsi128_si32(out[2]); + d[3*n_channels] = _mm_cvtsi128_si32(out[3]); + d += 4*n_channels; + } + for(; n < n_samples; n++) { + in[0] = _mm_load_ss(&s[n]); + in[0] = _mm_mul_ss(in[0], scale); + in[0] = _mm_add_ss(in[0], _mm_load_ss(&noise[n])); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + *d = _mm_cvtss_si32(in[0]) << 8; + d += n_channels; + } +} + +void +conv_f32d_to_s32_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + int32_t *d = dst[0]; + uint32_t i, k, chunk, n_channels = conv->n_channels; + + update_noise_sse2(conv, SPA_MIN(n_samples, conv->noise_size)); + + for(i = 0; i < n_channels; i++) { + const float *s = src[i]; + for(k = 0; k < n_samples; k += chunk) { + chunk = SPA_MIN(n_samples - k, conv->noise_size); + conv_f32d_to_s32_1s_noise_sse2(conv, &d[i + k*n_channels], &s[k], n_channels, chunk); + } + } +} + static void conv_interleave_32_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) @@ -613,7 +758,7 @@ conv_interleave_32_4s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA } void -conv_interleave_32_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], +conv_32d_to_32_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { int32_t *d = dst[0]; @@ -711,7 +856,7 @@ conv_interleave_32s_4s_sse2(void *data, void * SPA_RESTRICT dst, const void * SP } void -conv_interleave_32s_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], +conv_32d_to_32s_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { int32_t *d = dst[0]; @@ -792,7 +937,7 @@ conv_deinterleave_32_4s_sse2(void *data, void * SPA_RESTRICT dst[], const void * } void -conv_deinterleave_32_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], +conv_32_to_32d_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const float *s = src[0]; @@ -879,7 +1024,7 @@ conv_deinterleave_32s_4s_sse2(void *data, void * SPA_RESTRICT dst[], const void } void -conv_deinterleave_32s_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], +conv_32s_to_32d_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const float *s = src[0]; @@ -900,8 +1045,9 @@ conv_f32_to_s16_1_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RES uint32_t n, unrolled; __m128 in[2]; __m128i out[2]; - __m128 int_max = _mm_set1_ps(S16_MAX_F); - __m128 int_min = _mm_sub_ps(_mm_setzero_ps(), int_max); + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); if (SPA_IS_ALIGNED(s, 16)) unrolled = n_samples & ~7; @@ -909,8 +1055,8 @@ conv_f32_to_s16_1_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RES unrolled = 0; for(n = 0; n < unrolled; n += 8) { - in[0] = _mm_mul_ps(_mm_load_ps(&s[n]), int_max); - in[1] = _mm_mul_ps(_mm_load_ps(&s[n+4]), int_max); + in[0] = _mm_mul_ps(_mm_load_ps(&s[n]), int_scale); + in[1] = _mm_mul_ps(_mm_load_ps(&s[n+4]), int_scale); out[0] = _mm_cvtps_epi32(in[0]); out[1] = _mm_cvtps_epi32(in[1]); out[0] = _mm_packs_epi32(out[0], out[1]); @@ -918,8 +1064,8 @@ conv_f32_to_s16_1_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RES d += 8; } for(; n < n_samples; n++) { - in[0] = _mm_mul_ss(_mm_load_ss(&s[n]), int_max); - in[0] = _mm_min_ss(int_max, _mm_max_ss(in[0], int_min)); + in[0] = _mm_mul_ss(_mm_load_ss(&s[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); *d++ = _mm_cvtss_si32(in[0]); } } @@ -949,8 +1095,9 @@ conv_f32d_to_s16_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R uint32_t n, unrolled; __m128 in[2]; __m128i out[2]; - __m128 int_max = _mm_set1_ps(S16_MAX_F); - __m128 int_min = _mm_sub_ps(_mm_setzero_ps(), int_max); + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); if (SPA_IS_ALIGNED(s0, 16)) unrolled = n_samples & ~7; @@ -958,8 +1105,8 @@ conv_f32d_to_s16_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R unrolled = 0; for(n = 0; n < unrolled; n += 8) { - in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), int_max); - in[1] = _mm_mul_ps(_mm_load_ps(&s0[n+4]), int_max); + in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), int_scale); + in[1] = _mm_mul_ps(_mm_load_ps(&s0[n+4]), int_scale); out[0] = _mm_cvtps_epi32(in[0]); out[1] = _mm_cvtps_epi32(in[1]); out[0] = _mm_packs_epi32(out[0], out[1]); @@ -975,8 +1122,8 @@ conv_f32d_to_s16_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R d += 8*n_channels; } for(; n < n_samples; n++) { - in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_max); - in[0] = _mm_min_ss(int_max, _mm_max_ss(in[0], int_min)); + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); *d = _mm_cvtss_si32(in[0]); d += n_channels; } @@ -991,8 +1138,9 @@ conv_f32d_to_s16_2s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R uint32_t n, unrolled; __m128 in[2]; __m128i out[4], t[2]; - __m128 int_max = _mm_set1_ps(S16_MAX_F); - __m128 int_min = _mm_sub_ps(_mm_setzero_ps(), int_max); + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); if (SPA_IS_ALIGNED(s0, 16) && SPA_IS_ALIGNED(s1, 16)) @@ -1001,8 +1149,8 @@ conv_f32d_to_s16_2s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R unrolled = 0; for(n = 0; n < unrolled; n += 4) { - in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), int_max); - in[1] = _mm_mul_ps(_mm_load_ps(&s1[n]), int_max); + in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), int_scale); + in[1] = _mm_mul_ps(_mm_load_ps(&s1[n]), int_scale); t[0] = _mm_cvtps_epi32(in[0]); t[1] = _mm_cvtps_epi32(in[1]); @@ -1022,10 +1170,10 @@ conv_f32d_to_s16_2s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R d += 4*n_channels; } for(; n < n_samples; n++) { - in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_max); - in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_max); - in[0] = _mm_min_ss(int_max, _mm_max_ss(in[0], int_min)); - in[1] = _mm_min_ss(int_max, _mm_max_ss(in[1], int_min)); + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); d[0] = _mm_cvtss_si32(in[0]); d[1] = _mm_cvtss_si32(in[1]); d += n_channels; @@ -1041,8 +1189,9 @@ conv_f32d_to_s16_4s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R uint32_t n, unrolled; __m128 in[4]; __m128i out[4], t[4]; - __m128 int_max = _mm_set1_ps(S16_MAX_F); - __m128 int_min = _mm_sub_ps(_mm_setzero_ps(), int_max); + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); if (SPA_IS_ALIGNED(s0, 16) && SPA_IS_ALIGNED(s1, 16) && @@ -1053,10 +1202,10 @@ conv_f32d_to_s16_4s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R unrolled = 0; for(n = 0; n < unrolled; n += 4) { - in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), int_max); - in[1] = _mm_mul_ps(_mm_load_ps(&s1[n]), int_max); - in[2] = _mm_mul_ps(_mm_load_ps(&s2[n]), int_max); - in[3] = _mm_mul_ps(_mm_load_ps(&s3[n]), int_max); + in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), int_scale); + in[1] = _mm_mul_ps(_mm_load_ps(&s1[n]), int_scale); + in[2] = _mm_mul_ps(_mm_load_ps(&s2[n]), int_scale); + in[3] = _mm_mul_ps(_mm_load_ps(&s3[n]), int_scale); t[0] = _mm_cvtps_epi32(in[0]); t[1] = _mm_cvtps_epi32(in[1]); @@ -1079,14 +1228,14 @@ conv_f32d_to_s16_4s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R d += 4*n_channels; } for(; n < n_samples; n++) { - in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_max); - in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_max); - in[2] = _mm_mul_ss(_mm_load_ss(&s2[n]), int_max); - in[3] = _mm_mul_ss(_mm_load_ss(&s3[n]), int_max); - in[0] = _mm_min_ss(int_max, _mm_max_ss(in[0], int_min)); - in[1] = _mm_min_ss(int_max, _mm_max_ss(in[1], int_min)); - in[2] = _mm_min_ss(int_max, _mm_max_ss(in[2], int_min)); - in[3] = _mm_min_ss(int_max, _mm_max_ss(in[3], int_min)); + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); + in[2] = _mm_mul_ss(_mm_load_ss(&s2[n]), int_scale); + in[3] = _mm_mul_ss(_mm_load_ss(&s3[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); + in[2] = _MM_CLAMP_SS(in[2], int_min, int_max); + in[3] = _MM_CLAMP_SS(in[3], int_min, int_max); d[0] = _mm_cvtss_si32(in[0]); d[1] = _mm_cvtss_si32(in[1]); d[2] = _mm_cvtss_si32(in[2]); @@ -1110,6 +1259,126 @@ conv_f32d_to_s16_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const voi conv_f32d_to_s16_1s_sse2(conv, &d[i], &src[i], n_channels, n_samples); } +static void +conv_f32d_to_s16_1s_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const float *s0 = src; + int16_t *d = dst; + float *noise = SPA_PTR_ALIGN(conv->noise, 16, float); + uint32_t n, unrolled; + __m128 in[2]; + __m128i out[2]; + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); + + if (SPA_IS_ALIGNED(s0, 16)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), int_scale); + in[1] = _mm_mul_ps(_mm_load_ps(&s0[n+4]), int_scale); + in[0] = _mm_add_ps(in[0], _mm_load_ps(&noise[n])); + in[1] = _mm_add_ps(in[1], _mm_load_ps(&noise[n+4])); + out[0] = _mm_cvtps_epi32(in[0]); + out[1] = _mm_cvtps_epi32(in[1]); + out[0] = _mm_packs_epi32(out[0], out[1]); + + d[0*n_channels] = _mm_extract_epi16(out[0], 0); + d[1*n_channels] = _mm_extract_epi16(out[0], 1); + d[2*n_channels] = _mm_extract_epi16(out[0], 2); + d[3*n_channels] = _mm_extract_epi16(out[0], 3); + d[4*n_channels] = _mm_extract_epi16(out[0], 4); + d[5*n_channels] = _mm_extract_epi16(out[0], 5); + d[6*n_channels] = _mm_extract_epi16(out[0], 6); + d[7*n_channels] = _mm_extract_epi16(out[0], 7); + d += 8*n_channels; + } + for(; n < n_samples; n++) { + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[0] = _mm_add_ss(in[0], _mm_load_ss(&noise[n])); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + *d = _mm_cvtss_si32(in[0]); + d += n_channels; + } +} + +void +conv_f32d_to_s16_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + int16_t *d = dst[0]; + uint32_t i, k, chunk, n_channels = conv->n_channels; + + update_noise_sse2(conv, SPA_MIN(n_samples, conv->noise_size)); + + for(i = 0; i < n_channels; i++) { + const float *s = src[i]; + for(k = 0; k < n_samples; k += chunk) { + chunk = SPA_MIN(n_samples - k, conv->noise_size); + conv_f32d_to_s16_1s_noise_sse2(conv, &d[i + k*n_channels], &s[k], n_channels, chunk); + } + } +} + +static void +conv_f32_to_s16_1_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, + uint32_t n_samples) +{ + const float *s = src; + int16_t *d = dst; + float *noise = SPA_PTR_ALIGN(conv->noise, 16, float); + uint32_t n, unrolled; + __m128 in[2]; + __m128i out[2]; + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); + + if (SPA_IS_ALIGNED(s, 16)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in[0] = _mm_mul_ps(_mm_load_ps(&s[n]), int_scale); + in[1] = _mm_mul_ps(_mm_load_ps(&s[n+4]), int_scale); + in[0] = _mm_add_ps(in[0], _mm_load_ps(&noise[n])); + in[1] = _mm_add_ps(in[1], _mm_load_ps(&noise[n+4])); + out[0] = _mm_cvtps_epi32(in[0]); + out[1] = _mm_cvtps_epi32(in[1]); + out[0] = _mm_packs_epi32(out[0], out[1]); + _mm_storeu_si128((__m128i*)(&d[n]), out[0]); + } + for(; n < n_samples; n++) { + in[0] = _mm_mul_ss(_mm_load_ss(&s[n]), int_scale); + in[0] = _mm_add_ss(in[0], _mm_load_ss(&noise[n])); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + d[n] = _mm_cvtss_si32(in[0]); + } +} + +void +conv_f32d_to_s16d_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + uint32_t i, k, chunk, n_channels = conv->n_channels; + + update_noise_sse2(conv, SPA_MIN(n_samples, conv->noise_size)); + + for(i = 0; i < n_channels; i++) { + const float *s = src[i]; + int16_t *d = dst[i]; + for(k = 0; k < n_samples; k += chunk) { + chunk = SPA_MIN(n_samples - k, conv->noise_size); + conv_f32_to_s16_1_noise_sse2(conv, &d[k], &s[k], chunk); + } + } +} + void conv_f32d_to_s16_2_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) @@ -1119,8 +1388,9 @@ conv_f32d_to_s16_2_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const v uint32_t n, unrolled; __m128 in[4]; __m128i out[4]; - __m128 int_max = _mm_set1_ps(S16_MAX_F); - __m128 int_min = _mm_sub_ps(_mm_setzero_ps(), int_max); + __m128 int_scale = _mm_set1_ps(S16_SCALE); + __m128 int_max = _mm_set1_ps(S16_MAX); + __m128 int_min = _mm_set1_ps(S16_MIN); if (SPA_IS_ALIGNED(s0, 16) && SPA_IS_ALIGNED(s1, 16)) @@ -1129,10 +1399,10 @@ conv_f32d_to_s16_2_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const v unrolled = 0; for(n = 0; n < unrolled; n += 8) { - in[0] = _mm_mul_ps(_mm_load_ps(&s0[n+0]), int_max); - in[1] = _mm_mul_ps(_mm_load_ps(&s1[n+0]), int_max); - in[2] = _mm_mul_ps(_mm_load_ps(&s0[n+4]), int_max); - in[3] = _mm_mul_ps(_mm_load_ps(&s1[n+4]), int_max); + in[0] = _mm_mul_ps(_mm_load_ps(&s0[n+0]), int_scale); + in[1] = _mm_mul_ps(_mm_load_ps(&s1[n+0]), int_scale); + in[2] = _mm_mul_ps(_mm_load_ps(&s0[n+4]), int_scale); + in[3] = _mm_mul_ps(_mm_load_ps(&s1[n+4]), int_scale); out[0] = _mm_cvtps_epi32(in[0]); out[1] = _mm_cvtps_epi32(in[1]); @@ -1151,10 +1421,10 @@ conv_f32d_to_s16_2_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const v d += 16; } for(; n < n_samples; n++) { - in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_max); - in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_max); - in[0] = _mm_min_ss(int_max, _mm_max_ss(in[0], int_min)); - in[1] = _mm_min_ss(int_max, _mm_max_ss(in[1], int_min)); + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); d[0] = _mm_cvtss_si32(in[0]); d[1] = _mm_cvtss_si32(in[1]); d += 2; diff --git a/spa/plugins/audioconvert/fmt-ops-sse41.c b/spa/plugins/audioconvert/fmt-ops-sse41.c index 0478555e8517a0676b8292665a48198cba998942..042294d6b317c7a0d43c17d00f2632a28562ae16 100644 --- a/spa/plugins/audioconvert/fmt-ops-sse41.c +++ b/spa/plugins/audioconvert/fmt-ops-sse41.c @@ -30,10 +30,10 @@ static void conv_s24_to_f32d_1s_sse41(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { - const uint8_t *s = src; + const int24_t *s = src; float *d0 = dst[0]; uint32_t n, unrolled; - __m128i in; + __m128i in = _mm_setzero_si128(); __m128 out, factor = _mm_set1_ps(1.0f / S24_SCALE); if (SPA_IS_ALIGNED(d0, 16)) @@ -43,21 +43,21 @@ conv_s24_to_f32d_1s_sse41(void *data, void * SPA_RESTRICT dst[], const void * SP for(n = 0; n < unrolled; n += 4) { in = _mm_insert_epi32(in, *((uint32_t*)&s[0 * n_channels]), 0); - in = _mm_insert_epi32(in, *((uint32_t*)&s[3 * n_channels]), 1); - in = _mm_insert_epi32(in, *((uint32_t*)&s[6 * n_channels]), 2); - in = _mm_insert_epi32(in, *((uint32_t*)&s[9 * n_channels]), 3); + in = _mm_insert_epi32(in, *((uint32_t*)&s[1 * n_channels]), 1); + in = _mm_insert_epi32(in, *((uint32_t*)&s[2 * n_channels]), 2); + in = _mm_insert_epi32(in, *((uint32_t*)&s[3 * n_channels]), 3); in = _mm_slli_epi32(in, 8); in = _mm_srai_epi32(in, 8); out = _mm_cvtepi32_ps(in); out = _mm_mul_ps(out, factor); _mm_store_ps(&d0[n], out); - s += 12 * n_channels; + s += 4 * n_channels; } for(; n < n_samples; n++) { - out = _mm_cvtsi32_ss(out, read_s24(s)); + out = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); out = _mm_mul_ss(out, factor); _mm_store_ss(&d0[n], out); - s += 3 * n_channels; + s += n_channels; } } diff --git a/spa/plugins/audioconvert/fmt-ops-ssse3.c b/spa/plugins/audioconvert/fmt-ops-ssse3.c index 6a7fc2e05399fdfc74d07b1483c0d9c97cb9befa..c35c7c9f3db7af5db69e5e684fc3cb3177cd0323 100644 --- a/spa/plugins/audioconvert/fmt-ops-ssse3.c +++ b/spa/plugins/audioconvert/fmt-ops-ssse3.c @@ -30,7 +30,7 @@ static void conv_s24_to_f32d_4s_ssse3(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { - const uint8_t *s = src; + const int24_t *s = src; float *d0 = dst[0], *d1 = dst[1], *d2 = dst[2], *d3 = dst[3]; uint32_t n, unrolled; __m128i in[4]; @@ -48,9 +48,9 @@ conv_s24_to_f32d_4s_ssse3(void *data, void * SPA_RESTRICT dst[], const void * SP for(n = 0; n < unrolled; n += 4) { in[0] = _mm_loadu_si128((__m128i*)(s + 0*n_channels)); - in[1] = _mm_loadu_si128((__m128i*)(s + 3*n_channels)); - in[2] = _mm_loadu_si128((__m128i*)(s + 6*n_channels)); - in[3] = _mm_loadu_si128((__m128i*)(s + 9*n_channels)); + in[1] = _mm_loadu_si128((__m128i*)(s + 1*n_channels)); + in[2] = _mm_loadu_si128((__m128i*)(s + 2*n_channels)); + in[3] = _mm_loadu_si128((__m128i*)(s + 3*n_channels)); in[0] = _mm_shuffle_epi8(in[0], mask); in[1] = _mm_shuffle_epi8(in[1], mask); in[2] = _mm_shuffle_epi8(in[2], mask); @@ -74,13 +74,13 @@ conv_s24_to_f32d_4s_ssse3(void *data, void * SPA_RESTRICT dst[], const void * SP _mm_store_ps(&d1[n], out[1]); _mm_store_ps(&d2[n], out[2]); _mm_store_ps(&d3[n], out[3]); - s += 12 * n_channels; + s += 4 * n_channels; } for(; n < n_samples; n++) { - out[0] = _mm_cvtsi32_ss(out[0], read_s24(s)); - out[1] = _mm_cvtsi32_ss(out[1], read_s24(s+3)); - out[2] = _mm_cvtsi32_ss(out[2], read_s24(s+6)); - out[3] = _mm_cvtsi32_ss(out[3], read_s24(s+9)); + out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); + out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+1))); + out[2] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+2))); + out[3] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+3))); out[0] = _mm_mul_ss(out[0], factor); out[1] = _mm_mul_ss(out[1], factor); out[2] = _mm_mul_ss(out[2], factor); @@ -89,7 +89,7 @@ conv_s24_to_f32d_4s_ssse3(void *data, void * SPA_RESTRICT dst[], const void * SP _mm_store_ss(&d1[n], out[1]); _mm_store_ss(&d2[n], out[2]); _mm_store_ss(&d3[n], out[3]); - s += 3 * n_channels; + s += n_channels; } } diff --git a/spa/plugins/audioconvert/fmt-ops.c b/spa/plugins/audioconvert/fmt-ops.c index 617793971a1efdc0da155194ee724da172f5f5a9..443d59eec729b7b0ffb61ad2c554bddde9e1b918 100644 --- a/spa/plugins/audioconvert/fmt-ops.c +++ b/spa/plugins/audioconvert/fmt-ops.c @@ -32,6 +32,8 @@ #include "fmt-ops.h" +#define DITHER_SIZE (1<<10) + typedef void (*convert_func_t) (struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples); @@ -39,277 +41,322 @@ struct conv_info { uint32_t src_fmt; uint32_t dst_fmt; uint32_t n_channels; - uint32_t cpu_flags; convert_func_t process; + const char *name; + + uint32_t cpu_flags; +#define CONV_NOISE (1<<0) +#define CONV_SHAPE (1<<1) + uint32_t conv_flags; }; +#define MAKE(fmt1,fmt2,chan,func,...) \ + { SPA_AUDIO_FORMAT_ ##fmt1, SPA_AUDIO_FORMAT_ ##fmt2, chan, func, #func , __VA_ARGS__ } + static struct conv_info conv_table[] = { /* to f32 */ - { SPA_AUDIO_FORMAT_U8, SPA_AUDIO_FORMAT_F32, 0, 0, conv_u8_to_f32_c }, - { SPA_AUDIO_FORMAT_U8P, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_u8d_to_f32d_c }, - { SPA_AUDIO_FORMAT_U8, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_u8_to_f32d_c }, - { SPA_AUDIO_FORMAT_U8P, SPA_AUDIO_FORMAT_F32, 0, 0, conv_u8d_to_f32_c }, + MAKE(U8, F32, 0, conv_u8_to_f32_c), + MAKE(U8, F32, 0, conv_u8_to_f32_c), + MAKE(U8P, F32P, 0, conv_u8d_to_f32d_c), + MAKE(U8, F32P, 0, conv_u8_to_f32d_c), + MAKE(U8P, F32, 0, conv_u8d_to_f32_c), - { SPA_AUDIO_FORMAT_S8, SPA_AUDIO_FORMAT_F32, 0, 0, conv_s8_to_f32_c }, - { SPA_AUDIO_FORMAT_S8P, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_s8d_to_f32d_c }, - { SPA_AUDIO_FORMAT_S8, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_s8_to_f32d_c }, - { SPA_AUDIO_FORMAT_S8P, SPA_AUDIO_FORMAT_F32, 0, 0, conv_s8d_to_f32_c }, + MAKE(S8, F32, 0, conv_s8_to_f32_c), + MAKE(S8P, F32P, 0, conv_s8d_to_f32d_c), + MAKE(S8, F32P, 0, conv_s8_to_f32d_c), + MAKE(S8P, F32, 0, conv_s8d_to_f32_c), - { SPA_AUDIO_FORMAT_ALAW, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_alaw_to_f32d_c }, - { SPA_AUDIO_FORMAT_ULAW, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_ulaw_to_f32d_c }, + MAKE(ALAW, F32P, 0, conv_alaw_to_f32d_c), + MAKE(ULAW, F32P, 0, conv_ulaw_to_f32d_c), - { SPA_AUDIO_FORMAT_U16, SPA_AUDIO_FORMAT_F32, 0, 0, conv_u16_to_f32_c }, - { SPA_AUDIO_FORMAT_U16, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_u16_to_f32d_c }, + MAKE(U16, F32, 0, conv_u16_to_f32_c), + MAKE(U16, F32P, 0, conv_u16_to_f32d_c), - { SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_F32, 0, 0, conv_s16_to_f32_c }, - { SPA_AUDIO_FORMAT_S16P, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_s16d_to_f32d_c }, + MAKE(S16, F32, 0, conv_s16_to_f32_c), + MAKE(S16P, F32P, 0, conv_s16d_to_f32d_c), #if defined (HAVE_NEON) - { SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_F32P, 2, SPA_CPU_FLAG_NEON, conv_s16_to_f32d_2_neon }, - { SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_F32P, 0, SPA_CPU_FLAG_NEON, conv_s16_to_f32d_neon }, + MAKE(S16, F32P, 2, conv_s16_to_f32d_2_neon, SPA_CPU_FLAG_NEON), + MAKE(S16, F32P, 0, conv_s16_to_f32d_neon, SPA_CPU_FLAG_NEON), #endif #if defined (HAVE_AVX2) - { SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_F32P, 2, SPA_CPU_FLAG_AVX2, conv_s16_to_f32d_2_avx2 }, - { SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_F32P, 0, SPA_CPU_FLAG_AVX2, conv_s16_to_f32d_avx2 }, + MAKE(S16, F32P, 2, conv_s16_to_f32d_2_avx2, SPA_CPU_FLAG_AVX2), + MAKE(S16, F32P, 0, conv_s16_to_f32d_avx2, SPA_CPU_FLAG_AVX2), #endif #if defined (HAVE_SSE2) - { SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_F32P, 2, SPA_CPU_FLAG_SSE2, conv_s16_to_f32d_2_sse2 }, - { SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_F32P, 0, SPA_CPU_FLAG_SSE2, conv_s16_to_f32d_sse2 }, + MAKE(S16, F32P, 2, conv_s16_to_f32d_2_sse2, SPA_CPU_FLAG_SSE2), + MAKE(S16, F32P, 0, conv_s16_to_f32d_sse2, SPA_CPU_FLAG_SSE2), #endif - { SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_s16_to_f32d_c }, - { SPA_AUDIO_FORMAT_S16P, SPA_AUDIO_FORMAT_F32, 0, 0, conv_s16d_to_f32_c }, + MAKE(S16, F32P, 0, conv_s16_to_f32d_c), + MAKE(S16P, F32, 0, conv_s16d_to_f32_c), - { SPA_AUDIO_FORMAT_S16_OE, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_s16s_to_f32d_c }, + MAKE(S16_OE, F32P, 0, conv_s16s_to_f32d_c), - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_F32, 0, 0, conv_copy32_c }, - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_copy32d_c }, + MAKE(F32, F32, 0, conv_copy32_c), + MAKE(F32P, F32P, 0, conv_copy32d_c), #if defined (HAVE_SSE2) - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_deinterleave_32_sse2 }, + MAKE(F32, F32P, 0, conv_32_to_32d_sse2, SPA_CPU_FLAG_SSE2), #endif - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_deinterleave_32_c }, + MAKE(F32, F32P, 0, conv_32_to_32d_c), #if defined (HAVE_SSE2) - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_F32, 0, 0, conv_interleave_32_sse2 }, + MAKE(F32P, F32, 0, conv_32d_to_32_sse2, SPA_CPU_FLAG_SSE2), #endif - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_F32, 0, 0, conv_interleave_32_c }, + MAKE(F32P, F32, 0, conv_32d_to_32_c), #if defined (HAVE_SSE2) - { SPA_AUDIO_FORMAT_F32_OE, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_deinterleave_32s_sse2 }, + MAKE(F32_OE, F32P, 0, conv_32s_to_32d_sse2, SPA_CPU_FLAG_SSE2), #endif - { SPA_AUDIO_FORMAT_F32_OE, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_deinterleave_32s_c }, + MAKE(F32_OE, F32P, 0, conv_32s_to_32d_c), #if defined (HAVE_SSE2) - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_F32_OE, 0, 0, conv_interleave_32s_sse2 }, + MAKE(F32P, F32_OE, 0, conv_32d_to_32s_sse2, SPA_CPU_FLAG_SSE2), #endif - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_F32_OE, 0, 0, conv_interleave_32s_c }, + MAKE(F32P, F32_OE, 0, conv_32d_to_32s_c), - { SPA_AUDIO_FORMAT_U32, SPA_AUDIO_FORMAT_F32, 0, 0, conv_u32_to_f32_c }, - { SPA_AUDIO_FORMAT_U32, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_u32_to_f32d_c }, + MAKE(U32, F32, 0, conv_u32_to_f32_c), + MAKE(U32, F32P, 0, conv_u32_to_f32d_c), #if defined (HAVE_AVX2) - { SPA_AUDIO_FORMAT_S32, SPA_AUDIO_FORMAT_F32P, 0, SPA_CPU_FLAG_AVX2, conv_s32_to_f32d_avx2 }, + MAKE(S32, F32P, 0, conv_s32_to_f32d_avx2, SPA_CPU_FLAG_AVX2), #endif #if defined (HAVE_SSE2) - { SPA_AUDIO_FORMAT_S32, SPA_AUDIO_FORMAT_F32P, 0, SPA_CPU_FLAG_SSE2, conv_s32_to_f32d_sse2 }, + MAKE(S32, F32P, 0, conv_s32_to_f32d_sse2, SPA_CPU_FLAG_SSE2), #endif - { SPA_AUDIO_FORMAT_S32, SPA_AUDIO_FORMAT_F32, 0, 0, conv_s32_to_f32_c }, - { SPA_AUDIO_FORMAT_S32P, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_s32d_to_f32d_c }, - { SPA_AUDIO_FORMAT_S32, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_s32_to_f32d_c }, - { SPA_AUDIO_FORMAT_S32P, SPA_AUDIO_FORMAT_F32, 0, 0, conv_s32d_to_f32_c }, + MAKE(S32, F32, 0, conv_s32_to_f32_c), + MAKE(S32P, F32P, 0, conv_s32d_to_f32d_c), + MAKE(S32, F32P, 0, conv_s32_to_f32d_c), + MAKE(S32P, F32, 0, conv_s32d_to_f32_c), - { SPA_AUDIO_FORMAT_S32_OE, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_s32s_to_f32d_c }, + MAKE(S32_OE, F32P, 0, conv_s32s_to_f32d_c), - { SPA_AUDIO_FORMAT_U24, SPA_AUDIO_FORMAT_F32, 0, 0, conv_u24_to_f32_c }, - { SPA_AUDIO_FORMAT_U24, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_u24_to_f32d_c }, + MAKE(U24, F32, 0, conv_u24_to_f32_c), + MAKE(U24, F32P, 0, conv_u24_to_f32d_c), - { SPA_AUDIO_FORMAT_S24, SPA_AUDIO_FORMAT_F32, 0, 0, conv_s24_to_f32_c }, - { SPA_AUDIO_FORMAT_S24P, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_s24d_to_f32d_c }, + MAKE(S24, F32, 0, conv_s24_to_f32_c), + MAKE(S24P, F32P, 0, conv_s24d_to_f32d_c), #if defined (HAVE_AVX2) - { SPA_AUDIO_FORMAT_S24, SPA_AUDIO_FORMAT_F32P, 0, SPA_CPU_FLAG_AVX2, conv_s24_to_f32d_avx2 }, + MAKE(S24, F32P, 0, conv_s24_to_f32d_avx2, SPA_CPU_FLAG_AVX2), #endif #if defined (HAVE_SSSE3) -// { SPA_AUDIO_FORMAT_S24, SPA_AUDIO_FORMAT_F32P, 0, SPA_CPU_FLAG_SSSE3, conv_s24_to_f32d_ssse3 }, +// MAKE(S24, F32P, 0, conv_s24_to_f32d_ssse3, SPA_CPU_FLAG_SSSE3), #endif #if defined (HAVE_SSE41) - { SPA_AUDIO_FORMAT_S24, SPA_AUDIO_FORMAT_F32P, 0, SPA_CPU_FLAG_SSE41, conv_s24_to_f32d_sse41 }, + MAKE(S24, F32P, 0, conv_s24_to_f32d_sse41, SPA_CPU_FLAG_SSE41), #endif #if defined (HAVE_SSE2) - { SPA_AUDIO_FORMAT_S24, SPA_AUDIO_FORMAT_F32P, 0, SPA_CPU_FLAG_SSE2, conv_s24_to_f32d_sse2 }, + MAKE(S24, F32P, 0, conv_s24_to_f32d_sse2, SPA_CPU_FLAG_SSE2), #endif - { SPA_AUDIO_FORMAT_S24, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_s24_to_f32d_c }, - { SPA_AUDIO_FORMAT_S24P, SPA_AUDIO_FORMAT_F32, 0, 0, conv_s24d_to_f32_c }, + MAKE(S24, F32P, 0, conv_s24_to_f32d_c), + MAKE(S24P, F32, 0, conv_s24d_to_f32_c), - { SPA_AUDIO_FORMAT_S24_OE, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_s24s_to_f32d_c }, + MAKE(S24_OE, F32P, 0, conv_s24s_to_f32d_c), - { SPA_AUDIO_FORMAT_U24_32, SPA_AUDIO_FORMAT_F32, 0, 0, conv_u24_32_to_f32_c }, - { SPA_AUDIO_FORMAT_U24_32, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_u24_32_to_f32d_c }, + MAKE(U24_32, F32, 0, conv_u24_32_to_f32_c), + MAKE(U24_32, F32P, 0, conv_u24_32_to_f32d_c), - { SPA_AUDIO_FORMAT_S24_32, SPA_AUDIO_FORMAT_F32, 0, 0, conv_s24_32_to_f32_c }, - { SPA_AUDIO_FORMAT_S24_32P, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_s24_32d_to_f32d_c }, - { SPA_AUDIO_FORMAT_S24_32, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_s24_32_to_f32d_c }, - { SPA_AUDIO_FORMAT_S24_32P, SPA_AUDIO_FORMAT_F32, 0, 0, conv_s24_32d_to_f32_c }, + MAKE(S24_32, F32, 0, conv_s24_32_to_f32_c), + MAKE(S24_32P, F32P, 0, conv_s24_32d_to_f32d_c), + MAKE(S24_32, F32P, 0, conv_s24_32_to_f32d_c), + MAKE(S24_32P, F32, 0, conv_s24_32d_to_f32_c), - { SPA_AUDIO_FORMAT_S24_32_OE, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_s24_32s_to_f32d_c }, + MAKE(S24_32_OE, F32P, 0, conv_s24_32s_to_f32d_c), - { SPA_AUDIO_FORMAT_F64, SPA_AUDIO_FORMAT_F32, 0, 0, conv_f64_to_f32_c }, - { SPA_AUDIO_FORMAT_F64P, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_f64d_to_f32d_c }, - { SPA_AUDIO_FORMAT_F64, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_f64_to_f32d_c }, - { SPA_AUDIO_FORMAT_F64P, SPA_AUDIO_FORMAT_F32, 0, 0, conv_f64d_to_f32_c }, + MAKE(F64, F32, 0, conv_f64_to_f32_c), + MAKE(F64P, F32P, 0, conv_f64d_to_f32d_c), + MAKE(F64, F32P, 0, conv_f64_to_f32d_c), + MAKE(F64P, F32, 0, conv_f64d_to_f32_c), - { SPA_AUDIO_FORMAT_F64_OE, SPA_AUDIO_FORMAT_F32P, 0, 0, conv_f64s_to_f32d_c }, + MAKE(F64_OE, F32P, 0, conv_f64s_to_f32d_c), /* from f32 */ - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_U8, 0, 0, conv_f32_to_u8_c }, - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_U8P, 0, 0, conv_f32d_to_u8d_c }, - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_U8P, 0, 0, conv_f32_to_u8d_c }, - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_U8, 0, 0, conv_f32d_to_u8_c }, - - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_S8, 0, 0, conv_f32_to_s8_c }, - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S8P, 0, 0, conv_f32d_to_s8d_c }, - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_S8P, 0, 0, conv_f32_to_s8d_c }, - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S8, 0, 0, conv_f32d_to_s8_c }, - - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_ALAW, 0, 0, conv_f32d_to_alaw_c }, - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_ULAW, 0, 0, conv_f32d_to_ulaw_c }, - - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_U16, 0, 0, conv_f32_to_u16_c }, - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_U16, 0, 0, conv_f32d_to_u16_c }, + MAKE(F32, U8, 0, conv_f32_to_u8_c), + MAKE(F32P, U8P, 0, conv_f32d_to_u8d_shaped_c, 0, CONV_SHAPE), + MAKE(F32P, U8P, 0, conv_f32d_to_u8d_noise_c, 0, CONV_NOISE), + MAKE(F32P, U8P, 0, conv_f32d_to_u8d_c), + MAKE(F32, U8P, 0, conv_f32_to_u8d_c), + MAKE(F32P, U8, 0, conv_f32d_to_u8_shaped_c, 0, CONV_SHAPE), + MAKE(F32P, U8, 0, conv_f32d_to_u8_noise_c, 0, CONV_NOISE), + MAKE(F32P, U8, 0, conv_f32d_to_u8_c), + + MAKE(F32, S8, 0, conv_f32_to_s8_c), + MAKE(F32P, S8P, 0, conv_f32d_to_s8d_shaped_c, 0, CONV_SHAPE), + MAKE(F32P, S8P, 0, conv_f32d_to_s8d_noise_c, 0, CONV_NOISE), + MAKE(F32P, S8P, 0, conv_f32d_to_s8d_c), + MAKE(F32, S8P, 0, conv_f32_to_s8d_c), + MAKE(F32P, S8, 0, conv_f32d_to_s8_shaped_c, 0, CONV_SHAPE), + MAKE(F32P, S8, 0, conv_f32d_to_s8_noise_c, 0, CONV_NOISE), + MAKE(F32P, S8, 0, conv_f32d_to_s8_c), + + MAKE(F32P, ALAW, 0, conv_f32d_to_alaw_c), + MAKE(F32P, ULAW, 0, conv_f32d_to_ulaw_c), + + MAKE(F32, U16, 0, conv_f32_to_u16_c), + MAKE(F32P, U16, 0, conv_f32d_to_u16_c), #if defined (HAVE_SSE2) - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_S16, 0, SPA_CPU_FLAG_SSE2, conv_f32_to_s16_sse2 }, + MAKE(F32, S16, 0, conv_f32_to_s16_sse2, SPA_CPU_FLAG_SSE2), #endif - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_S16, 0, 0, conv_f32_to_s16_c }, + MAKE(F32, S16, 0, conv_f32_to_s16_c), + MAKE(F32P, S16P, 0, conv_f32d_to_s16d_shaped_c, 0, CONV_SHAPE), +#if defined (HAVE_SSE2) + MAKE(F32P, S16P, 0, conv_f32d_to_s16d_noise_sse2, SPA_CPU_FLAG_SSE2, CONV_NOISE), +#endif + MAKE(F32P, S16P, 0, conv_f32d_to_s16d_noise_c, 0, CONV_NOISE), #if defined (HAVE_SSE2) - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S16P, 0, SPA_CPU_FLAG_SSE2, conv_f32d_to_s16d_sse2 }, + MAKE(F32P, S16P, 0, conv_f32d_to_s16d_sse2, SPA_CPU_FLAG_SSE2), #endif - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S16P, 0, 0, conv_f32d_to_s16d_c }, + MAKE(F32P, S16P, 0, conv_f32d_to_s16d_c), - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_S16P, 0, 0, conv_f32_to_s16d_c }, + MAKE(F32, S16P, 0, conv_f32_to_s16d_c), + MAKE(F32P, S16, 0, conv_f32d_to_s16_shaped_c, 0, CONV_SHAPE), +#if defined (HAVE_SSE2) + MAKE(F32P, S16, 0, conv_f32d_to_s16_noise_sse2, SPA_CPU_FLAG_SSE2, CONV_NOISE), +#endif + MAKE(F32P, S16, 0, conv_f32d_to_s16_noise_c, 0, CONV_NOISE), #if defined (HAVE_NEON) - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S16, 0, SPA_CPU_FLAG_NEON, conv_f32d_to_s16_neon }, + MAKE(F32P, S16, 0, conv_f32d_to_s16_neon, SPA_CPU_FLAG_NEON), #endif #if defined (HAVE_AVX2) - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S16, 4, SPA_CPU_FLAG_AVX2, conv_f32d_to_s16_4_avx2 }, - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S16, 2, SPA_CPU_FLAG_AVX2, conv_f32d_to_s16_2_avx2 }, - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S16, 0, SPA_CPU_FLAG_AVX2, conv_f32d_to_s16_avx2 }, + MAKE(F32P, S16, 4, conv_f32d_to_s16_4_avx2, SPA_CPU_FLAG_AVX2), + MAKE(F32P, S16, 2, conv_f32d_to_s16_2_avx2, SPA_CPU_FLAG_AVX2), + MAKE(F32P, S16, 0, conv_f32d_to_s16_avx2, SPA_CPU_FLAG_AVX2), #endif #if defined (HAVE_SSE2) - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S16, 2, SPA_CPU_FLAG_SSE2, conv_f32d_to_s16_2_sse2 }, - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S16, 0, SPA_CPU_FLAG_SSE2, conv_f32d_to_s16_sse2 }, + MAKE(F32P, S16, 2, conv_f32d_to_s16_2_sse2, SPA_CPU_FLAG_SSE2), + MAKE(F32P, S16, 0, conv_f32d_to_s16_sse2, SPA_CPU_FLAG_SSE2), #endif - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S16, 0, 0, conv_f32d_to_s16_c }, + MAKE(F32P, S16, 0, conv_f32d_to_s16_c), + + MAKE(F32P, S16_OE, 0, conv_f32d_to_s16s_shaped_c, 0, CONV_SHAPE), + MAKE(F32P, S16_OE, 0, conv_f32d_to_s16s_noise_c, 0, CONV_NOISE), + MAKE(F32P, S16_OE, 0, conv_f32d_to_s16s_c), - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S16_OE, 0, 0, conv_f32d_to_s16s_c }, + MAKE(F32, U32, 0, conv_f32_to_u32_c), + MAKE(F32P, U32, 0, conv_f32d_to_u32_c), - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_U32, 0, 0, conv_f32_to_u32_c }, - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_U32, 0, 0, conv_f32d_to_u32_c }, + MAKE(F32, S32, 0, conv_f32_to_s32_c), + MAKE(F32P, S32P, 0, conv_f32d_to_s32d_noise_c, 0, CONV_NOISE), + MAKE(F32P, S32P, 0, conv_f32d_to_s32d_c), + MAKE(F32, S32P, 0, conv_f32_to_s32d_c), + +#if defined (HAVE_SSE2) + MAKE(F32P, S32, 0, conv_f32d_to_s32_noise_sse2, SPA_CPU_FLAG_SSE2, CONV_NOISE), +#endif + MAKE(F32P, S32, 0, conv_f32d_to_s32_noise_c, 0, CONV_NOISE), - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_S32, 0, 0, conv_f32_to_s32_c }, - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S32P, 0, 0, conv_f32d_to_s32d_c }, - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_S32P, 0, 0, conv_f32_to_s32d_c }, #if defined (HAVE_AVX2) - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S32, 0, SPA_CPU_FLAG_AVX2, conv_f32d_to_s32_avx2 }, + MAKE(F32P, S32, 0, conv_f32d_to_s32_avx2, SPA_CPU_FLAG_AVX2), #endif #if defined (HAVE_SSE2) - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S32, 0, SPA_CPU_FLAG_SSE2, conv_f32d_to_s32_sse2 }, + MAKE(F32P, S32, 0, conv_f32d_to_s32_sse2, SPA_CPU_FLAG_SSE2), #endif - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S32, 0, 0, conv_f32d_to_s32_c }, + MAKE(F32P, S32, 0, conv_f32d_to_s32_c), - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S32_OE, 0, 0, conv_f32d_to_s32s_c }, + MAKE(F32P, S32_OE, 0, conv_f32d_to_s32s_noise_c, 0, CONV_NOISE), + MAKE(F32P, S32_OE, 0, conv_f32d_to_s32s_c), - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_U24, 0, 0, conv_f32_to_u24_c }, - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_U24, 0, 0, conv_f32d_to_u24_c }, + MAKE(F32, U24, 0, conv_f32_to_u24_c), + MAKE(F32P, U24, 0, conv_f32d_to_u24_c), - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_S24, 0, 0, conv_f32_to_s24_c }, - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S24P, 0, 0, conv_f32d_to_s24d_c }, - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_S24P, 0, 0, conv_f32_to_s24d_c }, - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S24, 0, 0, conv_f32d_to_s24_c }, + MAKE(F32, S24, 0, conv_f32_to_s24_c), + MAKE(F32P, S24P, 0, conv_f32d_to_s24d_noise_c, 0, CONV_NOISE), + MAKE(F32P, S24P, 0, conv_f32d_to_s24d_c), + MAKE(F32, S24P, 0, conv_f32_to_s24d_c), + MAKE(F32P, S24, 0, conv_f32d_to_s24_noise_c, 0, CONV_NOISE), + MAKE(F32P, S24, 0, conv_f32d_to_s24_c), - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S24_OE, 0, 0, conv_f32d_to_s24s_c }, + MAKE(F32P, S24_OE, 0, conv_f32d_to_s24s_noise_c, 0, CONV_NOISE), + MAKE(F32P, S24_OE, 0, conv_f32d_to_s24s_c), - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_U24_32, 0, 0, conv_f32_to_u24_32_c }, - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_U24_32, 0, 0, conv_f32d_to_u24_32_c }, + MAKE(F32, U24_32, 0, conv_f32_to_u24_32_c), + MAKE(F32P, U24_32, 0, conv_f32d_to_u24_32_c), - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_S24_32, 0, 0, conv_f32_to_s24_32_c }, - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S24_32P, 0, 0, conv_f32d_to_s24_32d_c }, - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_S24_32P, 0, 0, conv_f32_to_s24_32d_c }, - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S24_32, 0, 0, conv_f32d_to_s24_32_c }, + MAKE(F32, S24_32, 0, conv_f32_to_s24_32_c), + MAKE(F32P, S24_32P, 0, conv_f32d_to_s24_32d_noise_c, 0, CONV_NOISE), + MAKE(F32P, S24_32P, 0, conv_f32d_to_s24_32d_c), + MAKE(F32, S24_32P, 0, conv_f32_to_s24_32d_c), + MAKE(F32P, S24_32, 0, conv_f32d_to_s24_32_noise_c, 0, CONV_NOISE), + MAKE(F32P, S24_32, 0, conv_f32d_to_s24_32_c), - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S24_32_OE, 0, 0, conv_f32d_to_s24_32s_c }, + MAKE(F32P, S24_32_OE, 0, conv_f32d_to_s24_32s_noise_c, 0, CONV_NOISE), + MAKE(F32P, S24_32_OE, 0, conv_f32d_to_s24_32s_c), - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_F64, 0, 0, conv_f32_to_f64_c }, - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_F64P, 0, 0, conv_f32d_to_f64d_c }, - { SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_F64P, 0, 0, conv_f32_to_f64d_c }, - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_F64, 0, 0, conv_f32d_to_f64_c }, + MAKE(F32, F64, 0, conv_f32_to_f64_c), + MAKE(F32P, F64P, 0, conv_f32d_to_f64d_c), + MAKE(F32, F64P, 0, conv_f32_to_f64d_c), + MAKE(F32P, F64, 0, conv_f32d_to_f64_c), - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_F64_OE, 0, 0, conv_f32d_to_f64s_c }, + MAKE(F32P, F64_OE, 0, conv_f32d_to_f64s_c), /* u8 */ - { SPA_AUDIO_FORMAT_U8, SPA_AUDIO_FORMAT_U8, 0, 0, conv_copy8_c }, - { SPA_AUDIO_FORMAT_U8P, SPA_AUDIO_FORMAT_U8P, 0, 0, conv_copy8d_c }, - { SPA_AUDIO_FORMAT_U8, SPA_AUDIO_FORMAT_U8P, 0, 0, conv_deinterleave_8_c }, - { SPA_AUDIO_FORMAT_U8P, SPA_AUDIO_FORMAT_U8, 0, 0, conv_interleave_8_c }, + MAKE(U8, U8, 0, conv_copy8_c), + MAKE(U8P, U8P, 0, conv_copy8d_c), + MAKE(U8, U8P, 0, conv_8_to_8d_c), + MAKE(U8P, U8, 0, conv_8d_to_8_c), /* s8 */ - { SPA_AUDIO_FORMAT_S8, SPA_AUDIO_FORMAT_S8, 0, 0, conv_copy8_c }, - { SPA_AUDIO_FORMAT_S8P, SPA_AUDIO_FORMAT_S8P, 0, 0, conv_copy8d_c }, - { SPA_AUDIO_FORMAT_S8, SPA_AUDIO_FORMAT_S8P, 0, 0, conv_deinterleave_8_c }, - { SPA_AUDIO_FORMAT_S8P, SPA_AUDIO_FORMAT_S8, 0, 0, conv_interleave_8_c }, + MAKE(S8, S8, 0, conv_copy8_c), + MAKE(S8P, S8P, 0, conv_copy8d_c), + MAKE(S8, S8P, 0, conv_8_to_8d_c), + MAKE(S8P, S8, 0, conv_8d_to_8_c), /* alaw */ - { SPA_AUDIO_FORMAT_ALAW, SPA_AUDIO_FORMAT_ALAW, 0, 0, conv_copy8_c }, + MAKE(ALAW, ALAW, 0, conv_copy8_c), /* ulaw */ - { SPA_AUDIO_FORMAT_ULAW, SPA_AUDIO_FORMAT_ULAW, 0, 0, conv_copy8_c }, + MAKE(ULAW, ULAW, 0, conv_copy8_c), /* s16 */ - { SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_S16, 0, 0, conv_copy16_c }, - { SPA_AUDIO_FORMAT_S16P, SPA_AUDIO_FORMAT_S16P, 0, 0, conv_copy16d_c }, - { SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_S16P, 0, 0, conv_deinterleave_16_c }, - { SPA_AUDIO_FORMAT_S16P, SPA_AUDIO_FORMAT_S16, 0, 0, conv_interleave_16_c }, + MAKE(S16, S16, 0, conv_copy16_c), + MAKE(S16P, S16P, 0, conv_copy16d_c), + MAKE(S16, S16P, 0, conv_16_to_16d_c), + MAKE(S16P, S16, 0, conv_16d_to_16_c), /* s32 */ - { SPA_AUDIO_FORMAT_S32, SPA_AUDIO_FORMAT_S32, 0, 0, conv_copy32_c }, - { SPA_AUDIO_FORMAT_S32P, SPA_AUDIO_FORMAT_S32P, 0, 0, conv_copy32d_c }, + MAKE(S32, S32, 0, conv_copy32_c), + MAKE(S32P, S32P, 0, conv_copy32d_c), #if defined (HAVE_SSE2) - { SPA_AUDIO_FORMAT_S32, SPA_AUDIO_FORMAT_S32P, 0, 0, conv_deinterleave_32_sse2 }, + MAKE(S32, S32P, 0, conv_32_to_32d_sse2, SPA_CPU_FLAG_SSE2), #endif - { SPA_AUDIO_FORMAT_S32, SPA_AUDIO_FORMAT_S32P, 0, 0, conv_deinterleave_32_c }, + MAKE(S32, S32P, 0, conv_32_to_32d_c), #if defined (HAVE_SSE2) - { SPA_AUDIO_FORMAT_S32P, SPA_AUDIO_FORMAT_S32, 0, 0, conv_interleave_32_sse2 }, + MAKE(S32P, S32, 0, conv_32d_to_32_sse2, SPA_CPU_FLAG_SSE2), #endif - { SPA_AUDIO_FORMAT_S32P, SPA_AUDIO_FORMAT_S32, 0, 0, conv_interleave_32_c }, + MAKE(S32P, S32, 0, conv_32d_to_32_c), /* s24 */ - { SPA_AUDIO_FORMAT_S24, SPA_AUDIO_FORMAT_S24, 0, 0, conv_copy24_c }, - { SPA_AUDIO_FORMAT_S24P, SPA_AUDIO_FORMAT_S24P, 0, 0, conv_copy24d_c }, - { SPA_AUDIO_FORMAT_S24, SPA_AUDIO_FORMAT_S24P, 0, 0, conv_deinterleave_24_c }, - { SPA_AUDIO_FORMAT_S24P, SPA_AUDIO_FORMAT_S24, 0, 0, conv_interleave_24_c }, + MAKE(S24, S24, 0, conv_copy24_c), + MAKE(S24P, S24P, 0, conv_copy24d_c), + MAKE(S24, S24P, 0, conv_24_to_24d_c), + MAKE(S24P, S24, 0, conv_24d_to_24_c), /* s24_32 */ - { SPA_AUDIO_FORMAT_S24_32, SPA_AUDIO_FORMAT_S24_32, 0, 0, conv_copy32_c }, - { SPA_AUDIO_FORMAT_S24_32P, SPA_AUDIO_FORMAT_S24_32P, 0, 0, conv_copy32d_c }, + MAKE(S24_32, S24_32, 0, conv_copy32_c), + MAKE(S24_32P, S24_32P, 0, conv_copy32d_c), #if defined (HAVE_SSE2) - { SPA_AUDIO_FORMAT_S24_32, SPA_AUDIO_FORMAT_S24_32P, 0, 0, conv_deinterleave_32_sse2 }, + MAKE(S24_32, S24_32P, 0, conv_32_to_32d_sse2, SPA_CPU_FLAG_SSE2), #endif - { SPA_AUDIO_FORMAT_S24_32, SPA_AUDIO_FORMAT_S24_32P, 0, 0, conv_deinterleave_32_c }, + MAKE(S24_32, S24_32P, 0, conv_32_to_32d_c), #if defined (HAVE_SSE2) - { SPA_AUDIO_FORMAT_S24_32P, SPA_AUDIO_FORMAT_S24_32, 0, 0, conv_interleave_32_sse2 }, + MAKE(S24_32P, S24_32, 0, conv_32d_to_32_sse2, SPA_CPU_FLAG_SSE2), #endif - { SPA_AUDIO_FORMAT_S24_32P, SPA_AUDIO_FORMAT_S24_32, 0, 0, conv_interleave_32_c }, + MAKE(S24_32P, S24_32, 0, conv_32d_to_32_c), /* F64 */ - { SPA_AUDIO_FORMAT_F64, SPA_AUDIO_FORMAT_F64, 0, 0, conv_copy64_c }, - { SPA_AUDIO_FORMAT_F64P, SPA_AUDIO_FORMAT_F64P, 0, 0, conv_copy64d_c }, - { SPA_AUDIO_FORMAT_F64, SPA_AUDIO_FORMAT_F64P, 0, 0, conv_deinterleave_64_c }, - { SPA_AUDIO_FORMAT_F64P, SPA_AUDIO_FORMAT_F64, 0, 0, conv_interleave_64_c }, + MAKE(F64, F64, 0, conv_copy64_c), + MAKE(F64P, F64P, 0, conv_copy64d_c), + MAKE(F64, F64P, 0, conv_64_to_64d_c), + MAKE(F64P, F64, 0, conv_64d_to_64_c), }; +#undef MAKE #define MATCH_CHAN(a,b) ((a) == 0 || (a) == (b)) #define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) +#define MATCH_DITHER(a,b) ((a) == 0 || ((a) & (b)) == a) static const struct conv_info *find_conv_info(uint32_t src_fmt, uint32_t dst_fmt, - uint32_t n_channels, uint32_t cpu_flags) + uint32_t n_channels, uint32_t cpu_flags, uint32_t conv_flags) { size_t i; @@ -317,7 +364,8 @@ static const struct conv_info *find_conv_info(uint32_t src_fmt, uint32_t dst_fmt if (conv_table[i].src_fmt == src_fmt && conv_table[i].dst_fmt == dst_fmt && MATCH_CHAN(conv_table[i].n_channels, n_channels) && - MATCH_CPU_FLAGS(conv_table[i].cpu_flags, cpu_flags)) + MATCH_CPU_FLAGS(conv_table[i].cpu_flags, cpu_flags) && + MATCH_DITHER(conv_table[i].conv_flags, conv_flags)) return &conv_table[i]; } return NULL; @@ -326,20 +374,140 @@ static const struct conv_info *find_conv_info(uint32_t src_fmt, uint32_t dst_fmt static void impl_convert_free(struct convert *conv) { conv->process = NULL; + free(conv->noise); + conv->noise = NULL; +} + +static bool need_dither(uint32_t format) +{ + switch (format) { + case SPA_AUDIO_FORMAT_U8: + case SPA_AUDIO_FORMAT_U8P: + case SPA_AUDIO_FORMAT_S8: + case SPA_AUDIO_FORMAT_S8P: + case SPA_AUDIO_FORMAT_ULAW: + case SPA_AUDIO_FORMAT_ALAW: + case SPA_AUDIO_FORMAT_S16P: + case SPA_AUDIO_FORMAT_S16: + case SPA_AUDIO_FORMAT_S16_OE: + return true; + } + return false; +} + +/* filters based on F-weighted curves + * from 'Psychoacoustically Optimal Noise Shaping' (**) + * this filter is the "F-Weighted" noise filter described by Wannamaker + * It is designed to produce minimum audibility: */ +static const float wan3[] = { /* Table 3; 3 Coefficients */ + 1.623f, -0.982f, 0.109f +}; +/* Noise shaping coefficients from[1], moves most power of the + * error noise into inaudible frequency ranges. + * + * [1] + * "Minimally Audible Noise Shaping", Stanley P. Lipshitz, + * John Vanderkooy, and Robert A. Wannamaker, + * J. Audio Eng. Soc., Vol. 39, No. 11, November 1991. */ +static const float lips44[] = { /* improved E-weighted (appendix: 5) */ + 2.033f, -2.165f, 1.959f, -1.590f, 0.6149f +}; + +static const struct dither_info { + uint32_t method; + uint32_t noise_method; + uint32_t rate; + const float *ns; + uint32_t n_ns; +} dither_info[] = { + { DITHER_METHOD_NONE, NOISE_METHOD_NONE, }, + { DITHER_METHOD_RECTANGULAR, NOISE_METHOD_RECTANGULAR, }, + { DITHER_METHOD_TRIANGULAR, NOISE_METHOD_TRIANGULAR, }, + { DITHER_METHOD_TRIANGULAR_HF, NOISE_METHOD_TRIANGULAR_HF, }, + { DITHER_METHOD_WANNAMAKER_3, NOISE_METHOD_TRIANGULAR_HF, 44100, wan3, SPA_N_ELEMENTS(wan3) }, + { DITHER_METHOD_LIPSHITZ, NOISE_METHOD_TRIANGULAR, 44100, lips44, SPA_N_ELEMENTS(lips44) } +}; + +static const struct dither_info *find_dither_info(uint32_t method, uint32_t rate) +{ + size_t i; + + for (i = 0; i < SPA_N_ELEMENTS(dither_info); i++) { + const struct dither_info *di = &dither_info[i]; + if (di->method != method) + continue; + /* don't use shaped for too low rates, it moves the noise to + * audible ranges */ + if (di->ns != NULL && rate < di->rate * 3 / 4) + return find_dither_info(DITHER_METHOD_TRIANGULAR_HF, rate); + return &dither_info[i]; + } + return NULL; } int convert_init(struct convert *conv) { const struct conv_info *info; + const struct dither_info *dinfo; + uint32_t i, conv_flags; + + conv->scale = 1.0f / (float)(INT32_MAX); + + /* disable dither if not needed */ + if (!need_dither(conv->dst_fmt)) + conv->method = DITHER_METHOD_NONE; + + dinfo = find_dither_info(conv->method, conv->rate); + if (dinfo == NULL) + return -EINVAL; + + conv->noise_method = dinfo->noise_method; + if (conv->noise_bits > 0) { + switch (conv->noise_method) { + case NOISE_METHOD_NONE: + conv->noise_method = NOISE_METHOD_PATTERN; + conv->scale = -1.0f * (1 << (conv->noise_bits-1)); + break; + case NOISE_METHOD_RECTANGULAR: + conv->noise_method = NOISE_METHOD_TRIANGULAR; + SPA_FALLTHROUGH; + case NOISE_METHOD_TRIANGULAR: + case NOISE_METHOD_TRIANGULAR_HF: + conv->scale *= (1 << (conv->noise_bits-1)); + break; + } + } + if (conv->noise_method < NOISE_METHOD_TRIANGULAR) + conv->scale *= 0.5f; + + conv_flags = 0; + if (conv->noise_method != NOISE_METHOD_NONE) + conv_flags |= CONV_NOISE; + if (dinfo->n_ns > 0) { + conv_flags |= CONV_SHAPE; + conv->n_ns = dinfo->n_ns; + conv->ns = dinfo->ns; + } - info = find_conv_info(conv->src_fmt, conv->dst_fmt, conv->n_channels, conv->cpu_flags); + info = find_conv_info(conv->src_fmt, conv->dst_fmt, conv->n_channels, + conv->cpu_flags, conv_flags); if (info == NULL) return -ENOTSUP; + conv->noise_size = DITHER_SIZE; + conv->noise = calloc(conv->noise_size + 16 + + FMT_OPS_MAX_ALIGN / sizeof(float), sizeof(float)); + if (conv->noise == NULL) + return -errno; + + for (i = 0; i < SPA_N_ELEMENTS(conv->random); i++) + conv->random[i] = random(); + conv->is_passthrough = conv->src_fmt == conv->dst_fmt; conv->cpu_flags = info->cpu_flags; conv->process = info->process; conv->free = impl_convert_free; + conv->func_name = info->name; return 0; } diff --git a/spa/plugins/audioconvert/fmt-ops.h b/spa/plugins/audioconvert/fmt-ops.h index 3f31e1a4762bc51842beada35f832279ef8bcef4..a4cd1de1125e0ea06b6cc777b03421a3fb6d508d 100644 --- a/spa/plugins/audioconvert/fmt-ops.h +++ b/spa/plugins/audioconvert/fmt-ops.h @@ -23,7 +23,7 @@ */ #include <math.h> -#ifdef __FreeBSD__ +#if defined(__FreeBSD__) || defined(__MidnightBSD__) #include <sys/endian.h> #define bswap_16 bswap16 #define bswap_32 bswap32 @@ -33,158 +33,212 @@ #endif #include <spa/utils/defs.h> +#include <spa/utils/string.h> -#define U8_MIN 0 -#define U8_MAX 255 -#define U8_SCALE 127.5f -#define U8_OFFS 128 -#define U8_TO_F32(v) ((((uint8_t)(v)) * (1.0f / U8_OFFS)) - 1.0) -#define F32_TO_U8(v) (uint8_t)((SPA_CLAMP(v, -1.0f, 1.0f) * U8_SCALE) + U8_OFFS) - -#define S8_MIN -127 -#define S8_MAX 127 -#define S8_MAX_F 127.0f -#define S8_SCALE 127.0f -#define S8_TO_F32(v) (((int8_t)(v)) * (1.0f / S8_SCALE)) -#define F32_TO_S8(v) (int8_t)(SPA_CLAMP(v, -1.0f, 1.0f) * S8_SCALE) - -#define U16_MIN 0 -#define U16_MAX 65535 -#define U16_SCALE 32767.5f -#define U16_OFFS 32768 -#define U16_TO_F32(v) ((((uint16_t)(v)) * (1.0f / U16_OFFS)) - 1.0) -#define U16S_TO_F32(v) (((uint16_t)bswap_16((uint16_t)(v)) * (1.0f / U16_OFFS)) - 1.0) -#define F32_TO_U16(v) (uint16_t)((SPA_CLAMP(v, -1.0f, 1.0f) * U16_SCALE) + U16_OFFS) -#define F32_TO_U16S(v) ((uint16_t)bswap_16((uint16_t)((SPA_CLAMP(v, -1.0f, 1.0f) * U16_SCALE) + U16_OFFS))) - -#define S16_MIN -32767 -#define S16_MAX 32767 -#define S16_MAX_F 32767.0f -#define S16_SCALE 32767.0f -#define S16_TO_F32(v) (((int16_t)(v)) * (1.0f / S16_SCALE)) -#define S16S_TO_F32(v) (((int16_t)bswap_16((uint16_t)v)) * (1.0f / S16_SCALE)) -#define F32_TO_S16(v) (int16_t)(SPA_CLAMP(v, -1.0f, 1.0f) * S16_SCALE) -#define F32_TO_S16S(v) ((int16_t)bswap_16((uint16_t)(SPA_CLAMP(v, -1.0f, 1.0f) * S16_SCALE))) - -#define U24_MIN 0 -#define U24_MAX 16777215 -#define U24_SCALE 8388607.5f -#define U24_OFFS 8388608 -#define U24_TO_F32(v) ((((uint32_t)(v)) * (1.0f / U24_OFFS)) - 1.0) -#define F32_TO_U24(v) (uint32_t)((SPA_CLAMP(v, -1.0f, 1.0f) * U24_SCALE) + U24_OFFS) - -#define S24_MIN -8388607 -#define S24_MAX 8388607 -#define S24_MAX_F 8388607.0f -#define S24_SCALE 8388607.0f -#define S24_TO_F32(v) (((int32_t)(v)) * (1.0f / S24_SCALE)) -#define F32_TO_S24(v) (int32_t)(SPA_CLAMP(v, -1.0f, 1.0f) * S24_SCALE) - -#define U32_TO_F32(v) U24_TO_F32(((uint32_t)(v)) >> 8) -#define F32_TO_U32(v) (F32_TO_U24(v) << 8) - -#define S32_SCALE 2147483648.0f -#define S32_MIN 2147483520.0f - -#define S32_TO_F32(v) S24_TO_F32(((int32_t)(v)) >> 8) -#define S32S_TO_F32(v) S24_TO_F32(((int32_t)bswap_32(v)) >> 8) -#define F32_TO_S32(v) (F32_TO_S24(v) << 8) -#define F32_TO_S32S(v) bswap_32((F32_TO_S24(v) << 8)) +#define f32_round(a) lrintf(a) + +#define ITOF(type,v,scale,offs) \ + (((type)(v)) * (1.0f / (scale)) - (offs)) +#define FTOI(type,v,scale,offs,noise,min,max) \ + (type)f32_round(SPA_CLAMPF((v) * (scale) + (offs) + (noise), min, max)) + +#define FMT_OPS_MAX_ALIGN 32 + +#define U8_MIN 0u +#define U8_MAX 255u +#define U8_SCALE 128.f +#define U8_OFFS 128.f +#define U8_TO_F32(v) ITOF(uint8_t, v, U8_SCALE, 1.0f) +#define F32_TO_U8_D(v,d) FTOI(uint8_t, v, U8_SCALE, U8_OFFS, d, U8_MIN, U8_MAX) +#define F32_TO_U8(v) F32_TO_U8_D(v, 0.0f) + +#define S8_MIN -128 +#define S8_MAX 127 +#define S8_SCALE 128.0f +#define S8_TO_F32(v) ITOF(int8_t, v, S8_SCALE, 0.0f) +#define F32_TO_S8_D(v,d) FTOI(int8_t, v, S8_SCALE, 0.0f, d, S8_MIN, S8_MAX) +#define F32_TO_S8(v) F32_TO_S8_D(v, 0.0f); + +#define U16_MIN 0u +#define U16_MAX 65535u +#define U16_SCALE 32768.f +#define U16_OFFS 32768.f +#define U16_TO_F32(v) ITOF(uint16_t, v, U16_SCALE, 1.0f) +#define U16S_TO_F32(v) U16_TO_F32(bswap_16(v)) +#define F32_TO_U16_D(v,d) FTOI(uint16_t, v, U16_SCALE, U16_OFFS, d, U16_MIN, U16_MAX) +#define F32_TO_U16(v) F32_TO_U16_D(v, 0.0f); +#define F32_TO_U16S_D(v,d) bswap_16(F32_TO_U16_D(v,d)) +#define F32_TO_U16S(v) bswap_16(F32_TO_U16(v)) + +#define S16_MIN -32768 +#define S16_MAX 32767 +#define S16_SCALE 32768.0f +#define S16_TO_F32(v) ITOF(int16_t, v, S16_SCALE, 0.0f) +#define S16S_TO_F32(v) S16_TO_F32(bswap_16(v)) +#define F32_TO_S16_D(v,d) FTOI(int16_t, v, S16_SCALE, 0.0f, d, S16_MIN, S16_MAX) +#define F32_TO_S16(v) F32_TO_S16_D(v, 0.0f) +#define F32_TO_S16S_D(v,d) bswap_16(F32_TO_S16_D(v,d)) +#define F32_TO_S16S(v) bswap_16(F32_TO_S16(v)) + +#define U24_MIN 0u +#define U24_MAX 16777215u +#define U24_SCALE 8388608.f +#define U24_OFFS 8388608.f +#define U24_TO_F32(v) ITOF(uint32_t, u24_to_u32(v), U24_SCALE, 1.0f) +#define F32_TO_U24_D(v,d) u32_to_u24(FTOI(uint32_t, v, U24_SCALE, U24_OFFS, d, U24_MIN, U24_MAX)) +#define F32_TO_U24(v) F32_TO_U24_D(v, 0.0f) + +#define S24_MIN -8388608 +#define S24_MAX 8388607 +#define S24_SCALE 8388608.0f +#define S24_TO_F32(v) ITOF(int32_t, s24_to_s32(v), S24_SCALE, 0.0f) +#define S24S_TO_F32(v) S24_TO_F32(bswap_s24(v)) +#define F32_TO_S24_D(v,d) s32_to_s24(FTOI(int32_t, v, S24_SCALE, 0.0f, d, S24_MIN, S24_MAX)) +#define F32_TO_S24(v) F32_TO_S24_D(v, 0.0f) +#define F32_TO_S24S(v) bswap_s24(F32_TO_S24(v)) #define U24_32_TO_F32(v) U32_TO_F32((v)<<8) -#define U24_32S_TO_F32(v) U32_TO_F32(((int32_t)bswap_32(v))<<8) -#define F32_TO_U24_32(v) F32_TO_U24(v) -#define F32_TO_U24_32S(v) bswap_32(F32_TO_U24(v)) +#define U24_32S_TO_F32(v) U24_32_TO_F32(bswap_32(v)) +#define F32_TO_U24_32_D(v,d) FTOI(uint32_t, v, U24_SCALE, U24_OFFS, d, U24_MIN, U24_MAX) +#define F32_TO_U24_32(v) F32_TO_U24_32_D(v, 0.0f) +#define F32_TO_U24_32S(v) bswap_32(F32_TO_U24_32(v)) +#define F32_TO_U24_32S_D(v,d) bswap_32(F32_TO_U24_32_D(v,d)) + +#define U32_MIN 0u +#define U32_MAX 4294967295u +#define U32_SCALE 2147483648.f +#define U32_OFFS 2147483648.f +#define U32_TO_F32(v) ITOF(uint32_t, (v) >> 8, U24_SCALE, 1.0f) +#define F32_TO_U32(v) (F32_TO_U24_32(v) << 8) +#define F32_TO_U32_D(v,d) (F32_TO_U24_32_D(v,d) << 8) #define S24_32_TO_F32(v) S32_TO_F32((v)<<8) -#define S24_32S_TO_F32(v) S32_TO_F32(((int32_t)bswap_32(v))<<8) -#define F32_TO_S24_32(v) F32_TO_S24(v) -#define F32_TO_S24_32S(v) bswap_32(F32_TO_S24(v)) +#define S24_32S_TO_F32(v) S24_32_TO_F32(bswap_32(v)) +#define F32_TO_S24_32_D(v,d) FTOI(int32_t, v, S24_SCALE, 0.0f, d, S24_MIN, S24_MAX) +#define F32_TO_S24_32(v) F32_TO_S24_32_D(v, 0.0f) +#define F32_TO_S24_32S(v) bswap_32(F32_TO_S24_32(v)) +#define F32_TO_S24_32S_D(v,d) bswap_32(F32_TO_S24_32_D(v,d)) -static inline uint32_t read_u24(const void *src) -{ - const uint8_t *s = src; +#define S32_MIN (S24_MIN * 256) +#define S32_MAX (S24_MAX * 256) +#define S32_TO_F32(v) ITOF(int32_t, (v) >> 8, S24_SCALE, 0.0f) +#define S32S_TO_F32(v) S32_TO_F32(bswap_32(v)) +#define F32_TO_S32(v) (F32_TO_S24_32(v) << 8) +#define F32_TO_S32_D(v,d) (F32_TO_S24_32_D(v,d) << 8) +#define F32_TO_S32S(v) bswap_32(F32_TO_S32(v)) +#define F32_TO_S32S_D(v,d) bswap_32(F32_TO_S32_D(v,d)) + +typedef struct { #if __BYTE_ORDER == __LITTLE_ENDIAN - return (((uint32_t)s[2] << 16) | ((uint32_t)(uint8_t)s[1] << 8) | (uint32_t)(uint8_t)s[0]); + uint8_t v3; + uint8_t v2; + uint8_t v1; #else - return (((uint32_t)s[0] << 16) | ((uint32_t)(uint8_t)s[1] << 8) | (uint32_t)(uint8_t)s[2]); + uint8_t v1; + uint8_t v2; + uint8_t v3; #endif -} +} __attribute__ ((packed)) uint24_t; -static inline int32_t read_s24(const void *src) -{ - const int8_t *s = src; +typedef struct { #if __BYTE_ORDER == __LITTLE_ENDIAN - return (((int32_t)s[2] << 16) | ((uint32_t)(uint8_t)s[1] << 8) | (uint32_t)(uint8_t)s[0]); + uint8_t v3; + uint8_t v2; + int8_t v1; #else - return (((int32_t)s[0] << 16) | ((uint32_t)(uint8_t)s[1] << 8) | (uint32_t)(uint8_t)s[2]); + int8_t v1; + uint8_t v2; + uint8_t v3; #endif +} __attribute__ ((packed)) int24_t; + +static inline uint32_t u24_to_u32(uint24_t src) +{ + return ((uint32_t)src.v1 << 16) | ((uint32_t)src.v2 << 8) | (uint32_t)src.v3; } -static inline int32_t read_s24s(const void *src) +#define U32_TO_U24(s) (uint24_t) { .v1 = (uint8_t)(((uint32_t)s) >> 16), \ + .v2 = (uint8_t)(((uint32_t)s) >> 8), .v3 = (uint8_t)((uint32_t)s) } + +static inline uint24_t u32_to_u24(uint32_t src) { - const int8_t *s = src; -#if __BYTE_ORDER == __LITTLE_ENDIAN - return (((int32_t)s[0] << 16) | ((uint32_t)(uint8_t)s[1] << 8) | (uint32_t)(uint8_t)s[2]); -#else - return (((int32_t)s[2] << 16) | ((uint32_t)(uint8_t)s[1] << 8) | (uint32_t)(uint8_t)s[0]); -#endif + return U32_TO_U24(src); } -static inline void write_u24(void *dst, uint32_t val) +static inline int32_t s24_to_s32(int24_t src) { - uint8_t *d = dst; -#if __BYTE_ORDER == __LITTLE_ENDIAN - d[0] = (uint8_t) (val); - d[1] = (uint8_t) (val >> 8); - d[2] = (uint8_t) (val >> 16); -#else - d[0] = (uint8_t) (val >> 16); - d[1] = (uint8_t) (val >> 8); - d[2] = (uint8_t) (val); -#endif + return ((int32_t)src.v1 << 16) | ((uint32_t)src.v2 << 8) | (uint32_t)src.v3; } -static inline void write_s24(void *dst, int32_t val) +#define S32_TO_S24(s) (int24_t) { .v1 = (int8_t)(((int32_t)s) >> 16), \ + .v2 = (uint8_t)(((uint32_t)s) >> 8), .v3 = (uint8_t)((uint32_t)s) } + +static inline int24_t s32_to_s24(int32_t src) { - uint8_t *d = dst; -#if __BYTE_ORDER == __LITTLE_ENDIAN - d[0] = (uint8_t) (val); - d[1] = (uint8_t) (val >> 8); - d[2] = (uint8_t) (val >> 16); -#else - d[0] = (uint8_t) (val >> 16); - d[1] = (uint8_t) (val >> 8); - d[2] = (uint8_t) (val); -#endif + return S32_TO_S24(src); } -static inline void write_s24s(void *dst, int32_t val) +static inline uint24_t bswap_u24(uint24_t src) { - uint8_t *d = dst; -#if __BYTE_ORDER == __LITTLE_ENDIAN - d[0] = (uint8_t) (val >> 16); - d[1] = (uint8_t) (val >> 8); - d[2] = (uint8_t) (val); -#else - d[0] = (uint8_t) (val); - d[1] = (uint8_t) (val >> 8); - d[2] = (uint8_t) (val >> 16); -#endif + return (uint24_t) { .v1 = src.v3, .v2 = src.v2, .v3 = src.v1 }; +} +static inline int24_t bswap_s24(int24_t src) +{ + return (int24_t) { .v1 = src.v3, .v2 = src.v2, .v3 = src.v1 }; } -#define MAX_NS 64 +#define F32_TO_F32S(v) \ + bswap_32((union { uint32_t i; float f; }){ .f = (v) }.i) +#define F32S_TO_F32(v) \ + ((union { uint32_t i; float f; }){ .i = bswap_32(v) }.f) + +#define F64_TO_F64S(v) \ + bswap_32((union { uint64_t i; double d; }){ .d = (v) }.i) +#define F64S_TO_F64(v) \ + ((union { uint64_t i; double d; }){ .i = bswap_32(v) }.d) + +#define NS_MAX 8 +#define NS_MASK (NS_MAX-1) + +struct shaper { + float e[NS_MAX * 2]; + uint32_t idx; + float r; +}; struct convert { + uint32_t noise_bits; +#define DITHER_METHOD_NONE 0 +#define DITHER_METHOD_RECTANGULAR 1 +#define DITHER_METHOD_TRIANGULAR 2 +#define DITHER_METHOD_TRIANGULAR_HF 3 +#define DITHER_METHOD_WANNAMAKER_3 4 +#define DITHER_METHOD_LIPSHITZ 5 + uint32_t method; + uint32_t src_fmt; uint32_t dst_fmt; uint32_t n_channels; + uint32_t rate; uint32_t cpu_flags; + const char *func_name; unsigned int is_passthrough:1; - float ns_data[MAX_NS]; - uint32_t ns_idx; - uint32_t ns_size; + + float scale; + uint32_t random[16 + FMT_OPS_MAX_ALIGN/4]; + int32_t prev[16 + FMT_OPS_MAX_ALIGN/4]; +#define NOISE_METHOD_NONE 0 +#define NOISE_METHOD_RECTANGULAR 1 +#define NOISE_METHOD_TRIANGULAR 2 +#define NOISE_METHOD_TRIANGULAR_HF 3 +#define NOISE_METHOD_PATTERN 4 + uint32_t noise_method; + float *noise; + uint32_t noise_size; + const float *ns; + uint32_t n_ns; + struct shaper shaper[64]; void (*process) (struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples); @@ -193,6 +247,35 @@ struct convert { int convert_init(struct convert *conv); +static const struct dither_method_info { + uint32_t method; + const char *label; + const char *description; +} dither_method_info[] = { + [DITHER_METHOD_NONE] = { DITHER_METHOD_NONE, + "none", "Disabled", }, + [DITHER_METHOD_RECTANGULAR] = { DITHER_METHOD_RECTANGULAR, + "rectangular", "Rectangular dithering", }, + [DITHER_METHOD_TRIANGULAR] = { DITHER_METHOD_TRIANGULAR, + "triangular", "Triangular dithering", }, + [DITHER_METHOD_TRIANGULAR_HF] = { DITHER_METHOD_TRIANGULAR_HF, + "triangular-hf", "Sloped Triangular dithering", }, + [DITHER_METHOD_WANNAMAKER_3] = { DITHER_METHOD_WANNAMAKER_3, + "wannamaker3", "Wannamaker 3 dithering", }, + [DITHER_METHOD_LIPSHITZ] = { DITHER_METHOD_LIPSHITZ, + "shaped5", "Lipshitz 5 dithering", }, +}; + +static inline uint32_t dither_method_from_label(const char *label) +{ + uint32_t i; + for (i = 0; i < SPA_N_ELEMENTS(dither_method_info); i++) { + if (spa_streq(dither_method_info[i].label, label)) + return dither_method_info[i].method; + } + return DITHER_METHOD_NONE; +} + #define convert_process(conv,...) (conv)->process(conv, __VA_ARGS__) #define convert_free(conv) (conv)->free(conv) @@ -200,8 +283,6 @@ int convert_init(struct convert *conv); void conv_##name##_##arch(struct convert *conv, void * SPA_RESTRICT dst[], \ const void * SPA_RESTRICT src[], uint32_t n_samples) \ -#define FMT_OPS_MAX_ALIGN 32 - DEFINE_FUNCTION(copy8d, c); DEFINE_FUNCTION(copy8, c); DEFINE_FUNCTION(copy16d, c); @@ -256,62 +337,85 @@ DEFINE_FUNCTION(f64_to_f32d, c); DEFINE_FUNCTION(f64s_to_f32d, c); DEFINE_FUNCTION(f64d_to_f32, c); DEFINE_FUNCTION(f32d_to_u8d, c); +DEFINE_FUNCTION(f32d_to_u8d_noise, c); +DEFINE_FUNCTION(f32d_to_u8d_shaped, c); DEFINE_FUNCTION(f32_to_u8, c); DEFINE_FUNCTION(f32_to_u8d, c); DEFINE_FUNCTION(f32d_to_u8, c); +DEFINE_FUNCTION(f32d_to_u8_noise, c); +DEFINE_FUNCTION(f32d_to_u8_shaped, c); DEFINE_FUNCTION(f32d_to_s8d, c); +DEFINE_FUNCTION(f32d_to_s8d_noise, c); +DEFINE_FUNCTION(f32d_to_s8d_shaped, c); DEFINE_FUNCTION(f32_to_s8, c); DEFINE_FUNCTION(f32_to_s8d, c); DEFINE_FUNCTION(f32d_to_s8, c); +DEFINE_FUNCTION(f32d_to_s8_noise, c); +DEFINE_FUNCTION(f32d_to_s8_shaped, c); DEFINE_FUNCTION(f32d_to_alaw, c); DEFINE_FUNCTION(f32d_to_ulaw, c); DEFINE_FUNCTION(f32_to_u16, c); DEFINE_FUNCTION(f32d_to_u16, c); DEFINE_FUNCTION(f32d_to_s16d, c); +DEFINE_FUNCTION(f32d_to_s16d_noise, c); +DEFINE_FUNCTION(f32d_to_s16d_shaped, c); DEFINE_FUNCTION(f32_to_s16, c); DEFINE_FUNCTION(f32_to_s16d, c); DEFINE_FUNCTION(f32d_to_s16, c); +DEFINE_FUNCTION(f32d_to_s16_noise, c); +DEFINE_FUNCTION(f32d_to_s16_shaped, c); DEFINE_FUNCTION(f32d_to_s16s, c); +DEFINE_FUNCTION(f32d_to_s16s_noise, c); +DEFINE_FUNCTION(f32d_to_s16s_shaped, c); DEFINE_FUNCTION(f32_to_u32, c); DEFINE_FUNCTION(f32d_to_u32, c); DEFINE_FUNCTION(f32d_to_s32d, c); +DEFINE_FUNCTION(f32d_to_s32d_noise, c); DEFINE_FUNCTION(f32_to_s32, c); DEFINE_FUNCTION(f32_to_s32d, c); DEFINE_FUNCTION(f32d_to_s32, c); +DEFINE_FUNCTION(f32d_to_s32_noise, c); DEFINE_FUNCTION(f32d_to_s32s, c); +DEFINE_FUNCTION(f32d_to_s32s_noise, c); DEFINE_FUNCTION(f32_to_u24, c); DEFINE_FUNCTION(f32d_to_u24, c); DEFINE_FUNCTION(f32d_to_s24d, c); +DEFINE_FUNCTION(f32d_to_s24d_noise, c); DEFINE_FUNCTION(f32_to_s24, c); DEFINE_FUNCTION(f32_to_s24d, c); DEFINE_FUNCTION(f32d_to_s24, c); +DEFINE_FUNCTION(f32d_to_s24_noise, c); DEFINE_FUNCTION(f32d_to_s24s, c); +DEFINE_FUNCTION(f32d_to_s24s_noise, c); DEFINE_FUNCTION(f32_to_u24_32, c); DEFINE_FUNCTION(f32d_to_u24_32, c); DEFINE_FUNCTION(f32d_to_s24_32d, c); +DEFINE_FUNCTION(f32d_to_s24_32d_noise, c); DEFINE_FUNCTION(f32_to_s24_32, c); DEFINE_FUNCTION(f32_to_s24_32d, c); DEFINE_FUNCTION(f32d_to_s24_32, c); +DEFINE_FUNCTION(f32d_to_s24_32_noise, c); DEFINE_FUNCTION(f32d_to_s24_32s, c); +DEFINE_FUNCTION(f32d_to_s24_32s_noise, c); DEFINE_FUNCTION(f32d_to_f64d, c); DEFINE_FUNCTION(f32_to_f64, c); DEFINE_FUNCTION(f32_to_f64d, c); DEFINE_FUNCTION(f32d_to_f64, c); DEFINE_FUNCTION(f32d_to_f64s, c); -DEFINE_FUNCTION(deinterleave_8, c); -DEFINE_FUNCTION(deinterleave_16, c); -DEFINE_FUNCTION(deinterleave_24, c); -DEFINE_FUNCTION(deinterleave_32, c); -DEFINE_FUNCTION(deinterleave_32s, c); -DEFINE_FUNCTION(deinterleave_64, c); -DEFINE_FUNCTION(deinterleave_64s, c); -DEFINE_FUNCTION(interleave_8, c); -DEFINE_FUNCTION(interleave_16, c); -DEFINE_FUNCTION(interleave_24, c); -DEFINE_FUNCTION(interleave_32, c); -DEFINE_FUNCTION(interleave_32s, c); -DEFINE_FUNCTION(interleave_64, c); -DEFINE_FUNCTION(interleave_64s, c); +DEFINE_FUNCTION(8_to_8d, c); +DEFINE_FUNCTION(16_to_16d, c); +DEFINE_FUNCTION(24_to_24d, c); +DEFINE_FUNCTION(32_to_32d, c); +DEFINE_FUNCTION(32s_to_32d, c); +DEFINE_FUNCTION(64_to_64d, c); +DEFINE_FUNCTION(64s_to_64sd, c); +DEFINE_FUNCTION(8d_to_8, c); +DEFINE_FUNCTION(16d_to_16, c); +DEFINE_FUNCTION(24d_to_24, c); +DEFINE_FUNCTION(32d_to_32, c); +DEFINE_FUNCTION(32d_to_32s, c); +DEFINE_FUNCTION(64d_to_64, c); +DEFINE_FUNCTION(64sd_to_64s, c); #if defined(HAVE_NEON) DEFINE_FUNCTION(s16_to_f32d_2, neon); @@ -324,14 +428,17 @@ DEFINE_FUNCTION(s16_to_f32d, sse2); DEFINE_FUNCTION(s24_to_f32d, sse2); DEFINE_FUNCTION(s32_to_f32d, sse2); DEFINE_FUNCTION(f32d_to_s32, sse2); +DEFINE_FUNCTION(f32d_to_s32_noise, sse2); DEFINE_FUNCTION(f32_to_s16, sse2); DEFINE_FUNCTION(f32d_to_s16_2, sse2); DEFINE_FUNCTION(f32d_to_s16, sse2); +DEFINE_FUNCTION(f32d_to_s16_noise, sse2); DEFINE_FUNCTION(f32d_to_s16d, sse2); -DEFINE_FUNCTION(deinterleave_32, sse2); -DEFINE_FUNCTION(deinterleave_32s, sse2); -DEFINE_FUNCTION(interleave_32, sse2); -DEFINE_FUNCTION(interleave_32s, sse2); +DEFINE_FUNCTION(f32d_to_s16d_noise, sse2); +DEFINE_FUNCTION(32_to_32d, sse2); +DEFINE_FUNCTION(32s_to_32d, sse2); +DEFINE_FUNCTION(32d_to_32, sse2); +DEFINE_FUNCTION(32d_to_32s, sse2); #endif #if defined(HAVE_SSSE3) DEFINE_FUNCTION(s24_to_f32d, ssse3); diff --git a/spa/plugins/audioconvert/fmtconvert.c b/spa/plugins/audioconvert/fmtconvert.c deleted file mode 100644 index d1d52ad48d5f9d61a50980514b6c9f0de174097f..0000000000000000000000000000000000000000 --- a/spa/plugins/audioconvert/fmtconvert.c +++ /dev/null @@ -1,1161 +0,0 @@ -/* Spa - * - * Copyright © 2018 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include <errno.h> -#include <string.h> -#include <stdio.h> -#include <limits.h> - -#include <spa/support/plugin.h> -#include <spa/support/log.h> -#include <spa/support/cpu.h> -#include <spa/utils/list.h> -#include <spa/utils/names.h> -#include <spa/utils/string.h> -#include <spa/node/node.h> -#include <spa/node/io.h> -#include <spa/node/utils.h> -#include <spa/param/audio/format-utils.h> -#include <spa/param/latency-utils.h> -#include <spa/param/param.h> -#include <spa/pod/filter.h> -#include <spa/debug/types.h> -#include <spa/debug/format.h> - -#include "fmt-ops.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.fmtconvert"); - -#define DEFAULT_RATE 48000 -#define DEFAULT_CHANNELS 2 - -#define MAX_BUFFERS 32 -#define MAX_ALIGN FMT_OPS_MAX_ALIGN -#define MAX_DATAS SPA_AUDIO_MAX_CHANNELS - -#define PROP_DEFAULT_TRUNCATE false -#define PROP_DEFAULT_DITHER 0 - -struct impl; - -struct props { - bool truncate; - uint32_t dither; -}; - -static void props_reset(struct props *props) -{ - props->truncate = PROP_DEFAULT_TRUNCATE; - props->dither = PROP_DEFAULT_DITHER; -} - -struct buffer { - uint32_t id; -#define BUFFER_FLAG_OUT (1 << 0) - uint32_t flags; - struct spa_list link; - struct spa_buffer *outbuf; - struct spa_meta_header *h; - void *datas[MAX_DATAS]; -}; - -struct port { - uint32_t direction; - uint32_t id; - - struct spa_io_buffers *io; - - uint64_t info_all; - struct spa_port_info info; -#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 params[N_PORT_PARAMS]; - - struct spa_audio_info format; - uint32_t stride; - uint32_t blocks; - uint32_t size; - unsigned int have_format:1; - - struct buffer buffers[MAX_BUFFERS]; - uint32_t n_buffers; - - struct spa_list queue; -}; - -struct impl { - struct spa_handle handle; - struct spa_node node; - - struct spa_log *log; - struct spa_cpu *cpu; - uint32_t cpu_flags; - uint32_t max_align; - uint32_t quantum_limit; - - struct spa_io_position *io_position; - - uint64_t info_all; - struct spa_node_info info; - struct props props; -#define N_NODE_PARAMS 0 - struct spa_param_info params[1]; - - struct spa_hook_list hooks; - - struct port ports[2][1]; - - uint32_t src_remap[SPA_AUDIO_MAX_CHANNELS]; - uint32_t dst_remap[SPA_AUDIO_MAX_CHANNELS]; - - struct spa_latency_info latency[2]; - - struct convert conv; - unsigned int started:1; - unsigned int is_passthrough:1; -}; - -#define CHECK_PORT(this,d,id) (id == 0) -#define GET_PORT(this,d,id) (&this->ports[d][id]) -#define GET_IN_PORT(this,id) GET_PORT(this,SPA_DIRECTION_INPUT,id) -#define GET_OUT_PORT(this,id) GET_PORT(this,SPA_DIRECTION_OUTPUT,id) - -static int can_convert(const struct spa_audio_info *info1, const struct spa_audio_info *info2) -{ - if (info1->info.raw.channels != info2->info.raw.channels || - info1->info.raw.rate != info2->info.raw.rate) { - return 0; - } - return 1; -} - -static int setup_convert(struct impl *this) -{ - uint32_t src_fmt, dst_fmt; - struct spa_audio_info informat, outformat; - struct port *inport, *outport; - uint32_t i, j; - int res; - - inport = GET_IN_PORT(this, 0); - outport = GET_OUT_PORT(this, 0); - - if (!inport->have_format || !outport->have_format) - return -EIO; - - informat = inport->format; - outformat = outport->format; - - src_fmt = informat.info.raw.format; - dst_fmt = outformat.info.raw.format; - - spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d", this, - spa_debug_type_find_name(spa_type_audio_format, src_fmt), - informat.info.raw.channels, - informat.info.raw.rate, - spa_debug_type_find_name(spa_type_audio_format, dst_fmt), - outformat.info.raw.channels, - outformat.info.raw.rate); - - if (!can_convert(&informat, &outformat)) - return -EINVAL; - - for (i = 0; i < informat.info.raw.channels; i++) { - for (j = 0; j < outformat.info.raw.channels; j++) { - if (informat.info.raw.position[i] != - outformat.info.raw.position[j]) - continue; - if (inport->blocks > 1) { - this->src_remap[j] = i; - if (outport->blocks > 1) - this->dst_remap[j] = j; - else - this->dst_remap[j] = 0; - } else { - this->src_remap[j] = 0; - if (outport->blocks > 1) - this->dst_remap[i] = j; - else - this->dst_remap[j] = 0; - } - spa_log_debug(this->log, "%p: channel %d -> %d (%s -> %s)", this, - i, j, - spa_debug_type_find_short_name(spa_type_audio_channel, - informat.info.raw.position[i]), - spa_debug_type_find_short_name(spa_type_audio_channel, - outformat.info.raw.position[j])); - outformat.info.raw.position[j] = -1; - break; - } - } - this->conv.src_fmt = src_fmt; - this->conv.dst_fmt = dst_fmt; - this->conv.n_channels = outformat.info.raw.channels; - this->conv.cpu_flags = this->cpu_flags; - - if ((res = convert_init(&this->conv)) < 0) - return res; - - this->is_passthrough = this->conv.is_passthrough; - - spa_log_debug(this->log, "%p: got converter features %08x:%08x passthrough:%d", this, - this->cpu_flags, this->conv.cpu_flags, this->is_passthrough); - - 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) -{ - return -ENOTSUP; -} - -static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - return -ENOTSUP; -} - -static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_log_debug(this->log, "%p: io %d %p/%zd", this, id, data, size); - - switch (id) { - case SPA_IO_Position: - this->io_position = data; - break; - default: - return -ENOENT; - } - return 0; -} - -static int impl_node_send_command(void *object, const struct spa_command *command) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(command != NULL, -EINVAL); - - switch (SPA_NODE_COMMAND_ID(command)) { - case SPA_NODE_COMMAND_Start: - this->started = true; - break; - case SPA_NODE_COMMAND_Suspend: - case SPA_NODE_COMMAND_Flush: - case SPA_NODE_COMMAND_Pause: - this->started = false; - break; - default: - return -ENOTSUP; - } - return 0; -} - -static void emit_info(struct impl *this, bool full) -{ - uint64_t old = full ? this->info.change_mask : 0; - if (full) - this->info.change_mask = this->info_all; - if (this->info.change_mask) { - spa_node_emit_info(&this->hooks, &this->info); - this->info.change_mask = old; - } -} - -static void emit_port_info(struct impl *this, struct port *port, bool full) -{ - uint64_t old = full ? port->info.change_mask : 0; - if (full) - port->info.change_mask = port->info_all; - if (port->info.change_mask) { - spa_node_emit_port_info(&this->hooks, - port->direction, port->id, &port->info); - port->info.change_mask = old; - } -} - -static int -impl_node_add_listener(void *object, - struct spa_hook *listener, - const struct spa_node_events *events, - void *data) -{ - struct impl *this = object; - struct spa_hook_list save; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_hook_list_isolate(&this->hooks, &save, listener, events, data); - - emit_info(this, true); - emit_port_info(this, GET_IN_PORT(this, 0), true); - emit_port_info(this, GET_OUT_PORT(this, 0), true); - - spa_hook_list_join(&this->hooks, &save); - - return 0; -} - -static int -impl_node_set_callbacks(void *object, - const struct spa_node_callbacks *callbacks, - void *user_data) -{ - return 0; -} - -static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, - const struct spa_dict *props) -{ - return -ENOTSUP; -} - -static int -impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) -{ - return -ENOTSUP; -} - -static int int32_cmp(const void *v1, const void *v2) -{ - int32_t a1 = *(int32_t*)v1; - int32_t a2 = *(int32_t*)v2; - if (a1 == 0 && a2 != 0) - return 1; - if (a2 == 0 && a1 != 0) - return -1; - return a1 - a2; -} - -static int port_enum_formats(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t index, - struct spa_pod **param, - struct spa_pod_builder *builder) -{ - struct impl *this = object; - struct port *port, *other; - - port = GET_PORT(this, direction, port_id); - other = GET_PORT(this, SPA_DIRECTION_REVERSE(direction), 0); - - spa_log_debug(this->log, "%p: enum %p %d %d", this, other, port->have_format, other->have_format); - switch (index) { - case 0: - if (port->have_format) { - *param = spa_format_audio_raw_build(builder, - SPA_PARAM_EnumFormat, &port->format.info.raw); - } - else { - struct spa_pod_frame f; - struct spa_audio_info info; - - spa_pod_builder_push_object(builder, &f, - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); - - spa_pod_builder_add(builder, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - 0); - - if (other->have_format) - info = other->format; - else - info.info.raw.format = SPA_AUDIO_FORMAT_F32P; - - if (!other->have_format || - info.info.raw.format == SPA_AUDIO_FORMAT_F32P || - info.info.raw.format == SPA_AUDIO_FORMAT_F32) { - spa_pod_builder_add(builder, - SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(29, - info.info.raw.format, - SPA_AUDIO_FORMAT_F32P, - SPA_AUDIO_FORMAT_F32, - SPA_AUDIO_FORMAT_F32_OE, - SPA_AUDIO_FORMAT_F64P, - SPA_AUDIO_FORMAT_F64, - SPA_AUDIO_FORMAT_F64_OE, - SPA_AUDIO_FORMAT_S32P, - SPA_AUDIO_FORMAT_S32, - SPA_AUDIO_FORMAT_S32_OE, - SPA_AUDIO_FORMAT_U32, - SPA_AUDIO_FORMAT_S24_32P, - SPA_AUDIO_FORMAT_S24_32, - SPA_AUDIO_FORMAT_S24_32_OE, - SPA_AUDIO_FORMAT_U24_32, - SPA_AUDIO_FORMAT_S24P, - SPA_AUDIO_FORMAT_S24, - SPA_AUDIO_FORMAT_S24_OE, - SPA_AUDIO_FORMAT_U24, - SPA_AUDIO_FORMAT_S16P, - SPA_AUDIO_FORMAT_S16, - SPA_AUDIO_FORMAT_S16_OE, - SPA_AUDIO_FORMAT_U16, - SPA_AUDIO_FORMAT_S8P, - SPA_AUDIO_FORMAT_S8, - SPA_AUDIO_FORMAT_U8P, - SPA_AUDIO_FORMAT_U8, - SPA_AUDIO_FORMAT_ULAW, - SPA_AUDIO_FORMAT_ALAW), - 0); - } else { - spa_pod_builder_add(builder, - SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(5, - info.info.raw.format, - info.info.raw.format, - SPA_AUDIO_FORMAT_F32, - SPA_AUDIO_FORMAT_F32P, - SPA_AUDIO_FORMAT_F32_OE), - 0); - } - if (other->have_format) { - spa_pod_builder_add(builder, - SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info.info.raw.rate), - SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info.info.raw.channels), - 0); - if (!SPA_FLAG_IS_SET(info.info.raw.flags, SPA_AUDIO_FLAG_UNPOSITIONED)) { - qsort(info.info.raw.position, info.info.raw.channels, - sizeof(uint32_t), int32_cmp); - spa_pod_builder_prop(builder, SPA_FORMAT_AUDIO_position, 0); - spa_pod_builder_array(builder, sizeof(uint32_t), SPA_TYPE_Id, - info.info.raw.channels, info.info.raw.position); - } - } else { - uint32_t rate = this->io_position ? - this->io_position->clock.rate.denom : DEFAULT_RATE; - - spa_pod_builder_add(builder, - SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int( - rate, 1, INT32_MAX), - SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int( - DEFAULT_CHANNELS, 1, INT32_MAX), - 0); - } - *param = spa_pod_builder_pop(builder, &f); - } - break; - default: - return 0; - } - - return 1; -} - -static int -impl_node_port_enum_params(void *object, int seq, - enum spa_direction direction, uint32_t port_id, - uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) -{ - struct impl *this = object; - struct port *port, *other; - struct spa_pod *param; - struct spa_pod_builder b = { 0 }; - uint8_t buffer[4096]; - struct spa_result_node_params result; - uint32_t count = 0; - int res; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(num != 0, -EINVAL); - - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - port = GET_PORT(this, direction, port_id); - other = GET_PORT(this, SPA_DIRECTION_REVERSE(direction), port_id); - - spa_log_debug(this->log, "%p: enum params port %d.%d %d %u", - this, direction, port_id, seq, id); - - result.id = id; - result.next = start; - next: - result.index = result.next++; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - switch (id) { - case SPA_PARAM_EnumFormat: - if ((res = port_enum_formats(this, direction, port_id, - result.index, ¶m, &b)) <= 0) - return res; - break; - - case SPA_PARAM_Format: - if (!port->have_format) - return -EIO; - if (result.index > 0) - return 0; - - param = spa_format_audio_raw_build(&b, id, &port->format.info.raw); - break; - - case SPA_PARAM_Buffers: - { - if (!port->have_format) - return -EIO; - if (result.index > 0) - return 0; - - if (other->n_buffers > 0) { - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamBuffers, id, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->blocks), - SPA_PARAM_BUFFERS_size, SPA_POD_Int(other->size / other->stride * port->stride), - SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->stride)); - - - } else { - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamBuffers, id, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->blocks), - SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( - this->quantum_limit * 2 * port->stride, - 16 * port->stride, - INT32_MAX), - SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->stride)); - } - break; - } - case SPA_PARAM_Meta: - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamMeta, id, - SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), - SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); - break; - default: - return 0; - } - break; - - case SPA_PARAM_IO: - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamIO, id, - SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), - SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); - break; - default: - return 0; - } - break; - - case SPA_PARAM_Latency: - switch (result.index) { - case 0: case 1: - param = spa_latency_build(&b, id, &this->latency[result.index]); - break; - default: - return 0; - } - break; - - default: - return -ENOENT; - } - - if (spa_pod_filter(&b, &result.param, param, filter) < 0) - goto next; - - spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - - if (++count != num) - goto next; - - return 0; -} - -static int calc_width(struct spa_audio_info *info) -{ - switch (info->info.raw.format) { - case SPA_AUDIO_FORMAT_U8P: - case SPA_AUDIO_FORMAT_U8: - case SPA_AUDIO_FORMAT_S8P: - case SPA_AUDIO_FORMAT_S8: - case SPA_AUDIO_FORMAT_ALAW: - case SPA_AUDIO_FORMAT_ULAW: - return 1; - case SPA_AUDIO_FORMAT_S16P: - case SPA_AUDIO_FORMAT_S16: - case SPA_AUDIO_FORMAT_S16_OE: - case SPA_AUDIO_FORMAT_U16: - return 2; - case SPA_AUDIO_FORMAT_S24P: - case SPA_AUDIO_FORMAT_S24: - case SPA_AUDIO_FORMAT_S24_OE: - case SPA_AUDIO_FORMAT_U24: - return 3; - case SPA_AUDIO_FORMAT_F64P: - case SPA_AUDIO_FORMAT_F64: - case SPA_AUDIO_FORMAT_F64_OE: - return 8; - default: - return 4; - } -} - -static int clear_buffers(struct impl *this, struct port *port) -{ - if (port->n_buffers > 0) { - spa_log_debug(this->log, "%p: clear buffers %p", this, port); - port->n_buffers = 0; - spa_list_init(&port->queue); - } - return 0; -} -static int port_set_format(void *object, - enum spa_direction direction, - uint32_t port_id, - uint32_t flags, - const struct spa_pod *format) -{ - struct impl *this = object; - struct port *port, *other; - int res; - - port = GET_PORT(this, direction, port_id); - other = GET_PORT(this, SPA_DIRECTION_REVERSE(direction), port_id); - - if (format == NULL) { - if (port->have_format) { - port->have_format = false; - clear_buffers(this, port); - if (this->conv.process) - convert_free(&this->conv); - } - } else { - struct spa_audio_info info = { 0 }; - - if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) - return res; - - if (info.media_type != SPA_MEDIA_TYPE_audio || - info.media_subtype != SPA_MEDIA_SUBTYPE_raw) - return -EINVAL; - - if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) - return -EINVAL; - - if (other->have_format) { - spa_log_debug(this->log, "%p: channels:%d<>%d rate:%d<>%d format:%d<>%d", this, - info.info.raw.channels, other->format.info.raw.channels, - info.info.raw.rate, other->format.info.raw.rate, - info.info.raw.format, other->format.info.raw.format); - if (!can_convert(&info, &other->format)) - return -ENOTSUP; - } - - port->stride = calc_width(&info); - - if (SPA_AUDIO_FORMAT_IS_PLANAR(info.info.raw.format)) { - port->blocks = info.info.raw.channels; - } else { - port->stride *= info.info.raw.channels; - port->blocks = 1; - } - - port->have_format = true; - port->format = info; - - if (other->have_format && port->have_format) - if ((res = setup_convert(this)) < 0) - return res; - - spa_log_debug(this->log, "%p: set format on port %d:%d res:%d stride:%d", - this, direction, port_id, res, port->stride); - } - if (port->have_format) { - port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); - port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); - } else { - port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - } - port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - emit_port_info(this, port, false); - return 0; -} - -static int -impl_node_port_set_param(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - struct impl *this = object; - struct port *port; - int res; - - spa_return_val_if_fail(object != NULL, -EINVAL); - spa_return_val_if_fail(CHECK_PORT(object, direction, port_id), -EINVAL); - - port = GET_PORT(this, direction, port_id); - - spa_log_debug(this->log, "%p: set param %u on port %d:%d %p", - this, id, direction, port_id, param); - - switch (id) { - case SPA_PARAM_Latency: - { - struct spa_latency_info info; - if (param == NULL) - return 0; - if ((res = spa_latency_parse(param, &info)) < 0) - return res; - if (direction == info.direction) - return -EINVAL; - - this->latency[info.direction] = info; - port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - port->params[PORT_Latency].flags ^= SPA_PARAM_INFO_SERIAL; - emit_port_info(this, port, false); - break; - } - case SPA_PARAM_Format: - res = port_set_format(object, direction, port_id, flags, param); - break; - default: - res = -ENOENT; - } - return res; -} - -static int -impl_node_port_use_buffers(void *object, - enum spa_direction direction, - uint32_t port_id, - uint32_t flags, - struct spa_buffer **buffers, - uint32_t n_buffers) -{ - struct impl *this = object; - struct port *port; - uint32_t i, size = SPA_ID_INVALID, j; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - port = GET_PORT(this, direction, port_id); - - spa_return_val_if_fail(port->have_format, -EIO); - - spa_log_debug(this->log, "%p: use buffers %d on port %d", this, n_buffers, port_id); - - clear_buffers(this, port); - - for (i = 0; i < n_buffers; i++) { - struct buffer *b; - uint32_t n_datas = buffers[i]->n_datas; - struct spa_data *d = buffers[i]->datas; - - b = &port->buffers[i]; - b->id = i; - b->flags = 0; - b->outbuf = buffers[i]; - b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); - - if (n_datas != port->blocks) { - spa_log_error(this->log, "%p: expected %d blocks on buffer %d", this, - port->blocks, i); - return -EINVAL; - } - - for (j = 0; j < n_datas; j++) { - if (size == SPA_ID_INVALID) - size = d[j].maxsize; - else if (size != d[j].maxsize) { - spa_log_error(this->log, "%p: expected size %d on buffer %d", - this, size, i); - return -EINVAL; - } - - if (d[j].data == NULL) { - spa_log_error(this->log, "%p: invalid memory %d on buffer %d", - this, j, i); - return -EINVAL; - } - if (!SPA_IS_ALIGNED(d[j].data, this->max_align)) { - spa_log_warn(this->log, "%p: memory %d on buffer %d not aligned", - this, j, i); - } - b->datas[j] = d[j].data; - if (direction == SPA_DIRECTION_OUTPUT && - !SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_DYNAMIC)) - this->is_passthrough = false; - } - - if (direction == SPA_DIRECTION_OUTPUT) - spa_list_append(&port->queue, &b->link); - else - SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); - } - port->n_buffers = n_buffers; - port->size = size; - - spa_log_debug(this->log, "%p: buffer size %d", this, size); - - return 0; -} - -static int -impl_node_port_set_io(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t id, void *data, size_t size) -{ - struct impl *this = object; - struct port *port; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - port = GET_PORT(this, direction, port_id); - - spa_log_debug(this->log, "%p: port %d:%d update io %d %p", - this, direction, port_id, id, data); - - switch (id) { - case SPA_IO_Buffers: - port->io = data; - break; - default: - return -ENOENT; - } - return 0; -} - -static void recycle_buffer(struct impl *this, struct port *port, uint32_t id) -{ - struct buffer *b = &port->buffers[id]; - - if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { - spa_list_append(&port->queue, &b->link); - SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); - spa_log_trace_fp(this->log, "%p: recycle buffer %d", this, id); - } -} - -static inline struct buffer *dequeue_buffer(struct impl *this, struct port *port) -{ - struct buffer *b; - - if (spa_list_is_empty(&port->queue)) - return NULL; - b = spa_list_first(&port->queue, struct buffer, link); - spa_list_remove(&b->link); - SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); - return b; -} - -static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) -{ - struct impl *this = object; - struct port *port; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); - - port = GET_OUT_PORT(this, port_id); - - recycle_buffer(this, port, buffer_id); - - return 0; -} - -static int impl_node_process(void *object) -{ - struct impl *this = object; - struct port *inport, *outport; - struct spa_io_buffers *inio, *outio; - struct buffer *inbuf, *outbuf; - struct spa_buffer *inb, *outb; - const void **src_datas; - void **dst_datas; - uint32_t i, n_src_datas, n_dst_datas; - uint32_t n_samples, size, maxsize, offs; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - outport = GET_OUT_PORT(this, 0); - inport = GET_IN_PORT(this, 0); - - outio = outport->io; - inio = inport->io; - - spa_log_trace_fp(this->log, "%p: io %p %p", this, inio, outio); - - spa_return_val_if_fail(outio != NULL, -EIO); - spa_return_val_if_fail(inio != NULL, -EIO); - - spa_log_trace_fp(this->log, "%p: status %p %d %d -> %p %d %d", this, - inio, inio->status, inio->buffer_id, - outio, outio->status, outio->buffer_id); - - if (SPA_UNLIKELY(outio->status == SPA_STATUS_HAVE_DATA)) - return inio->status | outio->status; - - if (SPA_LIKELY(outio->buffer_id < outport->n_buffers)) { - recycle_buffer(this, outport, outio->buffer_id); - outio->buffer_id = SPA_ID_INVALID; - } - if (SPA_UNLIKELY(inio->status != SPA_STATUS_HAVE_DATA)) - return outio->status = inio->status; - - if (SPA_UNLIKELY(inio->buffer_id >= inport->n_buffers)) - return inio->status = -EINVAL; - - if (SPA_UNLIKELY((outbuf = dequeue_buffer(this, outport)) == NULL)) - return outio->status = -EPIPE; - - inbuf = &inport->buffers[inio->buffer_id]; - inb = inbuf->outbuf; - - n_src_datas = inb->n_datas; - src_datas = alloca(sizeof(void*) * n_src_datas); - - outb = outbuf->outbuf; - - n_dst_datas = outb->n_datas; - dst_datas = alloca(sizeof(void*) * n_dst_datas); - - size = UINT32_MAX; - for (i = 0; i < n_src_datas; i++) { - uint32_t src_remap = this->src_remap[i]; - struct spa_data *sd = &inb->datas[src_remap]; - offs = SPA_MIN(sd->chunk->offset, sd->maxsize); - size = SPA_MIN(size, SPA_MIN(sd->maxsize - offs, sd->chunk->size)); - src_datas[i] = SPA_PTROFF(sd->data, offs, void); - } - n_samples = size / inport->stride; - - maxsize = outb->datas[0].maxsize; - n_samples = SPA_MIN(n_samples, maxsize / outport->stride); - - spa_log_trace_fp(this->log, "%p: n_src:%d n_dst:%d size:%d maxsize:%d n_samples:%d p:%d", - this, n_src_datas, n_dst_datas, size, maxsize, n_samples, - this->is_passthrough); - - for (i = 0; i < n_dst_datas; i++) { - uint32_t dst_remap = this->dst_remap[i]; - struct spa_data *dd = outb->datas; - - if (this->is_passthrough) - dd[i].data = (void *)src_datas[i]; - else - dst_datas[i] = dd[dst_remap].data = outbuf->datas[dst_remap]; - - dd[i].chunk->offset = 0; - dd[i].chunk->size = n_samples * outport->stride; - } - if (!this->is_passthrough) - convert_process(&this->conv, dst_datas, src_datas, n_samples); - - inio->status = SPA_STATUS_NEED_DATA; - - outio->status = SPA_STATUS_HAVE_DATA; - outio->buffer_id = outbuf->id; - - return SPA_STATUS_NEED_DATA | SPA_STATUS_HAVE_DATA; -} - -static const struct spa_node_methods impl_node = { - SPA_VERSION_NODE_METHODS, - .add_listener = impl_node_add_listener, - .set_callbacks = impl_node_set_callbacks, - .enum_params = impl_node_enum_params, - .set_param = impl_node_set_param, - .set_io = impl_node_set_io, - .send_command = impl_node_send_command, - .add_port = impl_node_add_port, - .remove_port = impl_node_remove_port, - .port_enum_params = impl_node_port_enum_params, - .port_set_param = impl_node_port_set_param, - .port_use_buffers = impl_node_port_use_buffers, - .port_set_io = impl_node_port_set_io, - .port_reuse_buffer = impl_node_port_reuse_buffer, - .process = impl_node_process, -}; - -static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) -{ - struct impl *this; - - spa_return_val_if_fail(handle != NULL, -EINVAL); - spa_return_val_if_fail(interface != NULL, -EINVAL); - - this = (struct impl *) handle; - - if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) - *interface = &this->node; - else - return -ENOENT; - - return 0; -} - -static int impl_clear(struct spa_handle *handle) -{ - return 0; -} - -static int init_port(struct impl *this, enum spa_direction direction, uint32_t port_id) -{ - struct port *port; - - port = GET_PORT(this, direction, port_id); - port->direction = direction; - port->id = port_id; - - spa_list_init(&port->queue); - port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | - SPA_PORT_CHANGE_MASK_PARAMS; - port->info = SPA_PORT_INFO_INIT(); - port->info.flags = SPA_PORT_FLAG_NO_REF | - SPA_PORT_FLAG_DYNAMIC_DATA; - port->params[PORT_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); - port->params[PORT_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); - port->params[PORT_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); - port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - port->params[PORT_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); - port->info.params = port->params; - port->info.n_params = N_PORT_PARAMS; - port->have_format = false; - - return 0; -} - -static size_t -impl_get_size(const struct spa_handle_factory *factory, - const struct spa_dict *params) -{ - return sizeof(struct impl); -} - -static int -impl_init(const struct spa_handle_factory *factory, - struct spa_handle *handle, - const struct spa_dict *info, - const struct spa_support *support, - uint32_t n_support) -{ - struct impl *this; - uint32_t i; - - spa_return_val_if_fail(factory != NULL, -EINVAL); - spa_return_val_if_fail(handle != NULL, -EINVAL); - - handle->get_interface = impl_get_interface; - handle->clear = impl_clear; - - 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 (this->cpu) { - this->cpu_flags = spa_cpu_get_flags(this->cpu); - this->max_align = SPA_MIN(MAX_ALIGN, spa_cpu_get_max_align(this->cpu)); - } - - for (i = 0; info && i < info->n_items; i++) { - const char *k = info->items[i].key; - const char *s = info->items[i].value; - if (spa_streq(k, "clock.quantum-limit")) - spa_atou32(s, &this->quantum_limit, 0); - } - - this->node.iface = SPA_INTERFACE_INIT( - SPA_TYPE_INTERFACE_Node, - SPA_VERSION_NODE, - &impl_node, this); - spa_hook_list_init(&this->hooks); - - this->info_all = SPA_PORT_CHANGE_MASK_FLAGS; - this->info = SPA_NODE_INFO_INIT(); - this->info.flags = SPA_NODE_FLAG_RT; - this->info.params = this->params; - this->info.n_params = N_NODE_PARAMS; - props_reset(&this->props); - - this->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); - this->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); - - init_port(this, SPA_DIRECTION_OUTPUT, 0); - init_port(this, SPA_DIRECTION_INPUT, 0); - - return 0; -} - -static const struct spa_interface_info impl_interfaces[] = { - {SPA_TYPE_INTERFACE_Node,}, -}; - -static int -impl_enum_interface_info(const struct spa_handle_factory *factory, - const struct spa_interface_info **info, - uint32_t *index) -{ - spa_return_val_if_fail(factory != NULL, -EINVAL); - spa_return_val_if_fail(info != NULL, -EINVAL); - spa_return_val_if_fail(index != NULL, -EINVAL); - - switch (*index) { - case 0: - *info = &impl_interfaces[*index]; - break; - default: - return 0; - } - (*index)++; - return 1; -} - -const struct spa_handle_factory spa_fmtconvert_factory = { - SPA_VERSION_HANDLE_FACTORY, - SPA_NAME_AUDIO_PROCESS_FORMAT, - NULL, - impl_get_size, - impl_init, - impl_enum_interface_info, -}; diff --git a/spa/plugins/audioconvert/merger.c b/spa/plugins/audioconvert/merger.c deleted file mode 100644 index 3310855865aa2e439a3b667665669888322c5b39..0000000000000000000000000000000000000000 --- a/spa/plugins/audioconvert/merger.c +++ /dev/null @@ -1,1550 +0,0 @@ -/* Spa - * - * Copyright © 2018 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include <errno.h> -#include <string.h> -#include <stdio.h> -#include <limits.h> - -#include <spa/support/plugin.h> -#include <spa/support/cpu.h> -#include <spa/support/log.h> -#include <spa/utils/result.h> -#include <spa/utils/list.h> -#include <spa/utils/names.h> -#include <spa/utils/string.h> -#include <spa/node/node.h> -#include <spa/node/io.h> -#include <spa/node/utils.h> -#include <spa/node/keys.h> -#include <spa/param/audio/format-utils.h> -#include <spa/param/param.h> -#include <spa/param/latency-utils.h> -#include <spa/pod/filter.h> -#include <spa/debug/types.h> -#include <spa/debug/pod.h> - -#include "volume-ops.h" -#include "fmt-ops.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.merger"); - -#define DEFAULT_RATE 48000 -#define DEFAULT_CHANNELS 2 - -#define MAX_ALIGN FMT_OPS_MAX_ALIGN -#define MAX_BUFFERS 32 -#define MAX_DATAS SPA_AUDIO_MAX_CHANNELS -#define MAX_PORTS SPA_AUDIO_MAX_CHANNELS - -#define DEFAULT_MUTE false -#define DEFAULT_VOLUME VOLUME_NORM - -struct volumes { - bool mute; - uint32_t n_volumes; - float volumes[SPA_AUDIO_MAX_CHANNELS]; -}; - -static void init_volumes(struct volumes *vol) -{ - uint32_t i; - vol->mute = DEFAULT_MUTE; - vol->n_volumes = 0; - for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) - vol->volumes[i] = DEFAULT_VOLUME; -} - -struct props { - float volume; - uint32_t n_channels; - uint32_t channel_map[SPA_AUDIO_MAX_CHANNELS]; - struct volumes channel; - struct volumes soft; - struct volumes monitor; -}; - -static void props_reset(struct props *props) -{ - uint32_t i; - props->volume = DEFAULT_VOLUME; - props->n_channels = 0; - for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) - props->channel_map[i] = SPA_AUDIO_CHANNEL_UNKNOWN; - init_volumes(&props->channel); - init_volumes(&props->soft); - init_volumes(&props->monitor); -} - -struct buffer { - uint32_t id; -#define BUFFER_FLAG_QUEUED (1<<0) - uint32_t flags; - struct spa_list link; - struct spa_buffer *buf; - void *datas[MAX_DATAS]; -}; - -struct port { - uint32_t direction; - uint32_t id; - - struct spa_io_buffers *io; - - uint64_t info_all; - struct spa_port_info info; -#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 N_PORT_PARAMS 6 - struct spa_param_info params[N_PORT_PARAMS]; - char position[16]; - - struct spa_audio_info format; - uint32_t blocks; - uint32_t stride; - - struct buffer buffers[MAX_BUFFERS]; - uint32_t n_buffers; - - struct spa_list queue; - - unsigned int have_format:1; -}; - -struct impl { - struct spa_handle handle; - struct spa_node node; - - struct spa_log *log; - struct spa_cpu *cpu; - - uint32_t cpu_flags; - uint32_t max_align; - uint32_t quantum_limit; - - struct spa_io_position *io_position; - - uint64_t info_all; - struct spa_node_info info; -#define IDX_PortConfig 0 -#define IDX_PropInfo 1 -#define IDX_Props 2 -#define N_NODE_PARAMS 3 - struct spa_param_info params[N_NODE_PARAMS]; - - struct spa_hook_list hooks; - - uint32_t port_count; - uint32_t monitor_count; - struct port *in_ports[MAX_PORTS]; - struct port *out_ports[MAX_PORTS + 1]; - - struct spa_audio_info format; - unsigned int have_profile:1; - - struct convert conv; - unsigned int is_passthrough:1; - unsigned int started:1; - unsigned int monitor:1; - unsigned int monitor_channel_volumes:1; - - struct volume volume; - struct props props; - - uint32_t src_remap[SPA_AUDIO_MAX_CHANNELS]; - uint32_t dst_remap[SPA_AUDIO_MAX_CHANNELS]; - - struct spa_latency_info latency[2]; - - uint32_t empty_size; - float *empty; -}; - -#define CHECK_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < this->port_count) -#define CHECK_OUT_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) <= this->monitor_count) -#define CHECK_PORT(this,d,p) (CHECK_OUT_PORT(this,d,p) || CHECK_IN_PORT (this,d,p)) -#define GET_IN_PORT(this,p) (this->in_ports[p]) -#define GET_OUT_PORT(this,p) (this->out_ports[p]) -#define GET_PORT(this,d,p) (d == SPA_DIRECTION_INPUT ? GET_IN_PORT(this,p) : GET_OUT_PORT(this,p)) - -#define PORT_IS_DSP(d,p) (p != 0 || d != SPA_DIRECTION_OUTPUT) - -static void emit_node_info(struct impl *this, bool full) -{ - uint64_t old = full ? this->info.change_mask : 0; - if (full) - this->info.change_mask = this->info_all; - if (this->info.change_mask) { - spa_node_emit_info(&this->hooks, &this->info); - this->info.change_mask = old; - } -} - -static void emit_port_info(struct impl *this, struct port *port, bool full) -{ - uint64_t old = full ? port->info.change_mask : 0; - if (full) - port->info.change_mask = port->info_all; - if (port->info.change_mask) { - struct spa_dict_item items[3]; - uint32_t n_items = 0; - - if (PORT_IS_DSP(port->direction, port->id)) { - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit float mono audio"); - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_AUDIO_CHANNEL, port->position); - if (port->direction == SPA_DIRECTION_OUTPUT) - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_MONITOR, "true"); - } - port->info.props = &SPA_DICT_INIT(items, n_items); - - spa_node_emit_port_info(&this->hooks, port->direction, port->id, &port->info); - port->info.change_mask = old; - } -} - -static int init_port(struct impl *this, enum spa_direction direction, uint32_t port_id, - uint32_t position) -{ - struct port *port = GET_PORT(this, direction, port_id); - const char *name; - - if (port == NULL) { - port = calloc(1, sizeof(struct port)); - if (port == NULL) - return -errno; - if (direction == SPA_DIRECTION_INPUT) - this->in_ports[port_id] = port; - else - this->out_ports[port_id] = port; - } - port->direction = direction; - port->id = port_id; - - name = spa_debug_type_find_short_name(spa_type_audio_channel, position); - snprintf(port->position, sizeof(port->position), "%s", name ? name : "UNK"); - - port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | - SPA_PORT_CHANGE_MASK_PROPS | - SPA_PORT_CHANGE_MASK_PARAMS; - port->info = SPA_PORT_INFO_INIT(); - port->info.flags = SPA_PORT_FLAG_NO_REF | - SPA_PORT_FLAG_DYNAMIC_DATA; - port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); - port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); - port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); - port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - port->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); - port->info.params = port->params; - port->info.n_params = N_PORT_PARAMS; - - port->n_buffers = 0; - port->have_format = false; - port->format.media_type = SPA_MEDIA_TYPE_audio; - port->format.media_subtype = SPA_MEDIA_SUBTYPE_dsp; - port->format.info.dsp.format = SPA_AUDIO_FORMAT_DSP_F32; - spa_list_init(&port->queue); - - spa_log_debug(this->log, "%p: add port %d:%d position:%s", - this, direction, port_id, port->position); - emit_port_info(this, port, true); - - 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[4096]; - struct spa_result_node_params result; - uint32_t count = 0; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(num != 0, -EINVAL); - - result.id = id; - result.next = start; - next: - result.index = result.next++; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - switch (id) { - case SPA_PARAM_PortConfig: - return -ENOTSUP; - - case SPA_PARAM_PropInfo: - { - switch (result.index) { - default: - return 0; - } - break; - } - - case SPA_PARAM_Props: - { - switch (result.index) { - default: - return 0; - } - break; - } - default: - return 0; - } - - if (spa_pod_filter(&b, &result.param, param, filter) < 0) - goto next; - - spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - - if (++count != num) - goto next; - - return 0; -} - -static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_log_debug(this->log, "%p: io %d %p/%zd", this, id, data, size); - - switch (id) { - case SPA_IO_Position: - this->io_position = data; - break; - default: - return -ENOENT; - } - return 0; -} - -static int merger_set_param(struct impl *this, const char *k, const char *s) -{ - if (spa_streq(k, "monitor.channel-volumes")) - this->monitor_channel_volumes = spa_atob(s); - return 0; -} - -static int parse_prop_params(struct impl *this, struct spa_pod *params) -{ - struct spa_pod_parser prs; - struct spa_pod_frame f; - - spa_pod_parser_pod(&prs, params); - if (spa_pod_parser_push_struct(&prs, &f) < 0) - return 0; - - while (true) { - const char *name; - struct spa_pod *pod; - char value[512]; - - if (spa_pod_parser_get_string(&prs, &name) < 0) - break; - - if (spa_pod_parser_get_pod(&prs, &pod) < 0) - break; - - if (spa_pod_is_bool(pod)) { - snprintf(value, sizeof(value), "%s", - SPA_POD_VALUE(struct spa_pod_bool, pod) ? - "true" : "false"); - } else - continue; - - spa_log_info(this->log, "key:'%s' val:'%s'", name, value); - merger_set_param(this, name, value); - } - return 0; -} - -static int apply_props(struct impl *this, const struct spa_pod *param) -{ - struct spa_pod_prop *prop; - struct spa_pod_object *obj = (struct spa_pod_object *) param; - struct props *p = &this->props; - int changed = 0; - uint32_t n; - - SPA_POD_OBJECT_FOREACH(obj, prop) { - switch (prop->key) { - case SPA_PROP_volume: - if (spa_pod_get_float(&prop->value, &p->volume) == 0) - changed++; - break; - case SPA_PROP_mute: - if (spa_pod_get_bool(&prop->value, &p->channel.mute) == 0) - changed++; - break; - case SPA_PROP_channelVolumes: - if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - p->channel.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) { - p->channel.n_volumes = n; - changed++; - } - break; - case SPA_PROP_channelMap: - if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Id, - p->channel_map, SPA_AUDIO_MAX_CHANNELS)) > 0) { - p->n_channels = n; - changed++; - } - break; - case SPA_PROP_softMute: - if (spa_pod_get_bool(&prop->value, &p->soft.mute) == 0) - changed++; - break; - case SPA_PROP_softVolumes: - if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - p->soft.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) { - p->soft.n_volumes = n; - changed++; - } - break; - case SPA_PROP_monitorMute: - if (spa_pod_get_bool(&prop->value, &p->monitor.mute) == 0) - changed++; - break; - case SPA_PROP_monitorVolumes: - if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - p->monitor.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) { - p->monitor.n_volumes = n; - changed++; - } - break; - case SPA_PROP_params: - parse_prop_params(this, &prop->value); - break; - default: - break; - } - } - return changed; -} - -static int int32_cmp(const void *v1, const void *v2) -{ - int32_t a1 = *(int32_t*)v1; - int32_t a2 = *(int32_t*)v2; - if (a1 == 0 && a2 != 0) - return 1; - if (a2 == 0 && a1 != 0) - return -1; - return a1 - a2; -} - -static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - struct impl *this = object; - int res; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - if (param == NULL) - return 0; - - switch (id) { - case SPA_PARAM_PortConfig: - { - struct spa_audio_info info = { 0, }; - struct port *port; - struct spa_pod *format; - enum spa_direction direction; - enum spa_param_port_config_mode mode; - bool monitor = false; - uint32_t i; - - if (spa_pod_parse_object(param, - SPA_TYPE_OBJECT_ParamPortConfig, NULL, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(&direction), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(&mode), - SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_OPT_Bool(&monitor), - SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(&format)) < 0) - return -EINVAL; - - if (!spa_pod_is_object_type(format, SPA_TYPE_OBJECT_Format)) - return -EINVAL; - - if (mode != SPA_PARAM_PORT_CONFIG_MODE_dsp) - return -ENOTSUP; - if (direction != SPA_DIRECTION_INPUT) - return -EINVAL; - - if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) - return res; - - if (info.media_type != SPA_MEDIA_TYPE_audio || - info.media_subtype != SPA_MEDIA_SUBTYPE_raw) - return -EINVAL; - - if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) - return -EINVAL; - - info.info.raw.rate = 0; - - if (this->have_profile && - memcmp(&this->format, &info, sizeof(info)) == 0 && - this->monitor == monitor) - return 0; - - spa_log_debug(this->log, "%p: port config %d/%d %d", this, - info.info.raw.rate, info.info.raw.channels, monitor); - - for (i = 0; i < this->port_count; i++) { - spa_node_emit_port_info(&this->hooks, - SPA_DIRECTION_INPUT, i, NULL); - if (this->monitor) - spa_node_emit_port_info(&this->hooks, - SPA_DIRECTION_OUTPUT, i+1, NULL); - } - - this->monitor = monitor; - this->format = info; - this->have_profile = true; - this->port_count = info.info.raw.channels; - this->monitor_count = this->monitor ? this->port_count : 0; - for (i = 0; i < this->port_count; i++) - this->props.channel_map[i] = info.info.raw.position[i]; - this->props.channel.n_volumes = this->port_count; - this->props.monitor.n_volumes = this->port_count; - this->props.soft.n_volumes = this->port_count; - this->props.n_channels = this->port_count; - - for (i = 0; i < this->port_count; i++) { - init_port(this, SPA_DIRECTION_INPUT, i, info.info.raw.position[i]); - if (this->monitor) - init_port(this, SPA_DIRECTION_OUTPUT, i+1, - info.info.raw.position[i]); - } - - port = GET_OUT_PORT(this, 0); - qsort(info.info.raw.position, info.info.raw.channels, - sizeof(uint32_t), int32_cmp); - port->format = info; - port->have_format = true; - - this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; - this->params[IDX_Props].flags ^= SPA_PARAM_INFO_SERIAL; - emit_node_info(this, false); - return 0; - } - case SPA_PARAM_Props: - if (apply_props(this, param) > 0) { - this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; - this->params[IDX_Props].flags ^= SPA_PARAM_INFO_SERIAL; - emit_node_info(this, false); - } - break; - default: - return -ENOENT; - } - return 0; -} - -static int impl_node_send_command(void *object, const struct spa_command *command) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(command != NULL, -EINVAL); - - switch (SPA_NODE_COMMAND_ID(command)) { - case SPA_NODE_COMMAND_Start: - this->started = true; - break; - case SPA_NODE_COMMAND_Suspend: - case SPA_NODE_COMMAND_Flush: - case SPA_NODE_COMMAND_Pause: - this->started = false; - break; - default: - return -ENOTSUP; - } - return 0; -} - -static int -impl_node_add_listener(void *object, - struct spa_hook *listener, - const struct spa_node_events *events, - void *data) -{ - struct impl *this = object; - uint32_t i; - struct spa_hook_list save; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - 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); - emit_port_info(this, GET_OUT_PORT(this, 0), true); - for (i = 0; i < this->port_count; i++) { - emit_port_info(this, GET_IN_PORT(this, i), true); - if (this->monitor) - emit_port_info(this, GET_OUT_PORT(this, i+1), true); - } - - spa_hook_list_join(&this->hooks, &save); - - return 0; -} - -static int -impl_node_set_callbacks(void *object, - const struct spa_node_callbacks *callbacks, - void *user_data) -{ - return 0; -} - -static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, - const struct spa_dict *props) -{ - return -ENOTSUP; -} - -static int -impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) -{ - return -ENOTSUP; -} - -static int port_enum_formats(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t index, - struct spa_pod **param, - struct spa_pod_builder *builder) -{ - struct impl *this = object; - struct port *port = GET_PORT(this, direction, port_id); - - switch (index) { - case 0: - if (PORT_IS_DSP(direction, port_id)) { - *param = spa_format_audio_dsp_build(builder, - SPA_PARAM_EnumFormat, &port->format.info.dsp); - } else if (port->have_format) { - *param = spa_format_audio_raw_build(builder, - SPA_PARAM_EnumFormat, &port->format.info.raw); - } - else { - uint32_t rate = this->io_position ? - this->io_position->clock.rate.denom : DEFAULT_RATE; - - *param = spa_pod_builder_add_object(builder, - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(25, - SPA_AUDIO_FORMAT_F32P, - SPA_AUDIO_FORMAT_F32P, - SPA_AUDIO_FORMAT_F32, - SPA_AUDIO_FORMAT_F32_OE, - SPA_AUDIO_FORMAT_F64P, - SPA_AUDIO_FORMAT_F64, - SPA_AUDIO_FORMAT_F64_OE, - SPA_AUDIO_FORMAT_S32P, - SPA_AUDIO_FORMAT_S32, - SPA_AUDIO_FORMAT_S32_OE, - SPA_AUDIO_FORMAT_S24_32P, - SPA_AUDIO_FORMAT_S24_32, - SPA_AUDIO_FORMAT_S24_32_OE, - SPA_AUDIO_FORMAT_S24P, - SPA_AUDIO_FORMAT_S24, - SPA_AUDIO_FORMAT_S24_OE, - SPA_AUDIO_FORMAT_S16P, - SPA_AUDIO_FORMAT_S16, - SPA_AUDIO_FORMAT_S16_OE, - SPA_AUDIO_FORMAT_S8P, - SPA_AUDIO_FORMAT_S8, - SPA_AUDIO_FORMAT_U8P, - SPA_AUDIO_FORMAT_U8, - SPA_AUDIO_FORMAT_ULAW, - SPA_AUDIO_FORMAT_ALAW), - SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int( - rate, 1, INT32_MAX), - SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int( - DEFAULT_CHANNELS, 1, MAX_PORTS)); - } - break; - default: - return 0; - } - return 1; -} - -static int -impl_node_port_enum_params(void *object, int seq, - enum spa_direction direction, uint32_t port_id, - uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) -{ - struct impl *this = object; - struct port *port; - struct spa_pod *param; - struct spa_pod_builder b = { 0 }; - uint8_t buffer[2048]; - struct spa_result_node_params result; - uint32_t count = 0; - int res; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(num != 0, -EINVAL); - - spa_log_debug(this->log, "%p: enum params port %d.%d %d %u", - this, direction, port_id, seq, id); - - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - port = GET_PORT(this, direction, port_id); - - result.id = id; - result.next = start; - next: - result.index = result.next++; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - switch (id) { - case SPA_PARAM_EnumFormat: - if ((res = port_enum_formats(object, direction, port_id, result.index, ¶m, &b)) <= 0) - return res; - break; - case SPA_PARAM_Format: - if (!port->have_format) - return -EIO; - if (result.index > 0) - return 0; - - if (PORT_IS_DSP(direction, port_id)) - param = spa_format_audio_dsp_build(&b, id, &port->format.info.dsp); - else - param = spa_format_audio_raw_build(&b, id, &port->format.info.raw); - break; - case SPA_PARAM_Buffers: - if (!port->have_format) - return -EIO; - if (result.index > 0) - return 0; - - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamBuffers, id, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->blocks), - SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( - this->quantum_limit * port->stride, - 16 * port->stride, - INT32_MAX), - SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->stride)); - break; - case SPA_PARAM_Meta: - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamMeta, id, - SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), - SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); - break; - default: - return 0; - } - break; - case SPA_PARAM_IO: - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamIO, id, - SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), - SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); - break; - default: - return 0; - } - break; - case SPA_PARAM_Latency: - switch (result.index) { - case 0: case 1: - param = spa_latency_build(&b, id, &this->latency[result.index]); - break; - default: - return 0; - } - break; - default: - return -ENOENT; - } - - if (spa_pod_filter(&b, &result.param, param, filter) < 0) - goto next; - - spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - - if (++count != num) - goto next; - - return 0; -} - -static int clear_buffers(struct impl *this, struct port *port) -{ - if (port->n_buffers > 0) { - spa_log_debug(this->log, "%p: clear buffers %p", this, port); - port->n_buffers = 0; - spa_list_init(&port->queue); - } - return 0; -} - -static int setup_convert(struct impl *this) -{ - struct port *outport; - struct spa_audio_info informat, outformat; - uint32_t i, j, src_fmt, dst_fmt; - int res; - - outport = GET_OUT_PORT(this, 0); - - informat = this->format; - outformat = outport->format; - - src_fmt = SPA_AUDIO_FORMAT_DSP_F32; - dst_fmt = outformat.info.raw.format; - - spa_log_info(this->log, "%p: %s/%d@%dx%d->%s/%d@%d", this, - spa_debug_type_find_name(spa_type_audio_format, src_fmt), - 1, - informat.info.raw.rate, - informat.info.raw.channels, - spa_debug_type_find_name(spa_type_audio_format, dst_fmt), - outformat.info.raw.channels, - outformat.info.raw.rate); - - for (i = 0; i < informat.info.raw.channels; i++) { - for (j = 0; j < outformat.info.raw.channels; j++) { - if (informat.info.raw.position[i] != - outformat.info.raw.position[j]) - continue; - this->src_remap[j] = i; - this->dst_remap[i] = j; - spa_log_debug(this->log, "%p: channel %d -> %d (%s -> %s)", this, - i, j, - spa_debug_type_find_short_name(spa_type_audio_channel, - informat.info.raw.position[i]), - spa_debug_type_find_short_name(spa_type_audio_channel, - outformat.info.raw.position[j])); - outformat.info.raw.position[j] = -1; - break; - } - } - - this->conv.src_fmt = src_fmt; - this->conv.dst_fmt = dst_fmt; - this->conv.n_channels = outformat.info.raw.channels; - this->conv.cpu_flags = this->cpu_flags; - - if ((res = convert_init(&this->conv)) < 0) - return res; - - this->is_passthrough = this->conv.is_passthrough; - - spa_log_debug(this->log, "%p: got converter features %08x:%08x passthrough:%d", this, - this->cpu_flags, this->conv.cpu_flags, this->is_passthrough); - - return 0; -} - -static int calc_width(struct spa_audio_info *info) -{ - switch (info->info.raw.format) { - case SPA_AUDIO_FORMAT_U8: - case SPA_AUDIO_FORMAT_U8P: - case SPA_AUDIO_FORMAT_S8: - case SPA_AUDIO_FORMAT_S8P: - case SPA_AUDIO_FORMAT_ULAW: - case SPA_AUDIO_FORMAT_ALAW: - return 1; - case SPA_AUDIO_FORMAT_S16P: - case SPA_AUDIO_FORMAT_S16: - case SPA_AUDIO_FORMAT_S16_OE: - return 2; - case SPA_AUDIO_FORMAT_S24P: - case SPA_AUDIO_FORMAT_S24: - case SPA_AUDIO_FORMAT_S24_OE: - return 3; - case SPA_AUDIO_FORMAT_F64P: - case SPA_AUDIO_FORMAT_F64: - case SPA_AUDIO_FORMAT_F64_OE: - return 8; - default: - return 4; - } -} - -static int port_set_latency(void *object, - enum spa_direction direction, - uint32_t port_id, - uint32_t flags, - const struct spa_pod *latency) -{ - struct impl *this = object; - struct port *port; - enum spa_direction other = SPA_DIRECTION_REVERSE(direction); - uint32_t i; - - spa_log_debug(this->log, "%p: set latency direction:%d id:%d", - this, direction, port_id); - - if (direction == SPA_DIRECTION_OUTPUT && port_id != 0) - return 0; - - if (latency == NULL) { - this->latency[other] = SPA_LATENCY_INFO(other); - } else { - struct spa_latency_info info; - if (spa_latency_parse(latency, &info) < 0 || - info.direction != other) - return -EINVAL; - this->latency[other] = info; - } - for (i = 0; i < this->port_count; i++) { - port = GET_IN_PORT(this, i); - port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - port->params[IDX_Latency].flags ^= SPA_PARAM_INFO_SERIAL; - emit_port_info(this, port, false); - } - port = GET_OUT_PORT(this, 0); - port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - port->params[IDX_Latency].flags ^= SPA_PARAM_INFO_SERIAL; - emit_port_info(this, port, false); - return 0; -} - -static int port_set_format(void *object, - enum spa_direction direction, - uint32_t port_id, - uint32_t flags, - const struct spa_pod *format) -{ - struct impl *this = object; - struct port *port; - int res; - - port = GET_PORT(this, direction, port_id); - - spa_log_debug(this->log, "%p: set format", this); - - if (format == NULL) { - if (port->have_format) { - if (PORT_IS_DSP(direction, port_id)) - port->have_format = false; - else - port->have_format = this->have_profile; - port->format.info.raw.rate = 0; - clear_buffers(this, port); - } - } else { - struct spa_audio_info info = { 0 }; - - if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) { - spa_log_error(this->log, "can't parse format %s", spa_strerror(res)); - return res; - } - if (PORT_IS_DSP(direction, port_id)) { - if (info.media_type != SPA_MEDIA_TYPE_audio || - info.media_subtype != SPA_MEDIA_SUBTYPE_dsp) { - spa_log_error(this->log, "unexpected types %d/%d", - info.media_type, info.media_subtype); - return -EINVAL; - } - if ((res = spa_format_audio_dsp_parse(format, &info.info.dsp)) < 0) { - spa_log_error(this->log, "can't parse format %s", spa_strerror(res)); - return res; - } - if (info.info.dsp.format != SPA_AUDIO_FORMAT_DSP_F32) { - spa_log_error(this->log, "unexpected format %d<->%d", - info.info.dsp.format, SPA_AUDIO_FORMAT_DSP_F32); - return -EINVAL; - } - port->blocks = 1; - port->stride = 4; - } - else { - if (info.media_type != SPA_MEDIA_TYPE_audio || - info.media_subtype != SPA_MEDIA_SUBTYPE_raw) { - spa_log_error(this->log, "unexpected types %d/%d", - info.media_type, info.media_subtype); - return -EINVAL; - } - if ((res = spa_format_audio_raw_parse(format, &info.info.raw)) < 0) { - spa_log_error(this->log, "can't parse format %s", spa_strerror(res)); - return res; - } - if (info.info.raw.channels != this->port_count) { - spa_log_error(this->log, "unexpected channels %d<->%d", - info.info.raw.channels, this->port_count); - return -EINVAL; - } - port->stride = calc_width(&info); - if (SPA_AUDIO_FORMAT_IS_PLANAR(info.info.raw.format)) { - port->blocks = info.info.raw.channels; - } - else { - port->stride *= info.info.raw.channels; - port->blocks = 1; - } - } - port->format = info; - - spa_log_debug(this->log, "%p: %d %d %d", this, - port_id, port->stride, port->blocks); - - if (!PORT_IS_DSP(direction, port_id)) - if ((res = setup_convert(this)) < 0) - return res; - - port->have_format = true; - } - - port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - if (port->have_format) { - port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); - port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); - } else { - port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - } - emit_port_info(this, port, false); - - return 0; -} - - -static int -impl_node_port_set_param(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_log_debug(this->log, "%p: set param port %d.%d %u", - this, direction, port_id, id); - - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - switch (id) { - case SPA_PARAM_Latency: - return port_set_latency(this, direction, port_id, flags, param); - case SPA_PARAM_Format: - return port_set_format(this, direction, port_id, flags, param); - default: - return -ENOENT; - } -} - -static void queue_buffer(struct impl *this, struct port *port, uint32_t id) -{ - struct buffer *b = &port->buffers[id]; - - spa_log_trace_fp(this->log, "%p: queue buffer %d on port %d %d", - this, id, port->id, b->flags); - if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_QUEUED)) - return; - - spa_list_append(&port->queue, &b->link); - SPA_FLAG_SET(b->flags, BUFFER_FLAG_QUEUED); -} - -static struct buffer *dequeue_buffer(struct impl *this, struct port *port) -{ - struct buffer *b; - - if (spa_list_is_empty(&port->queue)) - return NULL; - - b = spa_list_first(&port->queue, struct buffer, link); - spa_list_remove(&b->link); - SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_QUEUED); - spa_log_trace_fp(this->log, "%p: dequeue buffer %d on port %d %u", - this, b->id, port->id, b->flags); - - return b; -} - -static int -impl_node_port_use_buffers(void *object, - enum spa_direction direction, - uint32_t port_id, - uint32_t flags, - struct spa_buffer **buffers, - uint32_t n_buffers) -{ - struct impl *this = object; - struct port *port; - uint32_t i, j, maxsize; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - port = GET_PORT(this, direction, port_id); - - spa_return_val_if_fail(port->have_format, -EIO); - - spa_log_debug(this->log, "%p: use buffers %d on port %d:%d", - this, n_buffers, direction, port_id); - - clear_buffers(this, port); - - maxsize = 0; - for (i = 0; i < n_buffers; i++) { - struct buffer *b; - uint32_t n_datas = buffers[i]->n_datas; - struct spa_data *d = buffers[i]->datas; - - b = &port->buffers[i]; - b->id = i; - b->flags = 0; - b->buf = buffers[i]; - - if (n_datas != port->blocks) { - spa_log_error(this->log, "%p: invalid blocks %d on buffer %d", - this, n_datas, i); - return -EINVAL; - } - - for (j = 0; j < n_datas; j++) { - if (d[j].data == NULL) { - spa_log_error(this->log, "%p: invalid memory %d on buffer %d %d %p", - this, j, i, d[j].type, d[j].data); - return -EINVAL; - } - if (!SPA_IS_ALIGNED(d[j].data, this->max_align)) { - spa_log_warn(this->log, "%p: memory %d on buffer %d not aligned", - this, j, i); - } - b->datas[j] = d[j].data; - if (direction == SPA_DIRECTION_OUTPUT && - !SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_DYNAMIC)) - this->is_passthrough = false; - - maxsize = SPA_MAX(maxsize, d[j].maxsize); - } - if (direction == SPA_DIRECTION_OUTPUT) - queue_buffer(this, port, i); - } - if (maxsize > this->empty_size) { - this->empty = realloc(this->empty, maxsize + MAX_ALIGN); - if (this->empty == NULL) - return -errno; - memset(this->empty, 0, maxsize + MAX_ALIGN); - this->empty_size = maxsize; - } - port->n_buffers = n_buffers; - - return 0; -} - -static int -impl_node_port_set_io(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t id, void *data, size_t size) -{ - struct impl *this = object; - struct port *port; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_log_debug(this->log, "%p: set io %d on port %d:%d %p", - this, id, direction, port_id, data); - - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - port = GET_PORT(this, direction, port_id); - - switch (id) { - case SPA_IO_Buffers: - port->io = data; - break; - default: - return -ENOENT; - } - return 0; -} - -static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) -{ - struct impl *this = object; - struct port *port; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); - - port = GET_OUT_PORT(this, port_id); - queue_buffer(this, port, buffer_id); - - return 0; -} - -static inline int get_in_buffer(struct impl *this, struct port *port, struct buffer **buf) -{ - struct spa_io_buffers *io; - - if ((io = port->io) == NULL) { - spa_log_trace_fp(this->log, "%p: no io on port %d", - this, port->id); - return -EIO; - } - if (io->status != SPA_STATUS_HAVE_DATA || - io->buffer_id >= port->n_buffers) { - spa_log_trace_fp(this->log, "%p: empty port %d %p %d %d %d", - this, port->id, io, io->status, io->buffer_id, - port->n_buffers); - return -EPIPE; - } - - *buf = &port->buffers[io->buffer_id]; - io->status = SPA_STATUS_NEED_DATA; - - return 0; -} - -static inline int get_out_buffer(struct impl *this, struct port *port, struct buffer **buf) -{ - struct spa_io_buffers *io; - - if (SPA_UNLIKELY((io = port->io) == NULL || - io->status == SPA_STATUS_HAVE_DATA)) - return SPA_STATUS_HAVE_DATA; - - if (SPA_LIKELY(io->buffer_id < port->n_buffers)) - queue_buffer(this, port, io->buffer_id); - - if (SPA_UNLIKELY((*buf = dequeue_buffer(this, port)) == NULL)) - return -EPIPE; - - io->status = SPA_STATUS_HAVE_DATA; - io->buffer_id = (*buf)->id; - - return 0; -} - -static inline int handle_monitor(struct impl *this, const void *data, float volume, int n_samples, struct port *outport) -{ - struct buffer *dbuf; - struct spa_data *dd; - int res, size; - - if (SPA_UNLIKELY((res = get_out_buffer(this, outport, &dbuf)) != 0)) - return res; - - dd = &dbuf->buf->datas[0]; - size = SPA_MIN(dd->maxsize, n_samples * outport->stride); - dd->chunk->offset = 0; - dd->chunk->size = size; - - spa_log_trace(this->log, "%p: io %p %08x", this, outport->io, dd->flags); - - if (SPA_FLAG_IS_SET(dd->flags, SPA_DATA_FLAG_DYNAMIC) && volume == VOLUME_NORM) - dd->data = (void*)data; - else - volume_process(&this->volume, dd->data, data, volume, size / outport->stride); - - return res; -} - -static int impl_node_process(void *object) -{ - struct impl *this = object; - struct port *outport; - struct spa_io_buffers *outio; - uint32_t i, maxsize, n_samples; - struct spa_data *sd, *dd; - struct buffer *sbuf, *dbuf; - uint32_t n_src_datas, n_dst_datas; - const void **src_datas; - void **dst_datas; - int res; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - outport = GET_OUT_PORT(this, 0); - outio = outport->io; - spa_return_val_if_fail(outio != NULL, -EIO); - spa_return_val_if_fail(this->conv.process != NULL, -EIO); - - spa_log_trace_fp(this->log, "%p: status %p %d %d", this, - outio, outio->status, outio->buffer_id); - - if (SPA_UNLIKELY((res = get_out_buffer(this, outport, &dbuf)) != 0)) - return res; - - dd = &dbuf->buf->datas[0]; - - maxsize = dd->maxsize; - - if (SPA_LIKELY(this->io_position)) - n_samples = this->io_position->clock.duration; - else - n_samples = maxsize / outport->stride; - - - n_dst_datas = dbuf->buf->n_datas; - dst_datas = alloca(sizeof(void*) * n_dst_datas); - - n_src_datas = this->port_count; - src_datas = alloca(sizeof(void*) * this->port_count); - - /* produce more output if possible */ - for (i = 0; i < n_src_datas; i++) { - struct port *inport = GET_IN_PORT(this, i); - - if (SPA_UNLIKELY(get_in_buffer(this, inport, &sbuf) < 0)) { - src_datas[i] = SPA_PTR_ALIGN(this->empty, MAX_ALIGN, void); - continue; - } - - sd = &sbuf->buf->datas[0]; - - src_datas[i] = SPA_PTROFF(sd->data, sd->chunk->offset, void); - - n_samples = SPA_MIN(n_samples, sd->chunk->size / inport->stride); - - spa_log_trace_fp(this->log, "%p: %d %d %d %p", this, - sd->chunk->size, maxsize, n_samples, src_datas[i]); - } - - for (i = 0; i < this->monitor_count; i++) { - float volume; - - volume = this->props.monitor.mute ? 0.0f : this->props.monitor.volumes[i]; - if (this->monitor_channel_volumes) - volume *= this->props.channel.mute ? 0.0f : this->props.channel.volumes[i]; - - handle_monitor(this, src_datas[i], volume, n_samples, - GET_OUT_PORT(this, i + 1)); - } - - for (i = 0; i < n_dst_datas; i++) { - uint32_t dst_remap = this->dst_remap[i]; - uint32_t src_remap = this->src_remap[i]; - struct spa_data *dd = dbuf->buf->datas; - - if (this->is_passthrough) - dd[i].data = (void *)src_datas[src_remap]; - else - dst_datas[dst_remap] = dd[i].data = dbuf->datas[i]; - - dd[i].chunk->offset = 0; - dd[i].chunk->size = n_samples * outport->stride; - } - - spa_log_trace_fp(this->log, "%p: n_src:%d n_dst:%d n_samples:%d max:%d p:%d", this, - n_src_datas, n_dst_datas, n_samples, maxsize, this->is_passthrough); - - if (!this->is_passthrough) - convert_process(&this->conv, dst_datas, src_datas, n_samples); - - return SPA_STATUS_NEED_DATA | SPA_STATUS_HAVE_DATA; -} - -static const struct spa_node_methods impl_node = { - SPA_VERSION_NODE_METHODS, - .add_listener = impl_node_add_listener, - .set_callbacks = impl_node_set_callbacks, - .enum_params = impl_node_enum_params, - .set_param = impl_node_set_param, - .set_io = impl_node_set_io, - .send_command = impl_node_send_command, - .add_port = impl_node_add_port, - .remove_port = impl_node_remove_port, - .port_enum_params = impl_node_port_enum_params, - .port_set_param = impl_node_port_set_param, - .port_use_buffers = impl_node_port_use_buffers, - .port_set_io = impl_node_port_set_io, - .port_reuse_buffer = impl_node_port_reuse_buffer, - .process = impl_node_process, -}; - -static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) -{ - struct impl *this; - - spa_return_val_if_fail(handle != NULL, -EINVAL); - spa_return_val_if_fail(interface != NULL, -EINVAL); - - this = (struct impl *) handle; - - if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) - *interface = &this->node; - else - return -ENOENT; - - return 0; -} - -static int impl_clear(struct spa_handle *handle) -{ - struct impl *this; - uint32_t i; - - spa_return_val_if_fail(handle != NULL, -EINVAL); - - this = (struct impl *) handle; - - for (i = 0; i < MAX_PORTS; i++) - free(this->in_ports[i]); - for (i = 0; i < MAX_PORTS+1; i++) - free(this->out_ports[i]); - free(this->empty); - return 0; -} - -static size_t -impl_get_size(const struct spa_handle_factory *factory, - const struct spa_dict *params) -{ - return sizeof(struct impl); -} - -static int -impl_init(const struct spa_handle_factory *factory, - struct spa_handle *handle, - const struct spa_dict *info, - const struct spa_support *support, - uint32_t n_support) -{ - struct impl *this; - uint32_t i; - - spa_return_val_if_fail(factory != NULL, -EINVAL); - spa_return_val_if_fail(handle != NULL, -EINVAL); - - handle->get_interface = impl_get_interface; - handle->clear = impl_clear; - - 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 (this->cpu) { - this->cpu_flags = spa_cpu_get_flags(this->cpu); - this->max_align = SPA_MIN(MAX_ALIGN, spa_cpu_get_max_align(this->cpu)); - } - - for (i = 0; info && i < info->n_items; i++) { - const char *k = info->items[i].key; - const char *s = info->items[i].value; - if (spa_streq(k, "clock.quantum-limit")) - spa_atou32(s, &this->quantum_limit, 0); - else - merger_set_param(this, k, s); - } - - this->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); - this->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); - - this->node.iface = SPA_INTERFACE_INIT( - SPA_TYPE_INTERFACE_Node, - SPA_VERSION_NODE, - &impl_node, this); - spa_hook_list_init(&this->hooks); - - this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | - SPA_NODE_CHANGE_MASK_PARAMS; - this->info = SPA_NODE_INFO_INIT(); - this->info.max_input_ports = MAX_PORTS; - this->info.max_output_ports = MAX_PORTS+1; - this->info.flags = SPA_NODE_FLAG_RT | - SPA_NODE_FLAG_IN_PORT_CONFIG; - this->params[IDX_PortConfig] = SPA_PARAM_INFO(SPA_PARAM_PortConfig, SPA_PARAM_INFO_WRITE); - 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->info.params = this->params; - this->info.n_params = N_NODE_PARAMS; - - init_port(this, SPA_DIRECTION_OUTPUT, 0, 0); - - this->volume.cpu_flags = this->cpu_flags; - volume_init(&this->volume); - props_reset(&this->props); - - return 0; -} - -static const struct spa_interface_info impl_interfaces[] = { - {SPA_TYPE_INTERFACE_Node,}, -}; - -static int -impl_enum_interface_info(const struct spa_handle_factory *factory, - const struct spa_interface_info **info, - uint32_t *index) -{ - spa_return_val_if_fail(factory != NULL, -EINVAL); - spa_return_val_if_fail(info != NULL, -EINVAL); - spa_return_val_if_fail(index != NULL, -EINVAL); - - switch (*index) { - case 0: - *info = &impl_interfaces[*index]; - break; - default: - return 0; - } - (*index)++; - return 1; -} - -const struct spa_handle_factory spa_merger_factory = { - SPA_VERSION_HANDLE_FACTORY, - SPA_NAME_AUDIO_PROCESS_INTERLEAVE, - NULL, - impl_get_size, - impl_init, - impl_enum_interface_info, -}; diff --git a/spa/plugins/audioconvert/meson.build b/spa/plugins/audioconvert/meson.build index 89af1267133b4a34a94a29fe58ac3aaf8a11518a..d84699242dcff1f665c3c0423c27871f34c16cca 100644 --- a/spa/plugins/audioconvert/meson.build +++ b/spa/plugins/audioconvert/meson.build @@ -1,15 +1,26 @@ -audioconvert_sources = ['audioadapter.c', +audioconvert_sources = [ + 'audioadapter.c', 'audioconvert.c', - 'fmtconvert.c', - 'channelmix.c', - 'merger.c', - 'plugin.c', - 'resample.c', - 'splitter.c'] + 'plugin.c' +] simd_cargs = [] simd_dependencies = [] +audioconvert_c = static_library('audioconvert_c', + [ 'channelmix-ops-c.c', + 'biquad.c', + 'crossover.c', + 'volume-ops-c.c', + 'resample-native-c.c', + 'resample-peaks-c.c', + 'fmt-ops-c.c' ], + c_args : ['-Ofast', '-ffast-math'], + dependencies : [ spa_dep ], + install : false + ) +simd_dependencies += audioconvert_c + if have_sse audioconvert_sse = static_library('audioconvert_sse', ['resample-native-sse.c', @@ -89,15 +100,10 @@ endif audioconvert_lib = static_library('audioconvert', ['fmt-ops.c', - 'biquad.c', - 'crossover.c', 'channelmix-ops.c', - 'channelmix-ops-c.c', 'resample-native.c', 'resample-peaks.c', - 'fmt-ops-c.c', - 'volume-ops.c', - 'volume-ops-c.c' ], + 'volume-ops.c' ], c_args : [ simd_cargs, '-O3'], link_with : simd_dependencies, include_directories : [configinc], diff --git a/spa/plugins/audioconvert/plugin.c b/spa/plugins/audioconvert/plugin.c index fba14c2059549fe0be1e74b768bd21b8bead1b36..03c206f89ccfb13a86c8e4ac8e147ad5900bbd0e 100644 --- a/spa/plugins/audioconvert/plugin.c +++ b/spa/plugins/audioconvert/plugin.c @@ -27,11 +27,6 @@ #include <spa/support/plugin.h> extern const struct spa_handle_factory spa_audioconvert_factory; -extern const struct spa_handle_factory spa_fmtconvert_factory; -extern const struct spa_handle_factory spa_channelmix_factory; -extern const struct spa_handle_factory spa_resample_factory; -extern const struct spa_handle_factory spa_splitter_factory; -extern const struct spa_handle_factory spa_merger_factory; extern const struct spa_handle_factory spa_audioadapter_factory; SPA_EXPORT @@ -45,21 +40,6 @@ int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *factory = &spa_audioconvert_factory; break; case 1: - *factory = &spa_fmtconvert_factory; - break; - case 2: - *factory = &spa_channelmix_factory; - break; - case 3: - *factory = &spa_resample_factory; - break; - case 4: - *factory = &spa_splitter_factory; - break; - case 5: - *factory = &spa_merger_factory; - break; - case 6: *factory = &spa_audioadapter_factory; break; default: diff --git a/spa/plugins/audioconvert/resample-native-c.c b/spa/plugins/audioconvert/resample-native-c.c new file mode 100644 index 0000000000000000000000000000000000000000..3fe50b769d1e6a4b4adad7a38a5586e5d1522691 --- /dev/null +++ b/spa/plugins/audioconvert/resample-native-c.c @@ -0,0 +1,65 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "resample-native-impl.h" + +static void inner_product_c(float *d, const float * SPA_RESTRICT s, + const float * SPA_RESTRICT taps, uint32_t n_taps) +{ + float sum = 0.0f; +#if 1 + uint32_t i, j, nt2 = n_taps/2; + for (i = 0, j = n_taps-1; i < nt2; i++, j--) + sum += s[i] * taps[i] + s[j] * taps[j]; +#else + uint32_t i; + for (i = 0; i < n_taps; i++) + sum += s[i] * taps[i]; +#endif + *d = sum; +} + +static 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) +{ + float sum[2] = { 0.0f, 0.0f }; + uint32_t i; +#if 1 + uint32_t j, nt2 = n_taps/2; + for (i = 0, j = n_taps-1; i < nt2; i++, j--) { + sum[0] += s[i] * t0[i] + s[j] * t0[j]; + sum[1] += s[i] * t1[i] + s[j] * t1[j]; + } +#else + for (i = 0; i < n_taps; i++) { + sum[0] += s[i] * t0[i]; + sum[1] += s[i] * t1[i]; + } +#endif + *d = (sum[1] - sum[0]) * x + sum[0]; +} + +MAKE_RESAMPLER_FULL(c); +MAKE_RESAMPLER_INTER(c); diff --git a/spa/plugins/audioconvert/resample-native-impl.h b/spa/plugins/audioconvert/resample-native-impl.h index beb7bd6f955cb33ce0a8d6816ec789d6b0966363..a6c87fc3640ea8c6cd0b096ff656be912d1db387 100644 --- a/spa/plugins/audioconvert/resample-native-impl.h +++ b/spa/plugins/audioconvert/resample-native-impl.h @@ -34,10 +34,13 @@ typedef void (*resample_func_t)(struct resample *r, struct resample_info { uint32_t format; - uint32_t cpu_flags; resample_func_t process_copy; + const char *copy_name; resample_func_t process_full; + const char *full_name; resample_func_t process_inter; + const char *inter_name; + uint32_t cpu_flags; }; struct native_data { diff --git a/spa/plugins/audioconvert/resample-native.c b/spa/plugins/audioconvert/resample-native.c index f522d67e2cf85b607a829db749b909b76faeae58..b46a09fc1f0cf0e223b0a70083e107da4824bb4f 100644 --- a/spa/plugins/audioconvert/resample-native.c +++ b/spa/plugins/audioconvert/resample-native.c @@ -33,22 +33,22 @@ struct quality { double cutoff; }; -static const struct quality blackman_qualities[] = { - { 8, 0.5, }, - { 16, 0.70, }, - { 24, 0.76, }, - { 32, 0.8, }, +static const struct quality window_qualities[] = { + { 8, 0.53, }, + { 16, 0.67, }, + { 24, 0.75, }, + { 32, 0.80, }, { 48, 0.85, }, /* default */ - { 64, 0.90, }, - { 80, 0.92, }, - { 96, 0.933, }, - { 128, 0.950, }, - { 144, 0.955, }, - { 160, 0.958, }, - { 192, 0.965, }, - { 256, 0.975, }, - { 896, 0.997, }, - { 1024, 0.998, }, + { 64, 0.88, }, + { 80, 0.895, }, + { 96, 0.910, }, + { 128, 0.936, }, + { 144, 0.945, }, + { 160, 0.950, }, + { 192, 0.960, }, + { 256, 0.970, }, + { 896, 0.990, }, + { 1024, 0.995, }, }; static inline double sinc(double x) @@ -58,14 +58,29 @@ static inline double sinc(double x) return sin(x) / x; } -static inline double blackman(double x, double n_taps) +static inline double window_blackman(double x, double n_taps) { - double alpha = 0.232; + double alpha = 0.232, r; x = 2.0 * M_PI * x / n_taps; - return (1.0 - alpha) / 2.0 + (1.0 / 2.0) * cos(x) + - (alpha / 2.0) * cos(2 * x); + r = (1.0 - alpha) / 2.0 + (1.0 / 2.0) * cos(x) + + (alpha / 2.0) * cos(2.0 * x); + return r; +} +static inline double window_cosh(double x, double n_taps) +{ + double r; + double A = 16.97789; + double x2; + x = 2.0 * x / n_taps; + x2 = x * x; + if (x2 >= 1.0) + return 0.0; + /* doi:10.1109/RME.2008.4595727 with tweak */ + r = (exp(A * sqrt(1 - x2)) - 1) / (exp(A) - 1); + return r; } +#define window window_cosh static int build_filter(float *taps, uint32_t stride, uint32_t n_taps, uint32_t n_phases, double cutoff) { @@ -77,74 +92,35 @@ static int build_filter(float *taps, uint32_t stride, uint32_t n_taps, uint32_t /* exploit symmetry in filter taps */ taps[(n_phases - i) * stride + n_taps12 + j] = taps[i * stride + (n_taps12 - j - 1)] = - cutoff * sinc(t * cutoff) * blackman(t, n_taps); + cutoff * sinc(t * cutoff) * window(t, n_taps); } } return 0; } -static void inner_product_c(float *d, const float * SPA_RESTRICT s, - const float * SPA_RESTRICT taps, uint32_t n_taps) -{ - float sum = 0.0f; -#if 1 - uint32_t i, j, nt2 = n_taps/2; - for (i = 0, j = n_taps-1; i < nt2; i++, j--) - sum += s[i] * taps[i] + s[j] * taps[j]; -#else - uint32_t i; - for (i = 0; i < n_taps; i++) - sum += s[i] * taps[i]; -#endif - *d = sum; -} - -static 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) -{ - float sum[2] = { 0.0f, 0.0f }; - uint32_t i; -#if 1 - uint32_t j, nt2 = n_taps/2; - for (i = 0, j = n_taps-1; i < nt2; i++, j--) { - sum[0] += s[i] * t0[i] + s[j] * t0[j]; - sum[1] += s[i] * t1[i] + s[j] * t1[j]; - } -#else - for (i = 0; i < n_taps; i++) { - sum[0] += s[i] * t0[i]; - sum[1] += s[i] * t1[i]; - } -#endif - *d = (sum[1] - sum[0]) * x + sum[0]; -} - MAKE_RESAMPLER_COPY(c); -MAKE_RESAMPLER_FULL(c); -MAKE_RESAMPLER_INTER(c); + +#define MAKE(fmt,copy,full,inter,...) \ + { SPA_AUDIO_FORMAT_ ##fmt, do_resample_ ##copy, #copy, \ + do_resample_ ##full, #full, do_resample_ ##inter, #inter, __VA_ARGS__ } static struct resample_info resample_table[] = { #if defined (HAVE_NEON) - { SPA_AUDIO_FORMAT_F32, SPA_CPU_FLAG_NEON, - do_resample_copy_c, do_resample_full_neon, do_resample_inter_neon }, + MAKE(F32, copy_c, full_neon, inter_neon, SPA_CPU_FLAG_NEON), #endif #if defined(HAVE_AVX) && defined(HAVE_FMA) - { SPA_AUDIO_FORMAT_F32, SPA_CPU_FLAG_AVX | SPA_CPU_FLAG_FMA3, - do_resample_copy_c, do_resample_full_avx, do_resample_inter_avx }, + MAKE(F32, copy_c, full_avx, inter_avx, SPA_CPU_FLAG_AVX | SPA_CPU_FLAG_FMA3), #endif #if defined (HAVE_SSSE3) - { SPA_AUDIO_FORMAT_F32, SPA_CPU_FLAG_SSSE3 | SPA_CPU_FLAG_SLOW_UNALIGNED, - do_resample_copy_c, do_resample_full_ssse3, do_resample_inter_ssse3 }, + MAKE(F32, copy_c, full_ssse3, inter_ssse3, SPA_CPU_FLAG_SSSE3 | SPA_CPU_FLAG_SLOW_UNALIGNED), #endif #if defined (HAVE_SSE) - { SPA_AUDIO_FORMAT_F32, SPA_CPU_FLAG_SSE, - do_resample_copy_c, do_resample_full_sse, do_resample_inter_sse }, + MAKE(F32, copy_c, full_sse, inter_sse, SPA_CPU_FLAG_SSE), #endif - { SPA_AUDIO_FORMAT_F32, 0, - do_resample_copy_c, do_resample_full_c, do_resample_inter_c }, + MAKE(F32, copy_c, full_c, inter_c), }; +#undef MAKE #define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) static const struct resample_info *find_resample_info(uint32_t format, uint32_t cpu_flags) @@ -200,12 +176,18 @@ static void impl_native_update_rate(struct resample *r, double rate) data->inc = data->in_rate / data->out_rate; data->frac = data->in_rate % data->out_rate; - if (data->in_rate == data->out_rate) + if (data->in_rate == data->out_rate) { data->func = data->info->process_copy; - else if (rate == 1.0) + r->func_name = data->info->copy_name; + } + else if (rate == 1.0) { data->func = data->info->process_full; - else + r->func_name = data->info->full_name; + } + else { data->func = data->info->process_inter; + r->func_name = data->info->inter_name; + } spa_log_trace_fp(r->log, "native %p: rate:%f in:%d out:%d phase:%d inc:%d frac:%d", r, rate, data->in_rate, data->out_rate, data->phase, data->inc, data->frac); @@ -340,7 +322,7 @@ int resample_native_init(struct resample *r) uint32_t c, n_taps, n_phases, filter_size, in_rate, out_rate, gcd, filter_stride; uint32_t history_stride, history_size, oversample; - r->quality = SPA_CLAMP(r->quality, 0, (int) SPA_N_ELEMENTS(blackman_qualities) - 1); + r->quality = SPA_CLAMP(r->quality, 0, (int) SPA_N_ELEMENTS(window_qualities) - 1); r->free = impl_native_free; r->update_rate = impl_native_update_rate; r->in_len = impl_native_in_len; @@ -348,14 +330,15 @@ int resample_native_init(struct resample *r) r->reset = impl_native_reset; r->delay = impl_native_delay; - q = &blackman_qualities[r->quality]; + q = &window_qualities[r->quality]; gcd = calc_gcd(r->i_rate, r->o_rate); in_rate = r->i_rate / gcd; out_rate = r->o_rate / gcd; - scale = SPA_MIN(q->cutoff * out_rate / in_rate, 1.0); + scale = SPA_MIN(q->cutoff * out_rate / in_rate, q->cutoff); + /* multiple of 8 taps to ease simd optimizations */ n_taps = SPA_ROUND_UP_N((uint32_t)ceil(q->n_taps / scale), 8); n_taps = SPA_MIN(n_taps, 1u << 18); @@ -396,10 +379,9 @@ int resample_native_init(struct resample *r) build_filter(d->filter, d->filter_stride, n_taps, n_phases, scale); d->info = find_resample_info(SPA_AUDIO_FORMAT_F32, r->cpu_flags); - if (SPA_UNLIKELY(!d->info)) - { + if (SPA_UNLIKELY(d->info == NULL)) { spa_log_error(r->log, "failed to find suitable resample format!"); - return -1; + return -ENOTSUP; } spa_log_debug(r->log, "native %p: q:%d in:%d out:%d n_taps:%d n_phases:%d features:%08x:%08x", diff --git a/spa/plugins/audioconvert/resample-peaks-c.c b/spa/plugins/audioconvert/resample-peaks-c.c new file mode 100644 index 0000000000000000000000000000000000000000..3d27016e573e84978131751c869fd83680edcbbf --- /dev/null +++ b/spa/plugins/audioconvert/resample-peaks-c.c @@ -0,0 +1,73 @@ +/* Spa + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <math.h> + +#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) +{ + 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; + } +} diff --git a/spa/plugins/audioconvert/resample-peaks-impl.h b/spa/plugins/audioconvert/resample-peaks-impl.h index d8b28fb2bab9dc9628f08b831620543845a3fc30..7a39af078a4ed6d433ae8d1114d53a3f53ccc47e 100644 --- a/spa/plugins/audioconvert/resample-peaks-impl.h +++ b/spa/plugins/audioconvert/resample-peaks-impl.h @@ -34,6 +34,9 @@ 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); #if defined (HAVE_SSE) void resample_peaks_process_sse(struct resample *r, const void * SPA_RESTRICT src[], uint32_t *in_len, diff --git a/spa/plugins/audioconvert/resample-peaks.c b/spa/plugins/audioconvert/resample-peaks.c index 9e4c4223dc1546a61119a1cd6bf9c3492f04c93e..2fa52e875e4a65a55b89842f16759a15fb2a92ff 100644 --- a/spa/plugins/audioconvert/resample-peaks.c +++ b/spa/plugins/audioconvert/resample-peaks.c @@ -29,52 +29,6 @@ #include "resample-peaks-impl.h" -static 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) -{ - 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; - } -} - struct resample_info { uint32_t format; uint32_t cpu_flags; diff --git a/spa/plugins/audioconvert/resample.c b/spa/plugins/audioconvert/resample.c deleted file mode 100644 index 91dabb6cc36b89d10aaee24af75bd636b9e47605..0000000000000000000000000000000000000000 --- a/spa/plugins/audioconvert/resample.c +++ /dev/null @@ -1,1307 +0,0 @@ -/* Spa - * - * Copyright © 2018 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include <errno.h> -#include <string.h> -#include <stdio.h> - -#include <spa/support/plugin.h> -#include <spa/support/log.h> -#include <spa/utils/list.h> -#include <spa/utils/names.h> -#include <spa/utils/string.h> -#include <spa/node/node.h> -#include <spa/node/io.h> -#include <spa/node/utils.h> -#include <spa/param/audio/format-utils.h> -#include <spa/param/param.h> -#include <spa/pod/filter.h> -#include <spa/debug/types.h> - -#include "resample.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.resample"); - -#define DEFAULT_RATE 48000 -#define DEFAULT_CHANNELS 2 - -#define MAX_BUFFERS 32 - -struct impl; - -struct props { - double rate; - int quality; - bool disabled; -}; - -static void props_reset(struct props *props) -{ - props->rate = 1.0; - props->quality = RESAMPLE_DEFAULT_QUALITY; - props->disabled = false; -} - -struct buffer { - uint32_t id; -#define BUFFER_FLAG_OUT (1 << 0) - uint32_t flags; - struct spa_list link; - struct spa_buffer *outbuf; - struct spa_meta_header *h; -}; - -struct port { - uint32_t direction; - uint32_t id; - - uint64_t info_all; - struct spa_port_info info; - struct spa_param_info params[8]; - - struct spa_io_buffers *io; - - struct spa_audio_info format; - uint32_t stride; - uint32_t blocks; - uint32_t size; - unsigned int have_format:1; - - struct buffer buffers[MAX_BUFFERS]; - uint32_t n_buffers; - - uint32_t offset; - struct spa_list queue; -}; - -struct impl { - struct spa_handle handle; - struct spa_node node; - - struct spa_log *log; - struct spa_cpu *cpu; - - uint32_t quantum_limit; - - struct spa_io_position *io_position; - struct spa_io_rate_match *io_rate_match; - - uint64_t info_all; - struct spa_node_info info; - struct props props; - - struct spa_hook_list hooks; - - struct port in_port; - struct port out_port; - -#define MODE_SPLIT 0 -#define MODE_MERGE 1 -#define MODE_CONVERT 2 - int mode; - unsigned int started:1; - unsigned int peaks:1; - unsigned int drained:1; - - struct resample resample; - - double rate_scale; -}; - -#define CHECK_PORT(this,d,id) (id == 0) -#define GET_IN_PORT(this,id) (&this->in_port) -#define GET_OUT_PORT(this,id) (&this->out_port) -#define GET_PORT(this,d,id) (d == SPA_DIRECTION_INPUT ? GET_IN_PORT(this,id) : GET_OUT_PORT(this,id)) - -static int setup_convert(struct impl *this, - enum spa_direction direction, - const struct spa_audio_info *info) -{ - const struct spa_audio_info *src_info, *dst_info; - int err; - - if (direction == SPA_DIRECTION_INPUT) { - src_info = info; - dst_info = &GET_OUT_PORT(this, 0)->format; - } else { - src_info = &GET_IN_PORT(this, 0)->format; - dst_info = info; - } - - spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d", this, - spa_debug_type_find_name(spa_type_audio_format, src_info->info.raw.format), - src_info->info.raw.channels, - src_info->info.raw.rate, - spa_debug_type_find_name(spa_type_audio_format, dst_info->info.raw.format), - dst_info->info.raw.channels, - dst_info->info.raw.rate); - - if (src_info->info.raw.channels != dst_info->info.raw.channels) - return -EINVAL; - - if (this->resample.free) - resample_free(&this->resample); - - this->resample.channels = src_info->info.raw.channels; - this->resample.i_rate = src_info->info.raw.rate; - this->resample.o_rate = dst_info->info.raw.rate; - this->resample.log = this->log; - this->resample.quality = this->props.quality; - - if (this->peaks) - err = resample_peaks_init(&this->resample); - else - err = resample_native_init(&this->resample); - - return err; -} - -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[4096]; - struct spa_result_node_params result; - uint32_t count = 0; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(num != 0, -EINVAL); - - result.id = id; - result.next = start; - next: - result.index = result.next++; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - switch (id) { - case SPA_PARAM_PropInfo: - { - struct props *p = &this->props; - - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_rate), - SPA_PROP_INFO_description, SPA_POD_String("Rate scaler"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Double(p->rate, 0.0, 10.0)); - break; - case 1: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_quality), - SPA_PROP_INFO_name, SPA_POD_String("resample.quality"), - SPA_PROP_INFO_description, SPA_POD_String("Resample Quality"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->quality, 0, 14), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 2: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("resample.disable"), - SPA_PROP_INFO_description, SPA_POD_String("Disable Resampling"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->disabled), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - default: - return 0; - } - break; - } - case SPA_PARAM_Props: - { - struct props *p = &this->props; - struct spa_pod_frame f[2]; - - switch (result.index) { - case 0: - spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_Props, id); - spa_pod_builder_add(&b, - SPA_PROP_rate, SPA_POD_Double(p->rate), - SPA_PROP_quality, SPA_POD_Int(p->quality), - 0); - spa_pod_builder_prop(&b, SPA_PROP_params, 0); - spa_pod_builder_push_struct(&b, &f[1]); - spa_pod_builder_string(&b, "resample.quality"); - spa_pod_builder_int(&b, p->quality); - spa_pod_builder_string(&b, "resample.disable"); - spa_pod_builder_bool(&b, p->disabled); - spa_pod_builder_pop(&b, &f[1]); - param = spa_pod_builder_pop(&b, &f[0]); - break; - default: - return 0; - } - break; - } - default: - return -ENOENT; - } - - if (spa_pod_filter(&b, &result.param, param, filter) < 0) - goto next; - - spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - - if (++count != num) - goto next; - - return 0; -} - -static int resample_set_param(struct impl *this, const char *k, const char *s) -{ - if (spa_streq(k, "resample.quality")) - this->props.quality = atoi(s); - else if (spa_streq(k, "resample.disable")) - this->props.disabled = spa_atob(s); - return 0; -} - -static int parse_prop_params(struct impl *this, struct spa_pod *params) -{ - struct spa_pod_parser prs; - struct spa_pod_frame f; - - spa_pod_parser_pod(&prs, params); - if (spa_pod_parser_push_struct(&prs, &f) < 0) - return 0; - - while (true) { - const char *name; - struct spa_pod *pod; - char value[512]; - - if (spa_pod_parser_get_string(&prs, &name) < 0) - break; - - if (spa_pod_parser_get_pod(&prs, &pod) < 0) - break; - - if (spa_pod_is_string(pod)) { - spa_pod_copy_string(pod, sizeof(value), value); - } else if (spa_pod_is_int(pod)) { - snprintf(value, sizeof(value), "%d", - SPA_POD_VALUE(struct spa_pod_int, pod)); - } else if (spa_pod_is_bool(pod)) { - snprintf(value, sizeof(value), "%s", - SPA_POD_VALUE(struct spa_pod_bool, pod) ? - "true" : "false"); - } else - continue; - - spa_log_info(this->log, "key:'%s' val:'%s'", name, value); - resample_set_param(this, name, value); - } - return 0; -} - -static int apply_props(struct impl *this, const struct spa_pod *param) -{ - struct spa_pod_prop *prop; - struct spa_pod_object *obj = (struct spa_pod_object *) param; - struct props *p = &this->props; - int changed = 0; - - SPA_POD_OBJECT_FOREACH(obj, prop) { - switch (prop->key) { - case SPA_PROP_rate: - if (spa_pod_get_double(&prop->value, &p->rate) == 0) { - resample_update_rate(&this->resample, p->rate); - changed++; - } - break; - case SPA_PROP_quality: - if (spa_pod_get_int(&prop->value, &p->quality) == 0) - changed++; - break; - case SPA_PROP_params: - changed += parse_prop_params(this, &prop->value); - break; - default: - break; - } - } - return changed; -} - -static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - struct impl *this = object; - int res = 0; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - switch (id) { - case SPA_PARAM_Props: - apply_props(this, param); - break; - default: - return -ENOTSUP; - } - - return res; -} - -static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_log_debug(this->log, "%p: io %d %p/%zd", this, id, data, size); - - switch (id) { - case SPA_IO_Position: - this->io_position = data; - break; - default: - return -ENOENT; - } - return 0; -} - -static void update_rate_match(struct impl *this, bool passthrough, uint32_t out_size, uint32_t in_queued) -{ - double r = this->rate_scale / this->props.rate; - - if (this->io_rate_match) { - uint32_t delay, match_size; - - if (passthrough) { - delay = in_queued; - match_size = out_size; - } else { - if (SPA_FLAG_IS_SET(this->io_rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE)) - resample_update_rate(&this->resample, r * this->io_rate_match->rate); - else - resample_update_rate(&this->resample, r); - - delay = resample_delay(&this->resample) + in_queued; - match_size = resample_in_len(&this->resample, out_size); - } - match_size -= SPA_MIN(match_size, in_queued); - this->io_rate_match->size = match_size; - this->io_rate_match->delay = delay; - spa_log_trace_fp(this->log, "%p: next match:%u queued:%u delay:%u", this, match_size, - in_queued, delay); - } else { - resample_update_rate(&this->resample, r); - } -} -static inline bool is_passthrough(struct impl *this) -{ - return this->resample.i_rate == this->resample.o_rate && - this->rate_scale == 1.0 && this->props.rate == 1.0 && - (this->io_rate_match == NULL || this->props.disabled || - !SPA_FLAG_IS_SET(this->io_rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE)); -} - -static void recalc_rate_match(struct impl *this) -{ - bool passthrough = is_passthrough(this); - uint32_t out_size = this->io_position ? this->io_position->clock.duration : 1024; - update_rate_match(this, passthrough, out_size, 0); -} - -static void reset_node(struct impl *this) -{ - struct port *outport, *inport; - outport = GET_OUT_PORT(this, 0); - inport = GET_IN_PORT(this, 0); - - if (this->resample.reset) - resample_reset(&this->resample); - outport->offset = 0; - inport->offset = 0; -} - -static int impl_node_send_command(void *object, const struct spa_command *command) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(command != NULL, -EINVAL); - - switch (SPA_NODE_COMMAND_ID(command)) { - case SPA_NODE_COMMAND_Start: - recalc_rate_match(this); - this->started = true; - break; - case SPA_NODE_COMMAND_Suspend: - case SPA_NODE_COMMAND_Flush: - reset_node(this); - SPA_FALLTHROUGH; - case SPA_NODE_COMMAND_Pause: - this->started = false; - break; - default: - return -ENOTSUP; - } - return 0; -} - -static void emit_node_info(struct impl *this, bool full) -{ - uint64_t old = full ? this->info.change_mask : 0; - if (full) - this->info.change_mask = this->info_all; - if (this->info.change_mask) { - spa_node_emit_info(&this->hooks, &this->info); - this->info.change_mask = old; - } -} - -static void emit_port_info(struct impl *this, struct port *port, bool full) -{ - uint64_t old = full ? port->info.change_mask : 0; - if (full) - port->info.change_mask = port->info_all; - if (port->info.change_mask) { - spa_node_emit_port_info(&this->hooks, - port->direction, port->id, &port->info); - port->info.change_mask = old; - } -} - -static int -impl_node_add_listener(void *object, - struct spa_hook *listener, - const struct spa_node_events *events, - void *data) -{ - struct impl *this = object; - struct spa_hook_list save; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_hook_list_isolate(&this->hooks, &save, listener, events, data); - - emit_node_info(this, true); - emit_port_info(this, GET_IN_PORT(this, 0), true); - emit_port_info(this, GET_OUT_PORT(this, 0), true); - - spa_hook_list_join(&this->hooks, &save); - - return 0; -} - -static int -impl_node_set_callbacks(void *object, - const struct spa_node_callbacks *callbacks, - void *user_data) -{ - return 0; -} - -static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, - const struct spa_dict *props) -{ - return -ENOTSUP; -} - -static int -impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) -{ - return -ENOTSUP; -} - -static int port_enum_formats(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t index, - struct spa_pod **param, - struct spa_pod_builder *builder) -{ - struct impl *this = object; - struct port *other; - struct spa_pod_frame f; - uint32_t rate, min = 1, max = INT32_MAX; - - other = GET_PORT(this, SPA_DIRECTION_REVERSE(direction), 0); - - switch (index) { - case 0: - if (other->have_format) { - rate = other->format.info.raw.rate; - if (this->props.disabled) - min = max = rate; - - spa_pod_builder_push_object(builder, &f, - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); - spa_pod_builder_add(builder, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_F32P), - SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(rate, min, max), - SPA_FORMAT_AUDIO_channels, SPA_POD_Int(other->format.info.raw.channels), - 0); - spa_pod_builder_prop(builder, SPA_FORMAT_AUDIO_position, 0); - spa_pod_builder_array(builder, sizeof(uint32_t), SPA_TYPE_Id, - other->format.info.raw.channels, other->format.info.raw.position); - *param = spa_pod_builder_pop(builder, &f); - } else { - rate = this->io_position ? - this->io_position->clock.rate.denom : DEFAULT_RATE; - if (this->props.disabled) - min = max = rate; - - *param = spa_pod_builder_add_object(builder, - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_F32P), - SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(rate, min, max), - SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(DEFAULT_CHANNELS, 1, INT32_MAX)); - } - break; - default: - return 0; - } - return 1; -} - -static int -impl_node_port_enum_params(void *object, int seq, - enum spa_direction direction, uint32_t port_id, - uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) -{ - struct impl *this = object; - struct port *port, *other; - struct spa_pod *param; - struct spa_pod_builder b = { 0 }; - uint8_t buffer[1024]; - struct spa_result_node_params result; - uint32_t count = 0; - int res; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(num != 0, -EINVAL); - - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - port = GET_PORT(this, direction, port_id); - other = GET_PORT(this, SPA_DIRECTION_REVERSE(direction), port_id); - - spa_log_debug(this->log, "%p: enum params port %d.%d %d %u", - this, direction, port_id, seq, id); - - result.id = id; - result.next = start; - next: - result.index = result.next++; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - switch (id) { - case SPA_PARAM_EnumFormat: - if ((res = port_enum_formats(this, direction, port_id, - result.index, ¶m, &b)) <= 0) - return res; - break; - case SPA_PARAM_Format: - if (!port->have_format) - return -EIO; - if (result.index > 0) - return 0; - - param = spa_format_audio_raw_build(&b, id, &port->format.info.raw); - break; - case SPA_PARAM_Buffers: - { - uint32_t buffers, size; - uint32_t rate; - - if (!port->have_format || !other->have_format) - return -EIO; - if (result.index > 0) - return 0; - - if (direction == SPA_DIRECTION_OUTPUT) { - rate = (this->resample.o_rate + this->resample.i_rate - 1) / this->resample.i_rate; - } else { - rate = (this->resample.i_rate + this->resample.o_rate - 1) / this->resample.o_rate; - } - if (other->n_buffers > 0) { - buffers = other->n_buffers; - size = (other->size / other->stride) * rate; - } else { - buffers = 1; - size = this->quantum_limit * rate; - } - size = SPA_MAX(size, this->quantum_limit) * 2; - - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamBuffers, id, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(buffers, 1, MAX_BUFFERS), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->blocks), - SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( - size * port->stride, - 16 * port->stride, - INT32_MAX), - SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->stride)); - break; - } - case SPA_PARAM_Meta: - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamMeta, id, - SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), - SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); - break; - default: - return 0; - } - break; - case SPA_PARAM_IO: - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamIO, id, - SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), - SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); - break; - default: - return 0; - } - break; - default: - return -ENOENT; - } - - if (spa_pod_filter(&b, &result.param, param, filter) < 0) - goto next; - - spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - - if (++count != num) - goto next; - - return 0; -} - -static int clear_buffers(struct impl *this, struct port *port) -{ - if (port->n_buffers > 0) { - spa_log_debug(this->log, "%p: clear buffers %p", this, port); - port->n_buffers = 0; - spa_list_init(&port->queue); - } - return 0; -} - -static int port_set_format(void *object, - enum spa_direction direction, - uint32_t port_id, - uint32_t flags, - const struct spa_pod *format) -{ - struct impl *this = object; - struct port *port, *other; - int res = 0; - - port = GET_PORT(this, direction, port_id); - other = GET_PORT(this, SPA_DIRECTION_REVERSE(direction), port_id); - - if (format == NULL) { - if (port->have_format) { - port->have_format = false; - clear_buffers(this, port); - } - } else { - struct spa_audio_info info = { 0 }; - - if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) - return res; - - if (info.media_type != SPA_MEDIA_TYPE_audio || - info.media_subtype != SPA_MEDIA_SUBTYPE_raw) - return -EINVAL; - - if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) - return -EINVAL; - - if (info.info.raw.format != SPA_AUDIO_FORMAT_F32P) - return -EINVAL; - - port->stride = sizeof(float); - port->blocks = info.info.raw.channels; - - if (other->have_format) { - if ((res = setup_convert(this, direction, &info)) < 0) - return res; - } - port->format = info; - port->have_format = true; - - spa_log_debug(this->log, "%p: set format on port %d %d", this, port_id, res); - } - - port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - if (port->have_format) { - port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - } else { - port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); - port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); - } - emit_port_info(this, port, false); - - return res; -} - -static int -impl_node_port_set_param(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - spa_return_val_if_fail(object != NULL, -EINVAL); - - spa_return_val_if_fail(CHECK_PORT(object, direction, port_id), -EINVAL); - - if (id == SPA_PARAM_Format) { - return port_set_format(object, direction, port_id, flags, param); - } - else - return -ENOENT; -} - -static int -impl_node_port_use_buffers(void *object, - enum spa_direction direction, - uint32_t port_id, - uint32_t flags, - struct spa_buffer **buffers, - uint32_t n_buffers) -{ - struct impl *this = object; - struct port *port; - uint32_t i, j, size = SPA_ID_INVALID; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - port = GET_PORT(this, direction, port_id); - - spa_return_val_if_fail(port->have_format, -EIO); - - spa_log_debug(this->log, "%p: use buffers %d on port %d:%d", this, - n_buffers, direction, port_id); - - clear_buffers(this, port); - - for (i = 0; i < n_buffers; i++) { - struct buffer *b; - struct spa_data *d = buffers[i]->datas; - - b = &port->buffers[i]; - b->id = i; - b->flags = 0; - b->outbuf = buffers[i]; - b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); - - for (j = 0; j < buffers[i]->n_datas; j++) { - if (size == SPA_ID_INVALID) - size = d[j].maxsize; - else - if (size != d[j].maxsize) { - spa_log_error(this->log, "%p: invalid size %d on buffer %p", this, - size, buffers[i]); - return -EINVAL; - } - - if (d[j].data == NULL) { - spa_log_error(this->log, "%p: invalid memory on buffer %p", this, - buffers[i]); - return -EINVAL; - } - } - - if (direction == SPA_DIRECTION_OUTPUT) - spa_list_append(&port->queue, &b->link); - else - SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); - - port->offset = 0; - } - port->n_buffers = n_buffers; - port->size = size; - - return 0; -} - -static int -impl_node_port_set_io(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t id, void *data, size_t size) -{ - struct impl *this = object; - struct port *port; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - spa_log_trace_fp(this->log, "%p: %d:%d io %d", this, direction, port_id, id); - - port = GET_PORT(this, direction, port_id); - - switch (id) { - case SPA_IO_Buffers: - port->io = data; - break; - case SPA_IO_RateMatch: - this->io_rate_match = data; - break; - default: - return -ENOENT; - } - return 0; -} - -static void recycle_buffer(struct impl *this, uint32_t id) -{ - struct port *port = GET_OUT_PORT(this, 0); - struct buffer *b = &port->buffers[id]; - - if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { - spa_list_append(&port->queue, &b->link); - SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); - spa_log_trace_fp(this->log, "%p: recycle buffer %d", this, id); - } -} - -static struct buffer *peek_buffer(struct impl *this, struct port *port) -{ - struct buffer *b; - - if (spa_list_is_empty(&port->queue)) - return NULL; - - b = spa_list_first(&port->queue, struct buffer, link); - return b; -} - -static void dequeue_buffer(struct impl *this, struct buffer *b) -{ - spa_list_remove(&b->link); - SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); -} - -static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); - - recycle_buffer(this, buffer_id); - - return 0; -} - -static int impl_node_process(void *object) -{ - struct impl *this = object; - struct port *outport, *inport; - struct spa_io_buffers *outio, *inio; - struct buffer *sbuf, *dbuf; - struct spa_buffer *sb, *db; - uint32_t i, size, in_len, out_len, maxsize, max; -#ifndef FASTPATH - uint32_t pin_len, pout_len; -#endif - int res = 0; - const void **src_datas; - void **dst_datas; - bool flush_out = false; - bool flush_in = false; - bool draining = false; - bool passthrough; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - outport = GET_OUT_PORT(this, 0); - inport = GET_IN_PORT(this, 0); - - outio = outport->io; - inio = inport->io; - - spa_return_val_if_fail(outio != NULL, -EIO); - spa_return_val_if_fail(inio != NULL, -EIO); - - spa_log_trace_fp(this->log, "%p: status %p %d %d -> %p %d %d", this, - inio, inio->status, inio->buffer_id, - outio, outio->status, outio->buffer_id); - - if (SPA_UNLIKELY(outio->status == SPA_STATUS_HAVE_DATA)) - return SPA_STATUS_HAVE_DATA; - /* recycle */ - if (SPA_LIKELY(outio->buffer_id < outport->n_buffers)) { - recycle_buffer(this, outio->buffer_id); - outio->buffer_id = SPA_ID_INVALID; - } - if (SPA_UNLIKELY(inio->status != SPA_STATUS_HAVE_DATA)) { - if (inio->status != SPA_STATUS_DRAINED || this->drained) { - recalc_rate_match(this); - return outio->status = inio->status; - } - inio->buffer_id = 0; - inport->buffers[0].outbuf->datas[0].chunk->size = -1; - } - - if (SPA_UNLIKELY(inio->buffer_id >= inport->n_buffers)) - return inio->status = -EINVAL; - - if (SPA_UNLIKELY((dbuf = peek_buffer(this, outport)) == NULL)) - return outio->status = -EPIPE; - - sbuf = &inport->buffers[inio->buffer_id]; - - sb = sbuf->outbuf; - db = dbuf->outbuf; - - maxsize = db->datas[0].maxsize; - - if (SPA_LIKELY(this->io_position)) { - double r = this->rate_scale; - - max = this->io_position->clock.duration * sizeof(float); - if (this->mode == MODE_SPLIT) { - if (this->io_position->clock.rate.denom != this->resample.o_rate) - r = (double) this->io_position->clock.rate.denom / this->resample.o_rate; - else - r = 1.0; - } else { - if (this->io_position->clock.rate.denom != this->resample.i_rate) - r = (double) this->resample.i_rate / this->io_position->clock.rate.denom; - else - r = 1.0; - } - if (this->rate_scale != r) { - spa_log_info(this->log, "scale %f->%f", this->rate_scale, r); - this->rate_scale = r; - } - } - else - max = maxsize; - - switch (this->mode) { - case MODE_SPLIT: - /* in split mode we need to output exactly the size of the - * duration so we don't try to flush early */ - maxsize = SPA_MIN(maxsize, max); - flush_out = false; - break; - case MODE_MERGE: - default: - /* in merge mode we consume one duration of samples and - * always output the resulting data */ - flush_out = true; - break; - } - src_datas = alloca(sizeof(void*) * this->resample.channels); - dst_datas = alloca(sizeof(void*) * this->resample.channels); - - if (outport->offset > maxsize) - outport->offset = maxsize; - - size = sb->datas[0].chunk->size; - if (size == (uint32_t)-1) { - size = sb->datas[0].maxsize; - memset(sb->datas[0].data, 0, size); - for (i = 0; i < sb->n_datas; i++) - src_datas[i] = sb->datas[0].data; - inport->offset = 0; - flush_in = draining = true; - } else { - size = SPA_MIN(size, sb->datas[0].maxsize); - if (inport->offset > size) - inport->offset = size; - for (i = 0; i < sb->n_datas; i++) - src_datas[i] = SPA_PTROFF(sb->datas[i].data, inport->offset, void); - } - for (i = 0; i < db->n_datas; i++) - dst_datas[i] = SPA_PTROFF(db->datas[i].data, outport->offset, void); - - in_len = (size - inport->offset) / sizeof(float); - out_len = (maxsize - outport->offset) / sizeof(float); - -#ifndef FASTPATH - pin_len = in_len; - pout_len = out_len; -#endif - passthrough = is_passthrough(this); - - if (passthrough) { - uint32_t len = SPA_MIN(in_len, out_len); - for (i = 0; i < sb->n_datas; i++) - spa_memcpy(dst_datas[i], src_datas[i], len * sizeof(float)); - out_len = in_len = len; - } else { - resample_process(&this->resample, src_datas, &in_len, dst_datas, &out_len); - } - -#ifndef FASTPATH - spa_log_trace_fp(this->log, "%p: in %d/%d %zd %d out %d/%d %zd %d max:%d", - this, pin_len, in_len, size / sizeof(float), inport->offset, - pout_len, out_len, maxsize / sizeof(float), outport->offset, - max); -#endif - - for (i = 0; i < db->n_datas; i++) { - db->datas[i].chunk->size = outport->offset + (out_len * sizeof(float)); - db->datas[i].chunk->offset = 0; - } - - inport->offset += in_len * sizeof(float); - if (inport->offset >= size || flush_in) { - inio->status = SPA_STATUS_NEED_DATA; - spa_log_trace_fp(this->log, "%p: return input buffer of %zd samples", - this, size / sizeof(float)); - inport->offset = 0; - size = 0; - SPA_FLAG_SET(res, inio->status); - } - - outport->offset += out_len * sizeof(float); - if (outport->offset > 0 && (outport->offset >= maxsize || flush_out)) { - outio->status = SPA_STATUS_HAVE_DATA; - outio->buffer_id = dbuf->id; - spa_log_trace_fp(this->log, "%p: have output buffer of %zd samples", - this, outport->offset / sizeof(float)); - dequeue_buffer(this, dbuf); - outport->offset = 0; - this->drained = draining; - SPA_FLAG_SET(res, SPA_STATUS_HAVE_DATA); - } - if (out_len == 0 && this->peaks) { - outio->status = SPA_STATUS_HAVE_DATA; - outio->buffer_id = SPA_ID_INVALID; - SPA_FLAG_SET(res, SPA_STATUS_HAVE_DATA); - spa_log_trace_fp(this->log, "%p: no output buffer", this); - } - - update_rate_match(this, passthrough, (max - outport->offset) / sizeof(float), - (size - inport->offset) / sizeof(float)); - return res; -} - -static const struct spa_node_methods impl_node = { - SPA_VERSION_NODE_METHODS, - .add_listener = impl_node_add_listener, - .set_callbacks = impl_node_set_callbacks, - .enum_params = impl_node_enum_params, - .set_param = impl_node_set_param, - .set_io = impl_node_set_io, - .send_command = impl_node_send_command, - .add_port = impl_node_add_port, - .remove_port = impl_node_remove_port, - .port_enum_params = impl_node_port_enum_params, - .port_set_param = impl_node_port_set_param, - .port_use_buffers = impl_node_port_use_buffers, - .port_set_io = impl_node_port_set_io, - .port_reuse_buffer = impl_node_port_reuse_buffer, - .process = impl_node_process, -}; - -static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) -{ - struct impl *this; - - spa_return_val_if_fail(handle != NULL, -EINVAL); - spa_return_val_if_fail(interface != NULL, -EINVAL); - - this = (struct impl *) handle; - - if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) - *interface = &this->node; - else - return -ENOENT; - - return 0; -} - -static int impl_clear(struct spa_handle *handle) -{ - struct impl *this; - - spa_return_val_if_fail(handle != NULL, -EINVAL); - - this = (struct impl *) handle; - - if (this->resample.free) - resample_free(&this->resample); - return 0; -} - -static size_t -impl_get_size(const struct spa_handle_factory *factory, - const struct spa_dict *params) -{ - return sizeof(struct impl); -} - -static int -impl_init(const struct spa_handle_factory *factory, - struct spa_handle *handle, - const struct spa_dict *info, - const struct spa_support *support, - uint32_t n_support) -{ - struct impl *this; - struct port *port; - uint32_t i; - - spa_return_val_if_fail(factory != NULL, -EINVAL); - spa_return_val_if_fail(handle != NULL, -EINVAL); - - handle->get_interface = impl_get_interface; - handle->clear = impl_clear; - - 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 (this->cpu) - this->resample.cpu_flags = spa_cpu_get_flags(this->cpu); - - props_reset(&this->props); - - for (i = 0; info && i < info->n_items; i++) { - const char *k = info->items[i].key; - const char *s = info->items[i].value; - if (spa_streq(k, "clock.quantum-limit")) - spa_atou32(s, &this->quantum_limit, 0); - else if (spa_streq(k, "resample.peaks")) - this->peaks = spa_atob(s); - else if (spa_streq(k, "factory.mode")) { - if (spa_streq(s, "split")) - this->mode = MODE_SPLIT; - else if (spa_streq(s, "merge")) - this->mode = MODE_MERGE; - else - this->mode = MODE_CONVERT; - } else - resample_set_param(this, k, s); - - } - - spa_log_debug(this->log, "mode:%d", this->mode); - - this->node.iface = SPA_INTERFACE_INIT( - SPA_TYPE_INTERFACE_Node, - SPA_VERSION_NODE, - &impl_node, this); - - spa_hook_list_init(&this->hooks); - - this->rate_scale = 1.0; - - this->info = SPA_NODE_INFO_INIT(); - this->info_all = SPA_NODE_CHANGE_MASK_FLAGS; - this->info.max_input_ports = 1; - this->info.max_output_ports = 1; - this->info.flags = SPA_NODE_FLAG_RT; - - port = GET_OUT_PORT(this, 0); - port->direction = SPA_DIRECTION_OUTPUT; - port->id = 0; - port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | - SPA_PORT_CHANGE_MASK_PARAMS; - port->info = SPA_PORT_INFO_INIT(); - port->info.flags = 0; - port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); - port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); - port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); - port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - port->info.params = port->params; - port->info.n_params = 5; - spa_list_init(&port->queue); - - port = GET_IN_PORT(this, 0); - port->direction = SPA_DIRECTION_INPUT; - port->id = 0; - port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | - SPA_PORT_CHANGE_MASK_PARAMS; - port->info = SPA_PORT_INFO_INIT(); - port->info.flags = SPA_PORT_FLAG_DYNAMIC_DATA; - port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); - port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); - port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); - port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - port->info.params = port->params; - port->info.n_params = 5; - spa_list_init(&port->queue); - - return 0; -} - -static const struct spa_interface_info impl_interfaces[] = { - {SPA_TYPE_INTERFACE_Node,}, -}; - -static int -impl_enum_interface_info(const struct spa_handle_factory *factory, - const struct spa_interface_info **info, - uint32_t *index) -{ - spa_return_val_if_fail(factory != NULL, -EINVAL); - spa_return_val_if_fail(info != NULL, -EINVAL); - spa_return_val_if_fail(index != NULL, -EINVAL); - - switch (*index) { - case 0: - *info = &impl_interfaces[*index]; - break; - default: - return 0; - } - (*index)++; - return 1; -} - -const struct spa_handle_factory spa_resample_factory = { - SPA_VERSION_HANDLE_FACTORY, - SPA_NAME_AUDIO_PROCESS_RESAMPLE, - NULL, - impl_get_size, - impl_init, - impl_enum_interface_info, -}; diff --git a/spa/plugins/audioconvert/resample.h b/spa/plugins/audioconvert/resample.h index caf55977f88375e88322b2546fb9118a5dee4dab..0b9180ccb37cdb736d8b32a0edd81fa184d9f27e 100644 --- a/spa/plugins/audioconvert/resample.h +++ b/spa/plugins/audioconvert/resample.h @@ -31,11 +31,13 @@ #define RESAMPLE_DEFAULT_QUALITY 4 struct resample { + struct spa_log *log; uint32_t cpu_flags; + const char *func_name; + uint32_t channels; uint32_t i_rate; uint32_t o_rate; - struct spa_log *log; double rate; int quality; diff --git a/spa/plugins/audioconvert/spa-resample.c b/spa/plugins/audioconvert/spa-resample.c index 8d93697c5951b162e86c6b36046fdb2274a270da..4efb718decc8a86ab90f79020f47c2a91cc50e52 100644 --- a/spa/plugins/audioconvert/spa-resample.c +++ b/spa/plugins/audioconvert/spa-resample.c @@ -33,6 +33,7 @@ #include <spa/support/log-impl.h> #include <spa/debug/mem.h> #include <spa/utils/string.h> +#include <spa/utils/result.h> #include <sndfile.h> @@ -184,14 +185,14 @@ static int do_conversion(struct data *d) float out[MAX_SAMPLES * channels]; float ibuf[MAX_SAMPLES * channels]; float obuf[MAX_SAMPLES * channels]; - uint32_t in_len, out_len; - uint32_t pin_len, pout_len; + uint32_t in_len, out_len, queued; + uint32_t pin_len, pout_len; size_t read, written; const void *src[channels]; void *dst[channels]; uint32_t i; - int j, k, queued; - bool flushing = false; + int res, j, k; + uint32_t flushing = UINT32_MAX; spa_zero(r); r.cpu_flags = d->cpu_flags; @@ -200,7 +201,10 @@ static int do_conversion(struct data *d) r.i_rate = d->iinfo.samplerate; r.o_rate = d->oinfo.samplerate; r.quality = d->quality < 0 ? DEFAULT_QUALITY : d->quality; - resample_native_init(&r); + if ((res = resample_native_init(&r)) < 0) { + fprintf(stderr, "can't init converter: %s\n", spa_strerror(res)); + return res; + } for (j = 0; j < channels; j++) src[j] = &in[MAX_SAMPLES * j]; @@ -210,25 +214,29 @@ static int do_conversion(struct data *d) read = written = queued = 0; while (true) { pout_len = out_len = MAX_SAMPLES; - in_len = SPA_MIN(MAX_SAMPLES, resample_in_len(&r, out_len)) - queued; + in_len = SPA_MIN(MAX_SAMPLES, resample_in_len(&r, out_len)); + in_len -= SPA_MIN(queued, in_len); - pin_len = in_len = sf_readf_float(d->ifile, &ibuf[queued * channels], in_len); + if (in_len > 0) { + pin_len = in_len = sf_readf_float(d->ifile, &ibuf[queued * channels], in_len); - read += pin_len; + read += pin_len; - if (pin_len == 0) { - if (flushing) - break; + if (pin_len == 0) { + if (flushing == 0) + break; + if (flushing == UINT32_MAX) + flushing = resample_delay(&r); - flushing = true; - pin_len = in_len = resample_delay(&r); + pin_len = in_len = SPA_MIN(MAX_SAMPLES, flushing); + flushing -= in_len; - for (k = 0, i = 0; i < pin_len; i++) { - for (j = 0; j < channels; j++) - ibuf[k++] = 0.0; + for (k = 0, i = 0; i < pin_len; i++) { + for (j = 0; j < channels; j++) + ibuf[k++] = 0.0; + } } } - in_len += queued; pin_len = in_len; @@ -243,18 +251,20 @@ static int do_conversion(struct data *d) if (queued) memmove(ibuf, &ibuf[pin_len * channels], queued * channels * sizeof(float)); - for (k = 0, i = 0; i < pout_len; i++) { - for (j = 0; j < channels; j++) { - obuf[k++] = out[MAX_SAMPLES * j + i]; + if (pout_len > 0) { + for (k = 0, i = 0; i < pout_len; i++) { + for (j = 0; j < channels; j++) { + obuf[k++] = out[MAX_SAMPLES * j + i]; + } } - } - pout_len = sf_writef_float(d->ofile, obuf, pout_len); + pout_len = sf_writef_float(d->ofile, obuf, pout_len); - written += pout_len; + written += pout_len; + } } - if (d->verbose) { + if (d->verbose) fprintf(stdout, "read %zu samples, wrote %zu samples\n", read, written); - } + return 0; } diff --git a/spa/plugins/audioconvert/splitter.c b/spa/plugins/audioconvert/splitter.c deleted file mode 100644 index bcacb8c3cae0c0837ba4a2458cadf8f0bd4754cd..0000000000000000000000000000000000000000 --- a/spa/plugins/audioconvert/splitter.c +++ /dev/null @@ -1,1251 +0,0 @@ -/* Spa - * - * Copyright © 2018 Wim Taymans - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include <errno.h> -#include <string.h> -#include <stdio.h> -#include <limits.h> - -#include <spa/support/plugin.h> -#include <spa/support/cpu.h> -#include <spa/support/log.h> -#include <spa/utils/list.h> -#include <spa/utils/names.h> -#include <spa/utils/string.h> -#include <spa/node/node.h> -#include <spa/node/utils.h> -#include <spa/node/io.h> -#include <spa/param/audio/format-utils.h> -#include <spa/param/latency-utils.h> -#include <spa/param/param.h> -#include <spa/pod/filter.h> -#include <spa/debug/types.h> -#include <spa/debug/pod.h> - -#include "fmt-ops.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.splitter"); - -#define DEFAULT_RATE 48000 -#define DEFAULT_CHANNELS 2 -#define DEFAULT_MASK (1LL << SPA_AUDIO_CHANNEL_FL) | (1LL << SPA_AUDIO_CHANNEL_FR) - -#define MAX_ALIGN FMT_OPS_MAX_ALIGN -#define MAX_BUFFERS 32 -#define MAX_DATAS SPA_AUDIO_MAX_CHANNELS -#define MAX_PORTS SPA_AUDIO_MAX_CHANNELS - -struct buffer { - uint32_t id; -#define BUFFER_FLAG_QUEUED (1<<0) - uint32_t flags; - struct spa_list link; - struct spa_buffer *buf; - void *datas[MAX_DATAS]; -}; - -struct port { - uint32_t direction; - uint32_t id; - - struct spa_io_buffers *io; - - uint64_t info_all; - struct spa_port_info info; -#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 N_PORT_PARAMS 6 - struct spa_param_info params[N_PORT_PARAMS]; - - struct spa_dict info_props; - struct spa_dict_item info_props_items[2]; - char position[16]; - - bool have_format; - struct spa_audio_info format; - uint32_t blocks; - uint32_t stride; - - struct buffer buffers[MAX_BUFFERS]; - uint32_t n_buffers; - - struct spa_list queue; -}; - -struct impl { - struct spa_handle handle; - struct spa_node node; - - struct spa_log *log; - struct spa_cpu *cpu; - - uint32_t cpu_flags; - uint32_t max_align; - uint32_t quantum_limit; - - struct spa_io_position *io_position; - - uint64_t info_all; - struct spa_node_info info; -#define IDX_PortConfig 0 -#define N_NODE_PARAMS 1 - struct spa_param_info params[N_NODE_PARAMS]; - - struct spa_hook_list hooks; - - struct port in_ports[1]; - struct port *out_ports[MAX_PORTS]; - uint32_t port_count; - - struct spa_audio_info format; - unsigned int have_profile:1; - - struct convert conv; - unsigned int is_passthrough:1; - unsigned int started:1; - - struct spa_latency_info latency[2]; - - uint32_t src_remap[SPA_AUDIO_MAX_CHANNELS]; - uint32_t dst_remap[SPA_AUDIO_MAX_CHANNELS]; - - uint32_t empty_size; - float *empty; -}; - -#define CHECK_OUT_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) < this->port_count) -#define CHECK_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == 0) -#define CHECK_PORT(this,d,p) (CHECK_OUT_PORT(this,d,p) || CHECK_IN_PORT (this,d,p)) -#define GET_IN_PORT(this,p) (&this->in_ports[p]) -#define GET_OUT_PORT(this,p) (this->out_ports[p]) -#define GET_PORT(this,d,p) (d == SPA_DIRECTION_INPUT ? GET_IN_PORT(this,p) : GET_OUT_PORT(this,p)) - -static void emit_node_info(struct impl *this, bool full) -{ - uint64_t old = full ? this->info.change_mask : 0; - if (full) - this->info.change_mask = this->info_all; - if (this->info.change_mask) { - spa_node_emit_info(&this->hooks, &this->info); - this->info.change_mask = old; - } -} -static void emit_port_info(struct impl *this, struct port *port, bool full) -{ - uint64_t old = full ? port->info.change_mask : 0; - if (full) - port->info.change_mask = port->info_all; - if (port->info.change_mask) { - spa_node_emit_port_info(&this->hooks, - port->direction, port->id, &port->info); - port->info.change_mask = old; - } -} - -static int init_port(struct impl *this, enum spa_direction direction, - uint32_t port_id, uint32_t position) -{ - struct port *port = GET_OUT_PORT(this, port_id); - const char *name; - - if (port == NULL) { - port = calloc(1, sizeof(struct port)); - if (port == NULL) - return -errno; - this->out_ports[port_id] = port; - } - port->direction = direction; - port->id = port_id; - - name = spa_debug_type_find_short_name(spa_type_audio_channel, position); - snprintf(port->position, sizeof(port->position), "%s", name ? name : "UNK"); - - port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | - SPA_PORT_CHANGE_MASK_PROPS | - SPA_PORT_CHANGE_MASK_PARAMS; - - port->info = SPA_PORT_INFO_INIT(); - port->info.flags = SPA_PORT_FLAG_DYNAMIC_DATA; - port->info_props_items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit float mono audio"); - port->info_props_items[1] = SPA_DICT_ITEM_INIT(SPA_KEY_AUDIO_CHANNEL, port->position); - port->info_props = SPA_DICT_INIT(port->info_props_items, 2); - port->info.props = &port->info_props; - port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); - port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); - port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); - port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - port->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); - port->info.params = port->params; - port->info.n_params = N_PORT_PARAMS; - - spa_list_init(&port->queue); - - port->n_buffers = 0; - port->have_format = false; - port->format.media_type = SPA_MEDIA_TYPE_audio; - port->format.media_subtype = SPA_MEDIA_SUBTYPE_dsp; - port->format.info.dsp.format = SPA_AUDIO_FORMAT_DSP_F32; - - spa_log_debug(this->log, "%p: init port %d:%d position:%s", - this, direction, port_id, port->position); - emit_port_info(this, port, true); - - 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[4096]; - struct spa_result_node_params result; - uint32_t count = 0; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(num != 0, -EINVAL); - - result.id = id; - result.next = start; - next: - result.index = result.next++; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - switch (id) { - default: - return 0; - } - - if (spa_pod_filter(&b, &result.param, param, filter) < 0) - goto next; - - spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - - if (++count != num) - goto next; - - return 0; -} - -static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_log_debug(this->log, "%p: io %d %p/%zd", this, id, data, size); - - switch (id) { - case SPA_IO_Position: - this->io_position = data; - break; - default: - return -ENOENT; - } - return 0; -} - -static int int32_cmp(const void *v1, const void *v2) -{ - int32_t a1 = *(int32_t*)v1; - int32_t a2 = *(int32_t*)v2; - if (a1 == 0 && a2 != 0) - return 1; - if (a2 == 0 && a1 != 0) - return -1; - return a1 - a2; -} - -static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - struct impl *this = object; - int res; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - if (param == NULL) - return 0; - - switch (id) { - case SPA_PARAM_PortConfig: - { - struct port *port; - struct spa_audio_info info = { 0, }; - struct spa_pod *format; - enum spa_direction direction; - enum spa_param_port_config_mode mode; - uint32_t i; - - if (spa_pod_parse_object(param, - SPA_TYPE_OBJECT_ParamPortConfig, NULL, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(&direction), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(&mode), - SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(&format)) < 0) - return -EINVAL; - - if (!spa_pod_is_object_type(format, SPA_TYPE_OBJECT_Format)) - return -EINVAL; - - if (mode != SPA_PARAM_PORT_CONFIG_MODE_dsp) - return -ENOTSUP; - if (direction != SPA_DIRECTION_OUTPUT) - return -EINVAL; - - if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) - return res; - - if (info.media_type != SPA_MEDIA_TYPE_audio || - info.media_subtype != SPA_MEDIA_SUBTYPE_raw) - return -ENOTSUP; - - if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) - return -EINVAL; - - info.info.raw.rate = 0; - - if (this->have_profile && memcmp(&this->format, &info, sizeof(info)) == 0) - return 0; - - spa_log_debug(this->log, "%p: port config %d/%d", this, - info.info.raw.rate, info.info.raw.channels); - - for (i = 0; i < this->port_count; i++) - spa_node_emit_port_info(&this->hooks, - SPA_DIRECTION_OUTPUT, i, NULL); - - this->have_profile = true; - this->is_passthrough = true; - this->format = info; - - this->port_count = info.info.raw.channels; - for (i = 0; i < this->port_count; i++) { - init_port(this, SPA_DIRECTION_OUTPUT, i, - info.info.raw.position[i]); - } - port = GET_IN_PORT(this, 0); - qsort(info.info.raw.position, info.info.raw.channels, - sizeof(uint32_t), int32_cmp); - port->format = info; - port->have_format = true; - return 0; - } - default: - return -ENOENT; - } - return 0; -} - -static int impl_node_send_command(void *object, const struct spa_command *command) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(command != NULL, -EINVAL); - - switch (SPA_NODE_COMMAND_ID(command)) { - case SPA_NODE_COMMAND_Start: - this->started = true; - break; - case SPA_NODE_COMMAND_Suspend: - case SPA_NODE_COMMAND_Flush: - case SPA_NODE_COMMAND_Pause: - this->started = false; - break; - default: - return -ENOTSUP; - } - return 0; -} - -static int -impl_node_add_listener(void *object, - struct spa_hook *listener, - const struct spa_node_events *events, - void *data) -{ - struct impl *this = object; - struct spa_hook_list save; - uint32_t i; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_hook_list_isolate(&this->hooks, &save, listener, events, data); - - emit_node_info(this, true); - emit_port_info(this, GET_IN_PORT(this, 0), true); - for (i = 0; i < this->port_count; i++) - emit_port_info(this, GET_OUT_PORT(this, i), true); - - spa_hook_list_join(&this->hooks, &save); - - return 0; -} - -static int -impl_node_set_callbacks(void *object, - const struct spa_node_callbacks *callbacks, - void *user_data) -{ - return 0; -} - -static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, - const struct spa_dict *props) -{ - return -ENOTSUP; -} - -static int -impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) -{ - return -ENOTSUP; -} - -static int port_enum_formats(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t index, - struct spa_pod **param, - struct spa_pod_builder *builder) -{ - struct impl *this = object; - struct port *port = GET_PORT(this, direction, port_id); - - switch (index) { - case 0: - if (direction == SPA_DIRECTION_OUTPUT) { - *param = spa_format_audio_dsp_build(builder, - SPA_PARAM_EnumFormat, &port->format.info.dsp); - } else if (port->have_format) { - *param = spa_format_audio_raw_build(builder, - SPA_PARAM_EnumFormat, &port->format.info.raw); - } - else { - uint32_t rate = this->io_position ? - this->io_position->clock.rate.denom : DEFAULT_RATE; - - *param = spa_pod_builder_add_object(builder, - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(25, - SPA_AUDIO_FORMAT_F32P, - SPA_AUDIO_FORMAT_F32P, - SPA_AUDIO_FORMAT_F32, - SPA_AUDIO_FORMAT_F32_OE, - SPA_AUDIO_FORMAT_F64P, - SPA_AUDIO_FORMAT_F64, - SPA_AUDIO_FORMAT_F64_OE, - SPA_AUDIO_FORMAT_S32P, - SPA_AUDIO_FORMAT_S32, - SPA_AUDIO_FORMAT_S32_OE, - SPA_AUDIO_FORMAT_S24_32P, - SPA_AUDIO_FORMAT_S24_32, - SPA_AUDIO_FORMAT_S24_32_OE, - SPA_AUDIO_FORMAT_S24P, - SPA_AUDIO_FORMAT_S24, - SPA_AUDIO_FORMAT_S24_OE, - SPA_AUDIO_FORMAT_S16P, - SPA_AUDIO_FORMAT_S16, - SPA_AUDIO_FORMAT_S16_OE, - SPA_AUDIO_FORMAT_S8P, - SPA_AUDIO_FORMAT_S8, - SPA_AUDIO_FORMAT_U8P, - SPA_AUDIO_FORMAT_U8, - SPA_AUDIO_FORMAT_ULAW, - SPA_AUDIO_FORMAT_ALAW), - SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int( - rate, 1, INT32_MAX), - SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int( - DEFAULT_CHANNELS, 1, MAX_PORTS)); - } - break; - default: - return 0; - } - return 1; -} - -static int -impl_node_port_enum_params(void *object, int seq, - enum spa_direction direction, uint32_t port_id, - uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) -{ - struct impl *this = object; - struct port *port; - struct spa_pod *param; - struct spa_pod_builder b = { 0 }; - uint8_t buffer[2048]; - struct spa_result_node_params result; - uint32_t count = 0; - int res; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(num != 0, -EINVAL); - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - port = GET_PORT(this, direction, port_id); - - spa_log_debug(this->log, "%p: enum params port %d.%d %d %u", - this, direction, port_id, seq, id); - - result.id = id; - result.next = start; - next: - result.index = result.next++; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - switch (id) { - case SPA_PARAM_EnumFormat: - if ((res = port_enum_formats(this, direction, port_id, - result.index, ¶m, &b)) <= 0) - return res; - break; - case SPA_PARAM_Format: - if (!port->have_format) - return -EIO; - if (result.index > 0) - return 0; - - if (direction == SPA_DIRECTION_OUTPUT) - param = spa_format_audio_dsp_build(&b, id, &port->format.info.dsp); - else - param = spa_format_audio_raw_build(&b, id, &port->format.info.raw); - break; - case SPA_PARAM_Buffers: - if (!port->have_format) - return -EIO; - if (result.index > 0) - return 0; - - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamBuffers, id, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->blocks), - SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( - this->quantum_limit * port->stride, - 16 * port->stride, - INT32_MAX), - SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->stride)); - break; - - case SPA_PARAM_Meta: - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamMeta, id, - SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), - SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); - break; - default: - return 0; - } - break; - case SPA_PARAM_IO: - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamIO, id, - SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), - SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); - break; - default: - return 0; - } - break; - case SPA_PARAM_Latency: - switch (result.index) { - case 0: case 1: - param = spa_latency_build(&b, id, &this->latency[result.index]); - break; - default: - return 0; - } - break; - default: - return -ENOENT; - } - - if (spa_pod_filter(&b, &result.param, param, filter) < 0) - goto next; - - spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - - if (++count != num) - goto next; - - return 0; -} - -static int clear_buffers(struct impl *this, struct port *port) -{ - if (port->n_buffers > 0) { - spa_log_debug(this->log, "%p: clear buffers %p", this, port); - port->n_buffers = 0; - spa_list_init(&port->queue); - } - return 0; -} - -static int setup_convert(struct impl *this) -{ - struct port *inport; - struct spa_audio_info informat, outformat; - uint32_t i, j, src_fmt, dst_fmt; - int res; - - inport = GET_IN_PORT(this, 0); - - informat = inport->format; - outformat = this->format; - - src_fmt = informat.info.raw.format; - dst_fmt = SPA_AUDIO_FORMAT_DSP_F32; - - spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%dx%d", this, - spa_debug_type_find_name(spa_type_audio_format, src_fmt), - informat.info.raw.channels, - informat.info.raw.rate, - spa_debug_type_find_name(spa_type_audio_format, dst_fmt), - 1, - outformat.info.raw.rate, - outformat.info.raw.channels); - - for (i = 0; i < informat.info.raw.channels; i++) { - for (j = 0; j < outformat.info.raw.channels; j++) { - if (informat.info.raw.position[i] != - outformat.info.raw.position[j]) - continue; - this->src_remap[i] = j; - this->dst_remap[j] = i; - spa_log_debug(this->log, "%p: channel %d -> %d (%s -> %s)", this, - i, j, - spa_debug_type_find_short_name(spa_type_audio_channel, - informat.info.raw.position[i]), - spa_debug_type_find_short_name(spa_type_audio_channel, - outformat.info.raw.position[j])); - outformat.info.raw.position[j] = -1; - break; - } - } - - this->conv.src_fmt = src_fmt; - this->conv.dst_fmt = dst_fmt; - this->conv.n_channels = informat.info.raw.channels; - this->conv.cpu_flags = this->cpu_flags; - - if ((res = convert_init(&this->conv)) < 0) - return res; - - this->is_passthrough &= this->conv.is_passthrough; - - spa_log_debug(this->log, "%p: got converter features %08x:%08x passthrough:%d", this, - this->cpu_flags, this->conv.cpu_flags, this->is_passthrough); - - return 0; -} - -static int calc_width(struct spa_audio_info *info) -{ - switch (info->info.raw.format) { - case SPA_AUDIO_FORMAT_U8: - case SPA_AUDIO_FORMAT_U8P: - case SPA_AUDIO_FORMAT_S8: - case SPA_AUDIO_FORMAT_S8P: - case SPA_AUDIO_FORMAT_ULAW: - case SPA_AUDIO_FORMAT_ALAW: - return 1; - case SPA_AUDIO_FORMAT_S16P: - case SPA_AUDIO_FORMAT_S16: - case SPA_AUDIO_FORMAT_S16_OE: - return 2; - case SPA_AUDIO_FORMAT_S24P: - case SPA_AUDIO_FORMAT_S24: - case SPA_AUDIO_FORMAT_S24_OE: - return 3; - case SPA_AUDIO_FORMAT_F64P: - case SPA_AUDIO_FORMAT_F64: - case SPA_AUDIO_FORMAT_F64_OE: - return 8; - default: - return 4; - } -} - -static int port_set_latency(void *object, - enum spa_direction direction, - uint32_t port_id, - uint32_t flags, - const struct spa_pod *latency) -{ - struct impl *this = object; - struct port *port; - enum spa_direction other = SPA_DIRECTION_REVERSE(direction); - uint32_t i; - - spa_log_debug(this->log, "%p: set latency direction:%d", this, direction); - - if (latency == NULL) { - this->latency[other] = SPA_LATENCY_INFO(other); - } else { - struct spa_latency_info info; - if (spa_latency_parse(latency, &info) < 0 || - info.direction != other) - return -EINVAL; - this->latency[other] = info; - } - for (i = 0; i < this->port_count; i++) { - port = GET_OUT_PORT(this, i); - port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - port->params[IDX_Latency].flags ^= SPA_PARAM_INFO_SERIAL; - emit_port_info(this, port, false); - } - port = GET_IN_PORT(this, 0); - port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - port->params[IDX_Latency].flags ^= SPA_PARAM_INFO_SERIAL; - emit_port_info(this, port, false); - return 0; -} - -static int port_set_format(void *object, - enum spa_direction direction, - uint32_t port_id, - uint32_t flags, - const struct spa_pod *format) -{ - struct impl *this = object; - struct port *port; - int res; - - port = GET_PORT(this, direction, port_id); - - spa_log_debug(this->log, "%p: set format", this); - - if (format == NULL) { - if (port->have_format) { - if (direction == SPA_DIRECTION_INPUT) - port->have_format = this->have_profile; - else - port->have_format = false; - port->format.info.raw.rate = 0; - clear_buffers(this, port); - } - } else { - struct spa_audio_info info = { 0 }; - - if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) - return res; - - if (direction == SPA_DIRECTION_OUTPUT) { - if (info.media_type != SPA_MEDIA_TYPE_audio || - info.media_subtype != SPA_MEDIA_SUBTYPE_dsp) - return -EINVAL; - if (spa_format_audio_dsp_parse(format, &info.info.dsp) < 0) - return -EINVAL; - if (info.info.dsp.format != SPA_AUDIO_FORMAT_DSP_F32) - return -EINVAL; - - port->stride = 4; - port->blocks = 1; - } - else { - if (info.media_type != SPA_MEDIA_TYPE_audio || - info.media_subtype != SPA_MEDIA_SUBTYPE_raw) - return -EINVAL; - if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) - return -EINVAL; - if (info.info.raw.channels != this->port_count) - return -EINVAL; - - port->stride = calc_width(&info); - if (SPA_AUDIO_FORMAT_IS_PLANAR(info.info.raw.format)) { - port->blocks = info.info.raw.channels; - } else { - port->stride *= info.info.raw.channels; - port->blocks = 1; - } - } - - port->format = info; - - spa_log_debug(this->log, "%p: %d %d %d", this, port_id, port->stride, port->blocks); - - if (direction == SPA_DIRECTION_INPUT) - if ((res = setup_convert(this)) < 0) - return res; - - port->have_format = true; - } - port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - if (port->have_format) { - port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); - port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); - } else { - port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - } - emit_port_info(this, port, false); - - return 0; -} - - -static int -impl_node_port_set_param(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_log_debug(this->log, "%p: set param port %d.%d %u", - this, direction, port_id, id); - - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - switch (id) { - case SPA_PARAM_Latency: - return port_set_latency(this, direction, port_id, flags, param); - case SPA_PARAM_Format: - return port_set_format(this, direction, port_id, flags, param); - default: - return -ENOENT; - } -} - -static void queue_buffer(struct impl *this, struct port *port, uint32_t id) -{ - struct buffer *b = &port->buffers[id]; - - spa_log_trace_fp(this->log, "%p: queue buffer %d on port %d %d", - this, id, port->id, b->flags); - if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_QUEUED)) - return; - - spa_list_append(&port->queue, &b->link); - SPA_FLAG_SET(b->flags, BUFFER_FLAG_QUEUED); -} - -static struct buffer *dequeue_buffer(struct impl *this, struct port *port) -{ - struct buffer *b; - - if (spa_list_is_empty(&port->queue)) - return NULL; - - b = spa_list_first(&port->queue, struct buffer, link); - spa_list_remove(&b->link); - SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_QUEUED); - spa_log_trace_fp(this->log, "%p: dequeue buffer %d on port %d %u", - this, b->id, port->id, b->flags); - - return b; -} - -static int -impl_node_port_use_buffers(void *object, - enum spa_direction direction, - uint32_t port_id, - uint32_t flags, - struct spa_buffer **buffers, - uint32_t n_buffers) -{ - struct impl *this = object; - struct port *port; - uint32_t i, j, maxsize; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - port = GET_PORT(this, direction, port_id); - - spa_return_val_if_fail(port->have_format, -EIO); - - spa_log_debug(this->log, "%p: use buffers %d on port %d", this, n_buffers, port_id); - - clear_buffers(this, port); - - maxsize = 0; - for (i = 0; i < n_buffers; i++) { - struct buffer *b; - uint32_t n_datas = buffers[i]->n_datas; - struct spa_data *d = buffers[i]->datas; - - b = &port->buffers[i]; - b->id = i; - b->buf = buffers[i]; - b->flags = 0; - - for (j = 0; j < n_datas; j++) { - if (d[j].data == NULL) { - spa_log_error(this->log, "%p: invalid memory %d on buffer %d %d %p", - this, j, i, d[j].type, d[j].data); - return -EINVAL; - } - if (!SPA_IS_ALIGNED(d[j].data, this->max_align)) { - spa_log_warn(this->log, "%p: memory %d on buffer %d not aligned", - this, j, i); - } - b->datas[j] = d[j].data; - if (direction == SPA_DIRECTION_OUTPUT && - !SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_DYNAMIC)) - this->is_passthrough = false; - - spa_log_debug(this->log, "%p: buffer %d data %d flags:%08x %p", - this, i, j, d[j].flags, b->datas[j]); - - maxsize = SPA_MAX(maxsize, d[j].maxsize); - } - if (direction == SPA_DIRECTION_OUTPUT) - queue_buffer(this, port, i); - } - if (maxsize > this->empty_size) { - this->empty = realloc(this->empty, maxsize + MAX_ALIGN); - if (this->empty == NULL) - return -errno; - memset(this->empty, 0, maxsize + MAX_ALIGN); - this->empty_size = maxsize; - } - port->n_buffers = n_buffers; - - return 0; -} - -static int -impl_node_port_set_io(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t id, void *data, size_t size) -{ - struct impl *this = object; - struct port *port; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - port = GET_PORT(this, direction, port_id); - - switch (id) { - case SPA_IO_Buffers: - port->io = data; - break; - default: - return -ENOENT; - } - return 0; -} - -static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) -{ - struct impl *this = object; - struct port *port; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); - - port = GET_OUT_PORT(this, port_id); - queue_buffer(this, port, buffer_id); - - return 0; -} - -static int impl_node_process(void *object) -{ - struct impl *this = object; - struct port *inport; - struct spa_io_buffers *inio; - uint32_t i, maxsize, n_samples; - struct spa_data *sd, *dd; - struct buffer *sbuf, *dbuf; - uint32_t n_src_datas, n_dst_datas; - const void **src_datas; - void **dst_datas; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - inport = GET_IN_PORT(this, 0); - inio = inport->io; - spa_return_val_if_fail(inio != NULL, -EIO); - spa_return_val_if_fail(this->conv.process != NULL, -EIO); - - spa_log_trace_fp(this->log, "%p: status %p %d %d", this, - inio, inio->status, inio->buffer_id); - - if (SPA_UNLIKELY(inio->status != SPA_STATUS_HAVE_DATA)) - return inio->status; - - if (SPA_UNLIKELY(inio->buffer_id >= inport->n_buffers)) - return inio->status = -EINVAL; - - sbuf = &inport->buffers[inio->buffer_id]; - sd = sbuf->buf->datas; - - n_src_datas = sbuf->buf->n_datas; - src_datas = alloca(sizeof(void*) * n_src_datas); - - maxsize = INT_MAX; - for (i = 0; i < n_src_datas; i++) { - src_datas[i] = SPA_PTROFF(sd[i].data, - sd[i].chunk->offset, void); - maxsize = SPA_MIN(sd[i].chunk->size, maxsize); - } - n_samples = maxsize / inport->stride; - - n_dst_datas = this->port_count; - dst_datas = alloca(sizeof(void*) * n_dst_datas); - - for (i = 0; i < n_dst_datas; i++) { - struct port *outport = GET_OUT_PORT(this, i); - struct spa_io_buffers *outio; - uint32_t src_remap = this->src_remap[i]; - uint32_t dst_remap = this->dst_remap[i]; - - if (SPA_UNLIKELY((outio = outport->io) == NULL)) - goto empty; - - spa_log_trace_fp(this->log, "%p: %d %p %d %d %d", this, i, - outio, outio->status, outio->buffer_id, outport->stride); - - if (SPA_UNLIKELY(outio->status == SPA_STATUS_HAVE_DATA)) - goto empty; - - if (SPA_LIKELY(outio->buffer_id < outport->n_buffers)) { - queue_buffer(this, outport, outio->buffer_id); - outio->buffer_id = SPA_ID_INVALID; - } - - if (SPA_UNLIKELY((dbuf = dequeue_buffer(this, outport)) == NULL)) { - outio->status = -EPIPE; - empty: - spa_log_trace_fp(this->log, "%p: %d skip output", this, i); - dst_datas[dst_remap] = SPA_PTR_ALIGN(this->empty, MAX_ALIGN, void); - continue; - } - - dd = dbuf->buf->datas; - - maxsize = dd->maxsize; - n_samples = SPA_MIN(n_samples, maxsize / outport->stride); - - if (this->is_passthrough) - dd[0].data = (void *)src_datas[src_remap]; - else - dst_datas[dst_remap] = dd[0].data = dbuf->datas[0]; - - dd[0].chunk->offset = 0; - dd[0].chunk->size = n_samples * outport->stride; - - outio->status = SPA_STATUS_HAVE_DATA; - outio->buffer_id = dbuf->id; - } - - spa_log_trace_fp(this->log, "%p: n_src:%d n_dst:%d n_samples:%d max:%d stride:%d p:%d", this, - n_src_datas, n_dst_datas, n_samples, maxsize, inport->stride, - this->is_passthrough); - - if (!this->is_passthrough) - convert_process(&this->conv, dst_datas, src_datas, n_samples); - - inio->status = SPA_STATUS_NEED_DATA; - - return SPA_STATUS_NEED_DATA | SPA_STATUS_HAVE_DATA; -} - -static const struct spa_node_methods impl_node = { - SPA_VERSION_NODE_METHODS, - .add_listener = impl_node_add_listener, - .set_callbacks = impl_node_set_callbacks, - .enum_params = impl_node_enum_params, - .set_param = impl_node_set_param, - .set_io = impl_node_set_io, - .send_command = impl_node_send_command, - .add_port = impl_node_add_port, - .remove_port = impl_node_remove_port, - .port_enum_params = impl_node_port_enum_params, - .port_set_param = impl_node_port_set_param, - .port_use_buffers = impl_node_port_use_buffers, - .port_set_io = impl_node_port_set_io, - .port_reuse_buffer = impl_node_port_reuse_buffer, - .process = impl_node_process, -}; - -static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) -{ - struct impl *this; - - spa_return_val_if_fail(handle != NULL, -EINVAL); - spa_return_val_if_fail(interface != NULL, -EINVAL); - - this = (struct impl *) handle; - - if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) - *interface = &this->node; - else - return -ENOENT; - - return 0; -} - -static int impl_clear(struct spa_handle *handle) -{ - struct impl *this; - uint32_t i; - - spa_return_val_if_fail(handle != NULL, -EINVAL); - - this = (struct impl *) handle; - - for (i = 0; i < MAX_PORTS; i++) - free(this->out_ports[i]); - free(this->empty); - return 0; -} - -static size_t -impl_get_size(const struct spa_handle_factory *factory, - const struct spa_dict *params) -{ - return sizeof(struct impl); -} - -static int -impl_init(const struct spa_handle_factory *factory, - struct spa_handle *handle, - const struct spa_dict *info, - const struct spa_support *support, - uint32_t n_support) -{ - struct impl *this; - struct port *port; - uint32_t i; - - spa_return_val_if_fail(factory != NULL, -EINVAL); - spa_return_val_if_fail(handle != NULL, -EINVAL); - - handle->get_interface = impl_get_interface; - handle->clear = impl_clear; - - 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 (this->cpu) { - this->cpu_flags = spa_cpu_get_flags(this->cpu); - this->max_align = SPA_MIN(MAX_ALIGN, spa_cpu_get_max_align(this->cpu)); - } - - for (i = 0; info && i < info->n_items; i++) { - const char *k = info->items[i].key; - const char *s = info->items[i].value; - if (spa_streq(k, "clock.quantum-limit")) - spa_atou32(s, &this->quantum_limit, 0); - } - - spa_hook_list_init(&this->hooks); - - this->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); - this->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); - - this->node.iface = SPA_INTERFACE_INIT( - SPA_TYPE_INTERFACE_Node, - SPA_VERSION_NODE, - &impl_node, this); - this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | - SPA_NODE_CHANGE_MASK_PARAMS; - this->info = SPA_NODE_INFO_INIT(); - this->info.max_input_ports = 1; - this->info.max_output_ports = MAX_PORTS; - this->info.flags = SPA_NODE_FLAG_RT | - SPA_NODE_FLAG_OUT_PORT_CONFIG; - this->params[IDX_PortConfig] = SPA_PARAM_INFO(SPA_PARAM_PortConfig, SPA_PARAM_INFO_WRITE); - this->info.params = this->params; - this->info.n_params = N_NODE_PARAMS; - - port = GET_IN_PORT(this, 0); - port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | - SPA_PORT_CHANGE_MASK_PARAMS; - port->direction = SPA_DIRECTION_INPUT; - port->id = 0; - port->info = SPA_PORT_INFO_INIT(); - port->info.flags = SPA_PORT_FLAG_NO_REF | - SPA_PORT_FLAG_DYNAMIC_DATA; - port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); - port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); - port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); - port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - port->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); - port->info.params = port->params; - port->info.n_params = N_PORT_PARAMS; - - return 0; -} - -static const struct spa_interface_info impl_interfaces[] = { - {SPA_TYPE_INTERFACE_Node,}, -}; - -static int -impl_enum_interface_info(const struct spa_handle_factory *factory, - const struct spa_interface_info **info, - uint32_t *index) -{ - spa_return_val_if_fail(factory != NULL, -EINVAL); - spa_return_val_if_fail(info != NULL, -EINVAL); - spa_return_val_if_fail(index != NULL, -EINVAL); - - switch (*index) { - case 0: - *info = &impl_interfaces[*index]; - break; - default: - return 0; - } - (*index)++; - return 1; -} - -const struct spa_handle_factory spa_splitter_factory = { - SPA_VERSION_HANDLE_FACTORY, - SPA_NAME_AUDIO_PROCESS_DEINTERLEAVE, - NULL, - impl_get_size, - impl_init, - impl_enum_interface_info, -}; diff --git a/spa/plugins/audioconvert/test-audioconvert.c b/spa/plugins/audioconvert/test-audioconvert.c index 72ae39d1fa99cc5e3e622147b1c83925868e5e81..883803d06c30d1e562d4b60076984c404748325c 100644 --- a/spa/plugins/audioconvert/test-audioconvert.c +++ b/spa/plugins/audioconvert/test-audioconvert.c @@ -36,6 +36,7 @@ #include <spa/param/audio/format.h> #include <spa/param/audio/format-utils.h> #include <spa/node/node.h> +#include <spa/node/io.h> #include <spa/debug/mem.h> #include <spa/support/log-impl.h> @@ -43,7 +44,7 @@ SPA_LOG_IMPL(logger); extern const struct spa_handle_factory test_source_factory; -#define MAX_PORTS SPA_AUDIO_MAX_CHANNELS +#define MAX_PORTS (SPA_AUDIO_MAX_CHANNELS+1) struct context { struct spa_handle *convert_handle; @@ -71,6 +72,7 @@ static int setup_context(struct context *ctx) size_t size; int res; struct spa_support support[1]; + struct spa_dict_item items[2]; const struct spa_handle_factory *factory; void *iface; @@ -86,9 +88,11 @@ static int setup_context(struct context *ctx) ctx->convert_handle = calloc(1, size); spa_assert_se(ctx->convert_handle != NULL); + items[0] = SPA_DICT_ITEM_INIT("clock.quantum-limit", "8192"); + res = spa_handle_factory_init(factory, ctx->convert_handle, - NULL, + &SPA_DICT_INIT(items, 1), support, 1); spa_assert_se(res >= 0); @@ -510,6 +514,431 @@ static int test_set_in_format2(struct context *ctx) return 0; } +static int setup_direction(struct context *ctx, enum spa_direction direction, uint32_t mode, + struct spa_audio_info_raw *info) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param, *format; + int res; + uint32_t i; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + format = spa_format_audio_raw_build(&b, SPA_PARAM_Format, info); + + switch (mode) { + case SPA_PARAM_PORT_CONFIG_MODE_dsp: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(mode), + SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(format)); + break; + + case SPA_PARAM_PORT_CONFIG_MODE_convert: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(mode)); + break; + default: + return -EINVAL; + } + res = spa_node_set_param(ctx->convert_node, SPA_PARAM_PortConfig, 0, param); + spa_assert_se(res == 0); + + switch (mode) { + case SPA_PARAM_PORT_CONFIG_MODE_convert: + res = spa_node_port_set_param(ctx->convert_node, direction, 0, + SPA_PARAM_Format, 0, format); + spa_assert_se(res == 0); + break; + case SPA_PARAM_PORT_CONFIG_MODE_dsp: + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + format = spa_format_audio_dsp_build(&b, SPA_PARAM_Format, + &SPA_AUDIO_INFO_DSP_INIT( + .format = SPA_AUDIO_FORMAT_F32P)); + for (i = 0; i < info->channels; i++) { + res = spa_node_port_set_param(ctx->convert_node, direction, i, + SPA_PARAM_Format, 0, format); + spa_assert_se(res == 0); + } + break; + default: + return -EINVAL; + } + return 0; +} + +struct buffer { + struct spa_buffer buffer; + struct spa_data datas[MAX_PORTS]; + struct spa_chunk chunks[MAX_PORTS]; +}; + +struct data { + uint32_t mode; + struct spa_audio_info_raw info; + uint32_t ports; + uint32_t planes; + const void *data[MAX_PORTS]; + uint32_t size; +}; + +static int run_convert(struct context *ctx, struct data *in_data, + struct data *out_data) +{ + struct spa_command cmd; + int res; + uint32_t i, j, k; + struct buffer in_buffers[in_data->ports]; + struct buffer out_buffers[out_data->ports]; + struct spa_io_buffers in_io[in_data->ports]; + struct spa_io_buffers out_io[out_data->ports]; + + setup_direction(ctx, SPA_DIRECTION_INPUT, in_data->mode, &in_data->info); + setup_direction(ctx, SPA_DIRECTION_OUTPUT, out_data->mode, &out_data->info); + + cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start); + res = spa_node_send_command(ctx->convert_node, &cmd); + spa_assert_se(res == 0); + + for (i = 0, k = 0; i < in_data->ports; i++) { + struct buffer *b = &in_buffers[i]; + struct spa_buffer *buffers[1]; + spa_zero(*b); + b->buffer.datas = b->datas; + b->buffer.n_datas = in_data->planes; + + for (j = 0; j < in_data->planes; j++, k++) { + b->datas[j].type = SPA_DATA_MemPtr; + b->datas[j].flags = 0; + b->datas[j].fd = -1; + b->datas[j].mapoffset = 0; + b->datas[j].maxsize = in_data->size; + b->datas[j].data = (void *)in_data->data[k]; + b->datas[j].chunk = &b->chunks[j]; + b->datas[j].chunk->offset = 0; + b->datas[j].chunk->size = in_data->size; + b->datas[j].chunk->stride = 0; + } + buffers[0] = &b->buffer; + res = spa_node_port_use_buffers(ctx->convert_node, SPA_DIRECTION_INPUT, i, + 0, buffers, 1); + spa_assert_se(res == 0); + + in_io[i].status = SPA_STATUS_HAVE_DATA; + in_io[i].buffer_id = 0; + + res = spa_node_port_set_io(ctx->convert_node, SPA_DIRECTION_INPUT, i, + SPA_IO_Buffers, &in_io[i], sizeof(in_io[i])); + spa_assert_se(res == 0); + } + for (i = 0; i < out_data->ports; i++) { + struct buffer *b = &out_buffers[i]; + struct spa_buffer *buffers[1]; + spa_zero(*b); + b->buffer.datas = b->datas; + b->buffer.n_datas = out_data->planes; + + for (j = 0; j < out_data->planes; j++) { + b->datas[j].type = SPA_DATA_MemPtr; + b->datas[j].flags = 0; + b->datas[j].fd = -1; + b->datas[j].mapoffset = 0; + b->datas[j].maxsize = out_data->size; + b->datas[j].data = calloc(1, out_data->size); + b->datas[j].chunk = &b->chunks[j]; + b->datas[j].chunk->offset = 0; + b->datas[j].chunk->size = 0; + b->datas[j].chunk->stride = 0; + } + buffers[0] = &b->buffer; + res = spa_node_port_use_buffers(ctx->convert_node, + SPA_DIRECTION_OUTPUT, i, 0, buffers, 1); + spa_assert_se(res == 0); + + out_io[i].status = SPA_STATUS_NEED_DATA; + out_io[i].buffer_id = -1; + + res = spa_node_port_set_io(ctx->convert_node, SPA_DIRECTION_OUTPUT, i, + SPA_IO_Buffers, &out_io[i], sizeof(out_io[i])); + spa_assert_se(res == 0); + } + + res = spa_node_process(ctx->convert_node); + spa_assert_se(res == (SPA_STATUS_NEED_DATA | SPA_STATUS_HAVE_DATA)); + + for (i = 0, k = 0; i < out_data->ports; i++) { + struct buffer *b = &out_buffers[i]; + + spa_assert_se(out_io[i].status == SPA_STATUS_HAVE_DATA); + spa_assert_se(out_io[i].buffer_id == 0); + + for (j = 0; j < out_data->planes; j++, k++) { + spa_assert_se(b->datas[j].chunk->offset == 0); + spa_assert_se(b->datas[j].chunk->size == out_data->size); + + res = memcmp(b->datas[j].data, out_data->data[k], out_data->size); + if (res != 0) { + fprintf(stderr, "error plane %d\n", j); + spa_debug_mem(0, b->datas[j].data, out_data->size); + spa_debug_mem(0, out_data->data[j], out_data->size); + } + spa_assert_se(res == 0); + + free(b->datas[j].data); + } + } + cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Suspend); + res = spa_node_send_command(ctx->convert_node, &cmd); + spa_assert_se(res == 0); + + return 0; +} + +static const float data_f32p_1[] = { 0.1f, 0.1f, 0.1f, 0.1f }; +static const float data_f32p_2[] = { 0.2f, 0.2f, 0.2f, 0.2f }; +static const float data_f32p_3[] = { 0.3f, 0.3f, 0.3f, 0.3f }; +static const float data_f32p_4[] = { 0.4f, 0.4f, 0.4f, 0.4f }; +static const float data_f32p_5[] = { 0.5f, 0.5f, 0.5f, 0.5f }; +static const float data_f32p_6[] = { 0.6f, 0.6f, 0.6f, 0.6f }; +static const float data_f32p_7[] = { 0.7f, 0.7f, 0.7f, 0.7f }; +static const float data_f32p_8[] = { 0.8f, 0.8f, 0.8f, 0.8f }; + +static const float data_f32_5p1[] = { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, + 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, + 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, + 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f }; + +static const float data_f32_7p1_remapped[] = { 0.1f, 0.2f, 0.5f, 0.6f, 0.7f, 0.8f, 0.3f, 0.4f, + 0.1f, 0.2f, 0.5f, 0.6f, 0.7f, 0.8f, 0.3f, 0.4f, + 0.1f, 0.2f, 0.5f, 0.6f, 0.7f, 0.8f, 0.3f, 0.4f, + 0.1f, 0.2f, 0.5f, 0.6f, 0.7f, 0.8f, 0.3f, 0.4f }; +static const float data_f32_5p1_remapped[] = { 0.1f, 0.2f, 0.5f, 0.6f, 0.3f, 0.4f, + 0.1f, 0.2f, 0.5f, 0.6f, 0.3f, 0.4f, + 0.1f, 0.2f, 0.5f, 0.6f, 0.3f, 0.4f, + 0.1f, 0.2f, 0.5f, 0.6f, 0.3f, 0.4f }; + +struct data dsp_5p1 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 6, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + }), + .ports = 6, + .planes = 1, + .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_5, data_f32p_6, }, + .size = sizeof(float) * 4 +}; + +struct data dsp_5p1_remapped = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 6, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + }), + .ports = 6, + .planes = 1, + .data = { data_f32p_1, data_f32p_2, data_f32p_5, data_f32p_6, data_f32p_3, data_f32p_4, }, + .size = sizeof(float) * 4 +}; + +struct data dsp_7p1_remapped = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 8, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + SPA_AUDIO_CHANNEL_SL, + SPA_AUDIO_CHANNEL_SR, + }), + .ports = 8, + .planes = 1, + .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_7, data_f32p_8, data_f32p_5, data_f32p_6 }, + .size = sizeof(data_f32p_1) +}; + +struct data dsp_5p1_remapped_2 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 6, + .position = { + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FL, + }), + .ports = 6, + .planes = 1, + .data = { data_f32p_3, data_f32p_4, data_f32p_5, data_f32p_6, data_f32p_2, data_f32p_1, }, + .size = sizeof(float) * 4 +}; + +struct data conv_f32_48000_5p1 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 6, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + }), + .ports = 1, + .planes = 1, + .data = { data_f32_5p1 }, + .size = sizeof(data_f32_5p1) +}; + +struct data conv_f32_48000_5p1_remapped = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 6, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + }), + .ports = 1, + .planes = 1, + .data = { data_f32_5p1_remapped }, + .size = sizeof(data_f32_5p1_remapped) +}; + +struct data conv_f32p_48000_5p1 = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32P, + .rate = 48000, + .channels = 6, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + }), + .ports = 1, + .planes = 6, + .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_5, data_f32p_6, }, + .size = sizeof(float) * 4 +}; + +struct data conv_f32p_48000_5p1_remapped = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32P, + .rate = 48000, + .channels = 6, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + }), + .ports = 1, + .planes = 6, + .data = { data_f32p_1, data_f32p_2, data_f32p_5, data_f32p_6, data_f32p_3, data_f32p_4, }, + .size = sizeof(float) * 4 +}; + +struct data conv_f32_48000_7p1_remapped = { + .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, + .info = SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .rate = 48000, + .channels = 8, + .position = { + SPA_AUDIO_CHANNEL_FL, + SPA_AUDIO_CHANNEL_FR, + SPA_AUDIO_CHANNEL_SL, + SPA_AUDIO_CHANNEL_SR, + SPA_AUDIO_CHANNEL_RL, + SPA_AUDIO_CHANNEL_RR, + SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_LFE, + }), + .ports = 1, + .planes = 1, + .data = { data_f32_7p1_remapped, }, + .size = sizeof(data_f32_7p1_remapped) +}; + +static int test_convert_remap_dsp(struct context *ctx) +{ + run_convert(ctx, &dsp_5p1, &conv_f32_48000_5p1); + run_convert(ctx, &dsp_5p1, &conv_f32p_48000_5p1); + run_convert(ctx, &dsp_5p1, &conv_f32_48000_5p1_remapped); + run_convert(ctx, &dsp_5p1, &conv_f32p_48000_5p1_remapped); + run_convert(ctx, &dsp_5p1_remapped, &conv_f32_48000_5p1); + run_convert(ctx, &dsp_5p1_remapped, &conv_f32p_48000_5p1); + run_convert(ctx, &dsp_5p1_remapped, &conv_f32_48000_5p1_remapped); + run_convert(ctx, &dsp_5p1_remapped, &conv_f32p_48000_5p1_remapped); + run_convert(ctx, &dsp_5p1_remapped_2, &conv_f32_48000_5p1); + run_convert(ctx, &dsp_5p1_remapped_2, &conv_f32p_48000_5p1); + run_convert(ctx, &dsp_5p1_remapped_2, &conv_f32_48000_5p1_remapped); + run_convert(ctx, &dsp_5p1_remapped_2, &conv_f32p_48000_5p1_remapped); + return 0; +} + +static int test_convert_remap_conv(struct context *ctx) +{ + run_convert(ctx, &conv_f32_48000_5p1, &dsp_5p1); + run_convert(ctx, &conv_f32_48000_5p1, &dsp_5p1_remapped); + run_convert(ctx, &conv_f32_48000_5p1, &dsp_5p1_remapped_2); + run_convert(ctx, &conv_f32p_48000_5p1, &dsp_5p1); + run_convert(ctx, &conv_f32p_48000_5p1, &dsp_5p1_remapped); + run_convert(ctx, &conv_f32p_48000_5p1, &dsp_5p1_remapped_2); + run_convert(ctx, &conv_f32_48000_5p1_remapped, &dsp_5p1); + run_convert(ctx, &conv_f32_48000_5p1_remapped, &dsp_5p1_remapped); + run_convert(ctx, &conv_f32_48000_5p1_remapped, &dsp_5p1_remapped_2); + run_convert(ctx, &conv_f32p_48000_5p1_remapped, &dsp_5p1); + run_convert(ctx, &conv_f32p_48000_5p1_remapped, &dsp_5p1_remapped); + run_convert(ctx, &conv_f32_48000_7p1_remapped, &dsp_7p1_remapped); + run_convert(ctx, &conv_f32p_48000_5p1_remapped, &dsp_5p1_remapped_2); + return 0; +} + int main(int argc, char *argv[]) { struct context ctx; @@ -531,6 +960,9 @@ int main(int argc, char *argv[]) test_set_in_format2(&ctx); test_set_out_format(&ctx); + test_convert_remap_dsp(&ctx); + test_convert_remap_conv(&ctx); + clean_context(&ctx); return 0; diff --git a/spa/plugins/audioconvert/test-fmt-ops.c b/spa/plugins/audioconvert/test-fmt-ops.c index 48253b4a23659496943af431bdaf2b91ff8e59b4..8c8d4cdaca2579101231324f2fb6e7f06bac3a66 100644 --- a/spa/plugins/audioconvert/test-fmt-ops.c +++ b/spa/plugins/audioconvert/test-fmt-ops.c @@ -50,11 +50,11 @@ static void compare_mem(int i, int j, const void *m1, const void *m2, size_t siz { int res = memcmp(m1, m2, size); if (res != 0) { - fprintf(stderr, "%d %d:\n", i, j); + fprintf(stderr, "%d %d %zd:\n", i, j, size); spa_debug_mem(0, m1, size); spa_debug_mem(0, m2, size); } -// spa_assert_se(res == 0); + spa_assert_se(res == 0); } static void run_test(const char *name, @@ -81,19 +81,19 @@ static void run_test(const char *name, tp[0] = temp_in; switch(in_size) { case 1: - conv_interleave_8_c(&conv, tp, ip, N_SAMPLES); + conv_8d_to_8_c(&conv, tp, ip, N_SAMPLES); break; case 2: - conv_interleave_16_c(&conv, tp, ip, N_SAMPLES); + conv_16d_to_16_c(&conv, tp, ip, N_SAMPLES); break; case 3: - conv_interleave_24_c(&conv, tp, ip, N_SAMPLES); + conv_24d_to_24_c(&conv, tp, ip, N_SAMPLES); break; case 4: - conv_interleave_32_c(&conv, tp, ip, N_SAMPLES); + conv_32d_to_32_c(&conv, tp, ip, N_SAMPLES); break; case 8: - conv_interleave_64_c(&conv, tp, ip, N_SAMPLES); + conv_64d_to_64_c(&conv, tp, ip, N_SAMPLES); break; default: fprintf(stderr, "unknown size %zd\n", in_size); @@ -125,10 +125,42 @@ static void run_test(const char *name, } } +static void test_f32_s8(void) +{ + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/160.f, 1.0f/256.f, -1.0f/160.f, -1.0f/256.f }; + static const int8_t out[] = { 0, 127, -128, 64, 192, 127, -128, 1, 0, -1, 0 }; + + run_test("test_f32_s8", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_f32_to_s8_c); + run_test("test_f32d_s8", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f32d_to_s8_c); + run_test("test_f32_s8d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_f32_to_s8d_c); + run_test("test_f32d_s8d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, false, conv_f32d_to_s8d_c); +} + +static void test_s8_f32(void) +{ + static const int8_t in[] = { 0, 127, -128, 64, 192, }; + static const float out[] = { 0.0f, 0.9921875f, -1.0f, 0.5f, -0.5f, }; + + run_test("test_s8_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_s8_to_f32_c); + run_test("test_s8d_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_s8d_to_f32_c); + run_test("test_s8_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s8_to_f32d_c); + run_test("test_s8d_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, false, conv_s8d_to_f32d_c); +} + static void test_f32_u8(void) { - static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; - static const uint8_t out[] = { 128, 255, 0, 191, 64, 255, 0, }; + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/160.f, 1.0f/256.f, -1.0f/160.f, -1.0f/256.f }; + static const uint8_t out[] = { 128, 255, 0, 192, 64, 255, 0, 129, 128, 127, 128 }; run_test("test_f32_u8", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_f32_to_u8_c); @@ -157,8 +189,10 @@ static void test_u8_f32(void) static void test_f32_u16(void) { - static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; - static const uint16_t out[] = { 32767, 65535, 0, 49150, 16383, 65535, 0 }; + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/49152.f, 1.0f/65536.f, -1.0f/49152.f, -1.0f/65536.f }; + static const uint16_t out[] = { 32768, 65535, 0, 49152, 16384, 65535, 0, + 32769, 32768, 32767, 32768 }; run_test("test_f32_u16", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_f32_to_u16_c); @@ -168,8 +202,8 @@ static void test_f32_u16(void) static void test_u16_f32(void) { - static const uint16_t in[] = { 32767, 65535, 0, 49150, 16383, }; - static const float out[] = { 0.0f, 1.0f, -1.0f, 0.4999847412f, -0.4999847412f }; + static const uint16_t in[] = { 32768, 65535, 0, 49152, 16384, }; + static const float out[] = { 0.0f, 0.999969482422f, -1.0f, 0.5f, -0.5f }; run_test("test_u16_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_u16_to_f32d_c); @@ -179,8 +213,10 @@ static void test_u16_f32(void) static void test_f32_s16(void) { - static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; - static const int16_t out[] = { 0, 32767, -32767, 16383, -16383, 32767, -32767 }; + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/49152.f, 1.0f/65536.f, -1.0f/49152.f, -1.0f/65536.f }; + static const int16_t out[] = { 0, 32767, -32768, 16384, -16384, 32767, -32768, + 1, 0, -1, 0 }; run_test("test_f32_s16", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_f32_to_s16_c); @@ -192,16 +228,32 @@ static void test_f32_s16(void) false, false, conv_f32d_to_s16d_c); #if defined(HAVE_SSE2) if (cpu_flags & SPA_CPU_FLAG_SSE2) { + run_test("test_f32_s16_sse2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_f32_to_s16_sse2); run_test("test_f32d_s16_sse2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_f32d_to_s16_sse2); + run_test("test_f32d_s16d_sse2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, false, conv_f32d_to_s16d_sse2); + } +#endif +#if defined(HAVE_AVX2) + if (cpu_flags & SPA_CPU_FLAG_AVX2) { + run_test("test_f32d_s16_avx2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f32d_to_s16_avx2); + } +#endif +#if defined(HAVE_NEON) + if (cpu_flags & SPA_CPU_FLAG_NEON) { + run_test("test_f32d_s16_neon", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f32d_to_s16_neon); } #endif } static void test_s16_f32(void) { - static const int16_t in[] = { 0, 32767, -32767, 16383, -16383, }; - static const float out[] = { 0.0f, 1.0f, -1.0f, 0.4999847412f, -0.4999847412f }; + static const int16_t in[] = { 0, 32767, -32768, 16384, -16384, }; + static const float out[] = { 0.0f, 0.999969482422f, -1.0f, 0.5f, -0.5f }; run_test("test_s16_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s16_to_f32d_c); @@ -217,13 +269,27 @@ static void test_s16_f32(void) true, false, conv_s16_to_f32d_sse2); } #endif +#if defined(HAVE_AVX2) + if (cpu_flags & SPA_CPU_FLAG_AVX2) { + run_test("test_s16_f32d_avx2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s16_to_f32d_avx2); + } +#endif +#if defined(HAVE_NEON) + if (cpu_flags & SPA_CPU_FLAG_NEON) { + run_test("test_s16_f32d_neon", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s16_to_f32d_neon); + } +#endif } static void test_f32_u32(void) { - static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; - static const uint32_t out[] = { 0, 0x7fffff00, 0x80000100, 0x3fffff00, 0xc0000100, - 0x7fffff00, 0x80000100 }; + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; + static const uint32_t out[] = { 0x80000000, 0xffffff00, 0x0, 0xc0000000, 0x40000000, + 0xffffff00, 0x0, + 0x80000100, 0x80000000, 0x7fffff00, 0x80000000 }; run_test("test_f32_u32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_f32_to_u32_c); @@ -233,8 +299,8 @@ static void test_f32_u32(void) static void test_u32_f32(void) { - static const uint32_t in[] = { 0, 0x7fffff00, 0x80000100, 0x3fffff00, 0xc0000100 }; - static const float out[] = { 0.0f, 1.0f, -1.0f, 0.4999999404f, -0.4999999404f, }; + static const uint32_t in[] = { 0x80000000, 0xffffff00, 0x0, 0xc0000000, 0x40000000 }; + static const float out[] = { 0.0f, 0.999999880791f, -1.0f, 0.5f, -0.5f, }; run_test("test_u32_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_u32_to_f32d_c); @@ -244,9 +310,11 @@ static void test_u32_f32(void) static void test_f32_s32(void) { - static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; - static const int32_t out[] = { 0, 0x7fffff00, 0x80000100, 0x3fffff00, 0xc0000100, - 0x7fffff00, 0x80000100 }; + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; + static const int32_t out[] = { 0, 0x7fffff00, 0x80000000, 0x40000000, 0xc0000000, + 0x7fffff00, 0x80000000, + 0x00000100, 0x00000000, 0xffffff00, 0x00000000 }; run_test("test_f32_s32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_f32_to_s32_c); @@ -262,12 +330,18 @@ static void test_f32_s32(void) false, true, conv_f32d_to_s32_sse2); } #endif +#if defined(HAVE_AVX2) + if (cpu_flags & SPA_CPU_FLAG_AVX2) { + run_test("test_f32d_s32_avx2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f32d_to_s32_avx2); + } +#endif } static void test_s32_f32(void) { - static const int32_t in[] = { 0, 0x7fffff00, 0x80000100, 0x3fffff00, 0xc0000100 }; - static const float out[] = { 0.0f, 1.0f, -1.0f, 0.4999999404f, -0.4999999404f, }; + static const int32_t in[] = { 0, 0x7fffff00, 0x80000000, 0x40000000, 0xc0000000 }; + static const float out[] = { 0.0f, 0.999999880791, -1.0f, 0.5, -0.5, }; run_test("test_s32_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s32_to_f32d_c); @@ -283,18 +357,23 @@ static void test_s32_f32(void) true, false, conv_s32_to_f32d_sse2); } #endif +#if defined(HAVE_AVX2) + if (cpu_flags & SPA_CPU_FLAG_AVX2) { + run_test("test_s32_f32d_avx2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s32_to_f32d_avx2); + } +#endif } static void test_f32_u24(void) { - static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; -#if __BYTE_ORDER == __LITTLE_ENDIAN - static const uint8_t out[] = { 0x00, 0x00, 0x00, 0xff, 0xff, 0x7f, 0x01, 0x00, 0x80, - 0xff, 0xff, 0x3f, 0x01, 0x00, 0xc0, 0xff, 0xff, 0x7f, 0x01, 0x00, 0x80 }; -#else - static const uint8_t out[] = { 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0x80, 0x00, 0x01, - 0x3f, 0xff, 0xff, 0xc0, 0x00, 0x01, 0x7f, 0xff, 0xff, 0x80, 0x00, 0x01 }; -#endif + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; + static const uint24_t out[] = { U32_TO_U24(0x00800000), U32_TO_U24(0xffffff), + U32_TO_U24(0x000000), U32_TO_U24(0xc00000), U32_TO_U24(0x400000), + U32_TO_U24(0xffffff), U32_TO_U24(0x000000), + U32_TO_U24(0x800001), U32_TO_U24(0x800000), U32_TO_U24(0x7fffff), + U32_TO_U24(0x800000) }; run_test("test_f32_u24", in, sizeof(in[0]), out, 3, SPA_N_ELEMENTS(in), true, true, conv_f32_to_u24_c); @@ -304,14 +383,9 @@ static void test_f32_u24(void) static void test_u24_f32(void) { -#if __BYTE_ORDER == __LITTLE_ENDIAN - static const uint8_t in[] = { 0x00, 0x00, 0x00, 0xff, 0xff, 0x7f, 0x01, 0x00, 0x80, - 0xff, 0xff, 0x3f, 0x01, 0x00, 0xc0, }; -#else - static const uint8_t in[] = { 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0x80, 0x00, 0x01, - 0x3f, 0xff, 0xff, 0xc0, 0x00, 0x01, }; -#endif - static const float out[] = { 0.0f, 1.0f, -1.0f, 0.4999999404f, -0.4999999404f, }; + static const uint24_t in[] = { U32_TO_U24(0x00800000), U32_TO_U24(0xffffff), + U32_TO_U24(0x000000), U32_TO_U24(0xc00000), U32_TO_U24(0x400000) }; + static const float out[] = { 0.0f, 0.999999880791f, -1.0f, 0.5, -0.5, }; run_test("test_u24_f32d", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_u24_to_f32d_c); @@ -321,14 +395,13 @@ static void test_u24_f32(void) static void test_f32_s24(void) { - static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; -#if __BYTE_ORDER == __LITTLE_ENDIAN - static const uint8_t out[] = { 0x00, 0x00, 0x00, 0xff, 0xff, 0x7f, 0x01, 0x00, 0x80, - 0xff, 0xff, 0x3f, 0x01, 0x00, 0xc0, 0xff, 0xff, 0x7f, 0x01, 0x00, 0x80 }; -#else - static const uint8_t out[] = { 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0x80, 0x00, 0x01, - 0x3f, 0xff, 0xff, 0xc0, 0x00, 0x01, 0x7f, 0xff, 0xff, 0x80, 0x00, 0x01 }; -#endif + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; + static const int24_t out[] = { S32_TO_S24(0), S32_TO_S24(0x7fffff), + S32_TO_S24(0xff800000), S32_TO_S24(0x400000), S32_TO_S24(0xc00000), + S32_TO_S24(0x7fffff), S32_TO_S24(0xff800000), + S32_TO_S24(0x000001), S32_TO_S24(0x000000), S32_TO_S24(0xffffffff), + S32_TO_S24(0x000000) }; run_test("test_f32_s24", in, sizeof(in[0]), out, 3, SPA_N_ELEMENTS(in), true, true, conv_f32_to_s24_c); @@ -342,14 +415,9 @@ static void test_f32_s24(void) static void test_s24_f32(void) { -#if __BYTE_ORDER == __LITTLE_ENDIAN - static const uint8_t in[] = { 0x00, 0x00, 0x00, 0xff, 0xff, 0x7f, 0x01, 0x00, 0x80, - 0xff, 0xff, 0x3f, 0x01, 0x00, 0xc0, }; -#else - static const uint8_t in[] = { 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0x80, 0x00, 0x01, - 0x3f, 0xff, 0xff, 0xc0, 0x00, 0x01, }; -#endif - static const float out[] = { 0.0f, 1.0f, -1.0f, 0.4999999404f, -0.4999999404f, }; + static const int24_t in[] = { S32_TO_S24(0), S32_TO_S24(0x7fffff), + S32_TO_S24(0xff800000), S32_TO_S24(0x400000), S32_TO_S24(0xc00000) }; + static const float out[] = { 0.0f, 0.999999880791f, -1.0f, 0.5f, -0.5f, }; run_test("test_s24_f32d", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s24_to_f32d_c); @@ -377,13 +445,21 @@ static void test_s24_f32(void) true, false, conv_s24_to_f32d_sse41); } #endif +#if defined(HAVE_AVX2) + if (cpu_flags & SPA_CPU_FLAG_AVX2) { + run_test("test_s24_f32d_avx2", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s24_to_f32d_avx2); + } +#endif } static void test_f32_u24_32(void) { - static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; - static const uint32_t out[] = { 0, 0x7fffff, 0xff800001, 0x3fffff, 0xffc00001, - 0x7fffff, 0xff800001 }; + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; + static const uint32_t out[] = { 0x800000, 0xffffff, 0x0, 0xc00000, 0x400000, + 0xffffff, 0x000000, + 0x800001, 0x800000, 0x7fffff, 0x800000 }; run_test("test_f32_u24_32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_f32_to_u24_32_c); @@ -393,8 +469,8 @@ static void test_f32_u24_32(void) static void test_u24_32_f32(void) { - static const uint32_t in[] = { 0, 0x7fffff, 0xff800001, 0x3fffff, 0xffc00001 }; - static const float out[] = { 0.0f, 1.0f, -1.0f, 0.4999999404f, -0.4999999404f, }; + static const uint32_t in[] = { 0x800000, 0xffffff, 0x0, 0xc00000, 0x400000, 0x11000000 }; + static const float out[] = { 0.0f, 0.999999880791f, -1.0f, 0.5f, -0.5f, -1.0f }; run_test("test_u24_32_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_u24_32_to_f32d_c); @@ -404,9 +480,11 @@ static void test_u24_32_f32(void) static void test_f32_s24_32(void) { - static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; - static const int32_t out[] = { 0, 0x7fffff, 0xff800001, 0x3fffff, 0xffc00001, - 0x7fffff, 0xff800001 }; + static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, + 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; + static const int32_t out[] = { 0, 0x7fffff, 0xff800000, 0x400000, 0xffc00000, + 0x7fffff, 0xff800000, + 0x000001, 0x000000, 0xffffffff, 0x000000 }; run_test("test_f32_s24_32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_f32_to_s24_32_c); @@ -420,8 +498,8 @@ static void test_f32_s24_32(void) static void test_s24_32_f32(void) { - static const int32_t in[] = { 0, 0x7fffff, 0xff800001, 0x3fffff, 0xffc00001 }; - static const float out[] = { 0.0f, 1.0f, -1.0f, 0.4999999404f, -0.4999999404f, }; + static const int32_t in[] = { 0, 0x7fffff, 0xff800000, 0x400000, 0xffc00000, 0x66800000 }; + static const float out[] = { 0.0f, 0.999999880791f, -1.0f, 0.5f, -0.5f, -1.0f }; run_test("test_s24_32_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s24_32_to_f32d_c); @@ -435,8 +513,8 @@ static void test_s24_32_f32(void) static void test_f64_f32(void) { - static const double in[] = { 0.0, 1.0, -1.0, 0.4999999404, -0.4999999404, }; - static const float out[] = { 0.0f, 1.0f, -1.0f, 0.4999999404f, -0.4999999404f, }; + static const double in[] = { 0.0, 1.0, -1.0, 0.5, -0.5, }; + static const float out[] = { 0.0, 1.0, -1.0, 0.5, -0.5, }; run_test("test_f64_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_f64_to_f32d_c); @@ -451,7 +529,7 @@ static void test_f64_f32(void) static void test_f32_f64(void) { static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; - static const double out[] = { 0.0, 1.0, -1.0, 0.5, -0.5, 1.1, -1.1 }; + static const double out[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; run_test("test_f32_f64", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_f32_to_f64_c); @@ -462,11 +540,226 @@ static void test_f32_f64(void) run_test("test_f32d_f64d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, false, conv_f32d_to_f64d_c); } + +static void test_lossless_s8(void) +{ + int8_t i; + + fprintf(stderr, "test %s:\n", __func__); + for (i = S8_MIN; i < S8_MAX; i+=1) { + float v = S8_TO_F32(i); + int8_t t = F32_TO_S8(v); + spa_assert_se(i == t); + } +} + +static void test_lossless_u8(void) +{ + uint8_t i; + + fprintf(stderr, "test %s:\n", __func__); + for (i = U8_MIN; i < U8_MAX; i+=1) { + float v = U8_TO_F32(i); + uint8_t t = F32_TO_U8(v); + spa_assert_se(i == t); + } +} +static void test_lossless_s16(void) +{ + int16_t i; + + fprintf(stderr, "test %s:\n", __func__); + for (i = S16_MIN; i < S16_MAX; i+=3) { + float v = S16_TO_F32(i); + int16_t t = F32_TO_S16(v); + spa_assert_se(i == t); + } +} + +static void test_lossless_u16(void) +{ + uint32_t i; + + fprintf(stderr, "test %s:\n", __func__); + for (i = U16_MIN; i < U16_MAX; i+=3) { + float v = U16_TO_F32(i); + uint16_t t = F32_TO_U16(v); + spa_assert_se(i == t); + } +} + +static void test_lossless_s24(void) +{ + int32_t i; + + fprintf(stderr, "test %s:\n", __func__); + for (i = S24_MIN; i < S24_MAX; i+=13) { + float v = S24_TO_F32(s32_to_s24(i)); + int32_t t = s24_to_s32(F32_TO_S24(v)); + spa_assert_se(i == t); + } +} + +static void test_lossless_u24(void) +{ + uint32_t i; + + fprintf(stderr, "test %s:\n", __func__); + for (i = U24_MIN; i < U24_MAX; i+=11) { + float v = U24_TO_F32(u32_to_u24(i)); + uint32_t t = u24_to_u32(F32_TO_U24(v)); + spa_assert_se(i == t); + } +} + +static void test_lossless_s32(void) +{ + int32_t i; + + fprintf(stderr, "test %s:\n", __func__); + for (i = S32_MIN; i < S32_MAX; i+=255) { + float v = S32_TO_F32(i); + int32_t t = F32_TO_S32(v); + spa_assert_se(SPA_ABS(i - t) <= 256); + } +} + +static void test_lossless_u32(void) +{ + uint32_t i; + + fprintf(stderr, "test %s:\n", __func__); + for (i = U32_MIN; i < U32_MAX; i+=255) { + float v = U32_TO_F32(i); + uint32_t t = F32_TO_U32(v); + spa_assert_se(i > t ? (i - t) <= 256 : (t - i) <= 256); + } +} + +static void test_swaps(void) +{ + { + uint24_t v = U32_TO_U24(0x123456); + uint24_t t = U32_TO_U24(0x563412); + uint24_t s = bswap_u24(v); + spa_assert_se(memcmp(&s, &t, sizeof(t)) == 0); + } + { + int24_t v = S32_TO_S24(0xfffe1dc0); + int24_t t = S32_TO_S24(0xffc01dfe); + int24_t s = bswap_s24(v); + spa_assert_se(memcmp(&s, &t, sizeof(t)) == 0); + } + { + int24_t v = S32_TO_S24(0x123456); + int24_t t = S32_TO_S24(0x563412); + int24_t s = bswap_s24(v); + spa_assert_se(memcmp(&s, &t, sizeof(t)) == 0); + } +} + +static void run_test_noise(uint32_t fmt, uint32_t noise, uint32_t flags) +{ + struct convert conv; + const void *ip[N_CHANNELS]; + void *op[N_CHANNELS]; + uint32_t i, range; + bool all_zero; + + spa_zero(conv); + + conv.noise_bits = noise; + conv.src_fmt = SPA_AUDIO_FORMAT_F32P; + conv.dst_fmt = fmt; + conv.n_channels = 2; + conv.rate = 44100; + conv.cpu_flags = flags; + spa_assert_se(convert_init(&conv) == 0); + fprintf(stderr, "test noise %s:\n", conv.func_name); + + memset(samp_in, 0, sizeof(samp_in)); + for (i = 0; i < conv.n_channels; i++) { + ip[i] = samp_in; + op[i] = samp_out; + } + convert_process(&conv, op, ip, N_SAMPLES); + + range = 1 << conv.noise_bits; + + all_zero = true; + for (i = 0; i < conv.n_channels * N_SAMPLES; i++) { + switch (fmt) { + case SPA_AUDIO_FORMAT_S8: + { + int8_t *d = (int8_t *)samp_out; + if (d[i] != 0) + all_zero = false; + spa_assert_se(SPA_ABS(d[i] - 0) <= (int8_t)range); + break; + } + case SPA_AUDIO_FORMAT_U8: + { + uint8_t *d = (uint8_t *)samp_out; + if (d[i] != 0x80) + all_zero = false; + spa_assert_se((int8_t)SPA_ABS(d[i] - 0x80) <= (int8_t)(range<<1)); + break; + } + case SPA_AUDIO_FORMAT_S16: + { + int16_t *d = (int16_t *)samp_out; + if (d[i] != 0) + all_zero = false; + spa_assert_se(SPA_ABS(d[i] - 0) <= (int16_t)range); + break; + } + case SPA_AUDIO_FORMAT_S24: + { + int24_t *d = (int24_t *)samp_out; + int32_t t = s24_to_s32(d[i]); + if (t != 0) + all_zero = false; + spa_assert_se(SPA_ABS(t - 0) <= (int32_t)range); + break; + } + case SPA_AUDIO_FORMAT_S32: + { + int32_t *d = (int32_t *)samp_out; + if (d[i] != 0) + all_zero = false; + spa_assert_se(SPA_ABS(d[i] - 0) <= (int32_t)(range << 8)); + break; + } + default: + spa_assert_not_reached(); + break; + } + } + spa_assert_se(all_zero == false); + convert_free(&conv); +} + +static void test_noise(void) +{ + run_test_noise(SPA_AUDIO_FORMAT_S8, 1, 0); + run_test_noise(SPA_AUDIO_FORMAT_S8, 2, 0); + run_test_noise(SPA_AUDIO_FORMAT_U8, 1, 0); + run_test_noise(SPA_AUDIO_FORMAT_U8, 2, 0); + run_test_noise(SPA_AUDIO_FORMAT_S16, 1, 0); + run_test_noise(SPA_AUDIO_FORMAT_S16, 2, 0); + run_test_noise(SPA_AUDIO_FORMAT_S24, 1, 0); + run_test_noise(SPA_AUDIO_FORMAT_S24, 2, 0); + run_test_noise(SPA_AUDIO_FORMAT_S32, 1, 0); + run_test_noise(SPA_AUDIO_FORMAT_S32, 2, 0); +} + int main(int argc, char *argv[]) { cpu_flags = get_cpu_flags(); - printf("got get CPU flags %d\n", cpu_flags); + printf("got CPU flags %d\n", cpu_flags); + test_f32_s8(); + test_s8_f32(); test_f32_u8(); test_u8_f32(); test_f32_u16(); @@ -487,5 +780,19 @@ int main(int argc, char *argv[]) test_s24_32_f32(); test_f32_f64(); test_f64_f32(); + + test_lossless_s8(); + test_lossless_u8(); + test_lossless_s16(); + test_lossless_u16(); + test_lossless_s24(); + test_lossless_u24(); + test_lossless_s32(); + test_lossless_u32(); + + test_swaps(); + + test_noise(); + return 0; } diff --git a/spa/plugins/audioconvert/test-source.c b/spa/plugins/audioconvert/test-source.c index 3687ddf19c53973cc6567ac5170ccf6b4d471210..55a65c5d554ab88736174358a579d543efdb293d 100644 --- a/spa/plugins/audioconvert/test-source.c +++ b/spa/plugins/audioconvert/test-source.c @@ -749,9 +749,8 @@ static int impl_node_process(void *object) spa_return_val_if_fail(this != NULL, -EINVAL); port = GET_OUT_PORT(this, 0); - - io = port->io; - spa_return_val_if_fail(io != NULL, -EIO); + if ((io = port->io) == NULL) + return -EIO; spa_log_trace_fp(this->log, NAME " %p: status %d", this, io->status); diff --git a/spa/plugins/audioconvert/volume-ops.c b/spa/plugins/audioconvert/volume-ops.c index 32a0d22cb2bc34debdeef01a5181f1e225cc86c8..98887e46f5217b708bc2f76cf0b09fada094ba1d 100644 --- a/spa/plugins/audioconvert/volume-ops.c +++ b/spa/plugins/audioconvert/volume-ops.c @@ -36,16 +36,21 @@ typedef void (*volume_func_t) (struct volume *vol, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, float volume, uint32_t n_samples); +#define MAKE(func,...) \ + { func, #func , __VA_ARGS__ } + static const struct volume_info { volume_func_t process; + const char *name; uint32_t cpu_flags; } volume_table[] = { #if defined (HAVE_SSE) - { volume_f32_sse, SPA_CPU_FLAG_SSE }, + MAKE(volume_f32_sse, SPA_CPU_FLAG_SSE), #endif - { volume_f32_c, 0 }, + MAKE(volume_f32_c), }; +#undef MAKE #define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) @@ -73,6 +78,8 @@ int volume_init(struct volume *vol) if (info == NULL) return -ENOTSUP; + vol->cpu_flags = info->cpu_flags; + vol->func_name = info->name; vol->free = impl_volume_free; vol->process = info->process; return 0; diff --git a/spa/plugins/audioconvert/volume-ops.h b/spa/plugins/audioconvert/volume-ops.h index 4b3f763e44c60ce3049a8a5928bb4dac672155ce..0825712903a15167d93e771bd3e213f313e2bebe 100644 --- a/spa/plugins/audioconvert/volume-ops.h +++ b/spa/plugins/audioconvert/volume-ops.h @@ -33,6 +33,7 @@ struct volume { uint32_t cpu_flags; + const char *func_name; struct spa_log *log; diff --git a/spa/plugins/audiomixer/audiomixer.c b/spa/plugins/audiomixer/audiomixer.c index 1dbf51b28ae8c3d4fae0acda3b14394dd428ac71..5e682bba5d8c666fc46beacda2eb896a67f6904f 100644 --- a/spa/plugins/audiomixer/audiomixer.c +++ b/spa/plugins/audiomixer/audiomixer.c @@ -730,8 +730,8 @@ static int impl_node_process(void *object) spa_return_val_if_fail(this != NULL, -EINVAL); outport = GET_OUT_PORT(this, 0); - outio = outport->io; - spa_return_val_if_fail(outio != NULL, -EIO); + if ((outio = outport->io) == NULL) + return -EIO; spa_log_trace_fp(this->log, "%p: status %p %d %d", this, outio, outio->status, outio->buffer_id); @@ -755,6 +755,8 @@ static int impl_node_process(void *object) struct port *inport = GET_IN_PORT(this, i); struct spa_io_buffers *inio = NULL; struct buffer *inb; + struct spa_data *bd; + uint32_t size, offs; if (SPA_UNLIKELY(!PORT_VALID(inport) || (inio = inport->io) == NULL || @@ -770,13 +772,20 @@ static int impl_node_process(void *object) } inb = &inport->buffers[inio->buffer_id]; - maxsize = SPA_MIN(inb->buffer->datas[0].chunk->size, maxsize); + bd = &inb->buffer->datas[0]; - spa_log_trace_fp(this->log, "%p: mix input %d %p->%p %d %d %d", this, - i, inio, outio, inio->status, inio->buffer_id, maxsize); + offs = SPA_MIN(bd->chunk->offset, bd->maxsize); + size = SPA_MIN(bd->maxsize - offs, bd->chunk->size); + maxsize = SPA_MIN(size, maxsize); - datas[n_buffers] = inb->buffer->datas[0].data; - buffers[n_buffers++] = inb; + spa_log_trace_fp(this->log, "%p: mix input %d %p->%p %d %d %d:%d", this, + i, inio, outio, inio->status, inio->buffer_id, + offs, size); + + if (!SPA_FLAG_IS_SET(bd->chunk->flags, SPA_CHUNK_FLAG_EMPTY)) { + datas[n_buffers] = SPA_PTROFF(bd->data, offs, void); + buffers[n_buffers++] = inb; + } inio->status = SPA_STATUS_NEED_DATA; } @@ -788,8 +797,7 @@ static int impl_node_process(void *object) if (n_buffers == 1) { *outb->buffer = *buffers[0]->buffer; - } - else { + } else { struct spa_data *d = outb->buf.datas; *outb->buffer = outb->buf; @@ -799,6 +807,7 @@ static int impl_node_process(void *object) d[0].chunk->offset = 0; d[0].chunk->size = maxsize; d[0].chunk->stride = this->stride; + SPA_FLAG_UPDATE(d[0].chunk->flags, SPA_CHUNK_FLAG_EMPTY, n_buffers == 0); mix_ops_process(&this->ops, d[0].data, datas, n_buffers, maxsize / this->stride); diff --git a/spa/plugins/audiomixer/benchmark-mix-ops.c b/spa/plugins/audiomixer/benchmark-mix-ops.c new file mode 100644 index 0000000000000000000000000000000000000000..e698417b616d171ecca9675a65ecfcf5aa8b2882 --- /dev/null +++ b/spa/plugins/audiomixer/benchmark-mix-ops.c @@ -0,0 +1,222 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> + +#include "test-helper.h" +#include "mix-ops.h" + +static uint32_t cpu_flags; + +typedef void (*mix_func_t) (struct mix_ops *ops, void * SPA_RESTRICT dst, + const void * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples); +struct stats { + uint32_t n_samples; + uint32_t n_src; + uint64_t perf; + const char *name; + const char *impl; +}; + +#define MAX_SAMPLES 4096 +#define MAX_SRC 11 + +#define MAX_COUNT 100 + +static uint8_t samp_in[MAX_SAMPLES * MAX_SRC * 8]; +static uint8_t samp_out[MAX_SAMPLES * 8]; + +static const int sample_sizes[] = { 0, 1, 128, 513, 4096 }; +static const int src_counts[] = { 1, 2, 4, 6, 8, 11 }; + +#define MAX_RESULTS SPA_N_ELEMENTS(sample_sizes) * SPA_N_ELEMENTS(src_counts) * 70 + +static uint32_t n_results = 0; +static struct stats results[MAX_RESULTS]; + +static void run_test1(const char *name, const char *impl, mix_func_t func, int n_src, int n_samples) +{ + int i, j; + const void *ip[n_src]; + void *op; + struct timespec ts; + uint64_t count, t1, t2; + struct mix_ops mix; + + mix.n_channels = 1; + + for (j = 0; j < n_src; j++) + ip[j] = SPA_PTR_ALIGN(&samp_in[j * n_samples * 4], 32, void); + op = SPA_PTR_ALIGN(samp_out, 32, void); + + clock_gettime(CLOCK_MONOTONIC, &ts); + t1 = SPA_TIMESPEC_TO_NSEC(&ts); + + count = 0; + for (i = 0; i < MAX_COUNT; i++) { + func(&mix, op, ip, n_src, n_samples); + count++; + } + clock_gettime(CLOCK_MONOTONIC, &ts); + t2 = SPA_TIMESPEC_TO_NSEC(&ts); + + spa_assert(n_results < MAX_RESULTS); + + results[n_results++] = (struct stats) { + .n_samples = n_samples, + .n_src = n_src, + .perf = count * (uint64_t)SPA_NSEC_PER_SEC / (t2 - t1), + .name = name, + .impl = impl + }; +} + +static void run_test(const char *name, const char *impl, mix_func_t func) +{ + size_t i, j; + + for (i = 0; i < SPA_N_ELEMENTS(sample_sizes); i++) { + for (j = 0; j < SPA_N_ELEMENTS(src_counts); j++) { + run_test1(name, impl, func, src_counts[j], + (sample_sizes[i] + (src_counts[j] -1)) / src_counts[j]); + } + } +} + +static void test_s8(void) +{ + run_test("test_s8", "c", mix_s8_c); +} +static void test_u8(void) +{ + run_test("test_u8", "c", mix_u8_c); +} + +static void test_s16(void) +{ + run_test("test_s16", "c", mix_s16_c); +} +static void test_u16(void) +{ + run_test("test_u8", "c", mix_u16_c); +} + +static void test_s24(void) +{ + run_test("test_s24", "c", mix_s24_c); +} +static void test_u24(void) +{ + run_test("test_u24", "c", mix_u24_c); +} +static void test_s24_32(void) +{ + run_test("test_s24_32", "c", mix_s24_32_c); +} +static void test_u24_32(void) +{ + run_test("test_u24_32", "c", mix_u24_32_c); +} + +static void test_s32(void) +{ + run_test("test_s32", "c", mix_s32_c); +} +static void test_u32(void) +{ + run_test("test_u32", "c", mix_u32_c); +} + +static void test_f32(void) +{ + run_test("test_f32", "c", mix_f32_c); +#if defined (HAVE_SSE) + if (cpu_flags & SPA_CPU_FLAG_SSE) { + run_test("test_f32", "sse", mix_f32_sse); + } +#endif +#if defined (HAVE_AVX) + if (cpu_flags & SPA_CPU_FLAG_AVX) { + run_test("test_f32", "avx", mix_f32_avx); + } +#endif +} + +static void test_f64(void) +{ + run_test("test_f64", "c", mix_f64_c); +#if defined (HAVE_SSE2) + if (cpu_flags & SPA_CPU_FLAG_SSE2) { + run_test("test_f64", "sse2", mix_f64_sse2); + } +#endif +} + +static int compare_func(const void *_a, const void *_b) +{ + const struct stats *a = _a, *b = _b; + int diff; + if ((diff = strcmp(a->name, b->name)) != 0) return diff; + if ((diff = a->n_samples - b->n_samples) != 0) return diff; + if ((diff = a->n_src - b->n_src) != 0) return diff; + if ((diff = b->perf - a->perf) != 0) return diff; + return 0; +} + +int main(int argc, char *argv[]) +{ + uint32_t i; + + cpu_flags = get_cpu_flags(); + printf("got get CPU flags %d\n", cpu_flags); + + test_s8(); + test_u8(); + test_s16(); + test_u16(); + test_s24(); + test_u24(); + test_s32(); + test_u32(); + test_s24_32(); + test_u24_32(); + test_f32(); + test_f64(); + + qsort(results, n_results, sizeof(struct stats), compare_func); + + for (i = 0; i < n_results; i++) { + struct stats *s = &results[i]; + fprintf(stderr, "%-12."PRIu64" \t%-32.32s %s \t samples %d, src %d\n", + s->perf, s->name, s->impl, s->n_samples, s->n_src); + } + return 0; +} diff --git a/spa/plugins/audiomixer/meson.build b/spa/plugins/audiomixer/meson.build index 9e1d12d599334655eee10c5c133481a2953f8db1..5a4a1fbfe2fe9c18a701a5479fd89fb6394d363b 100644 --- a/spa/plugins/audiomixer/meson.build +++ b/spa/plugins/audiomixer/meson.build @@ -1,6 +1,5 @@ audiomixer_sources = [ 'audiomixer.c', - 'mix-ops.c', 'mixer-dsp.c', 'plugin.c' ] @@ -47,11 +46,81 @@ if have_avx and have_fma simd_dependencies += audiomixer_avx endif -audiomixerlib = shared_library('spa-audiomixer', +audiomixer_lib = static_library('audiomixer', + ['mix-ops.c' ], + c_args : [ simd_cargs, '-O3'], + link_with : simd_dependencies, + include_directories : [configinc], + dependencies : [ spa_dep ], + install : false + ) +audiomixer_dep = declare_dependency(link_with: audiomixer_lib) + +spa_audiomixer_lib = shared_library('spa-audiomixer', audiomixer_sources, c_args : simd_cargs, link_with : simd_dependencies, - dependencies : [ spa_dep, mathlib ], + dependencies : [ spa_dep, mathlib, audiomixer_dep ], install : true, install_dir : spa_plugindir / 'audiomixer' ) +spa_audiomixer_dep = declare_dependency(link_with: spa_audiomixer_lib) + +test_apps = [ + 'test-mix-ops', + ] + +foreach a : test_apps + test(a, + executable(a, a + '.c', + dependencies : [ spa_dep, dl_lib, pthread_lib, mathlib, audiomixer_dep ], + include_directories : [ configinc ], + link_with : [ test_lib ], + install_rpath : spa_plugindir / 'audiomixer', + c_args : [ simd_cargs ], + install : installed_tests_enabled, + install_dir : installed_tests_execdir / 'audiomixer'), + env : [ + 'SPA_PLUGIN_DIR=@0@'.format(spa_dep.get_variable('plugindir')), + ]) + + if installed_tests_enabled + test_conf = configuration_data() + test_conf.set('exec', installed_tests_execdir / 'audiomixer' / a) + configure_file( + input: installed_tests_template, + output: a + '.test', + install_dir: installed_tests_metadir / 'audiomixer', + configuration: test_conf + ) + endif +endforeach + +benchmark_apps = [ + 'benchmark-mix-ops', + ] + +foreach a : benchmark_apps + benchmark(a, + executable(a, a + '.c', + dependencies : [ spa_dep, dl_lib, pthread_lib, mathlib, audiomixer_dep ], + include_directories : [ configinc ], + c_args : [ simd_cargs ], + install_rpath : spa_plugindir / 'audiomixer', + install : installed_tests_enabled, + install_dir : installed_tests_execdir / 'audiomixer'), + env : [ + 'SPA_PLUGIN_DIR=@0@'.format(spa_dep.get_variable('plugindir')), + ]) + + if installed_tests_enabled + test_conf = configuration_data() + test_conf.set('exec', installed_tests_execdir / 'audiomixer' / a) + configure_file( + input: installed_tests_template, + output: a + '.test', + install_dir: installed_tests_metadir / 'audiomixer', + configuration: test_conf + ) + endif +endforeach diff --git a/spa/plugins/audiomixer/mix-ops-avx.c b/spa/plugins/audiomixer/mix-ops-avx.c index b3884246334b54ff216588dc1fe36862a6ee5867..a5e3b5b11763874aa123a3dd05e22c82c9ca4691 100644 --- a/spa/plugins/audiomixer/mix-ops-avx.c +++ b/spa/plugins/audiomixer/mix-ops-avx.c @@ -86,50 +86,59 @@ static inline void mix_4(float * dst, static inline void mix_2(float * dst, const float * SPA_RESTRICT src, uint32_t n_samples) { - uint32_t n, unrolled; - - if (SPA_IS_ALIGNED(src, 32) && - SPA_IS_ALIGNED(dst, 32)) - unrolled = n_samples & ~15; - else - unrolled = 0; - - for (n = 0; n < unrolled; n += 16) { - __m256 in1[2], in2[2]; - - in1[0] = _mm256_load_ps(&dst[n + 0]); - in1[1] = _mm256_load_ps(&dst[n + 8]); - in2[0] = _mm256_load_ps(&src[n + 0]); - in2[1] = _mm256_load_ps(&src[n + 8]); - - in1[0] = _mm256_add_ps(in1[0], in2[0]); - in1[1] = _mm256_add_ps(in1[1], in2[1]); - - _mm256_store_ps(&dst[n + 0], in1[0]); - _mm256_store_ps(&dst[n + 8], in1[1]); - } - for (; n < n_samples; n++) { - __m128 in1[1], in2[1]; - in1[0] = _mm_load_ss(&dst[n]), - in2[0] = _mm_load_ss(&src[n]), - in1[0] = _mm_add_ss(in1[0], in2[0]); - _mm_store_ss(&dst[n], in1[0]); - } } void mix_f32_avx(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples) { - uint32_t i; + n_samples *= ops->n_channels; if (n_src == 0) memset(dst, 0, n_samples * ops->n_channels * sizeof(float)); - else if (dst != src[0]) - spa_memcpy(dst, src[0], n_samples * ops->n_channels * sizeof(float)); - - for (i = 1; i + 2 < n_src; i += 3) - mix_4(dst, src[i], src[i + 1], src[i + 2], n_samples); - for (; i < n_src; i++) - mix_2(dst, src[i], n_samples * ops->n_channels); + else if (n_src == 1) { + if (dst != src[0]) + spa_memcpy(dst, src[0], n_samples * sizeof(float)); + } else { + uint32_t i, n, unrolled; + const float **s = (const float **)src; + float *d = dst; + + if (SPA_LIKELY(SPA_IS_ALIGNED(dst, 32))) { + unrolled = n_samples & ~31; + for (i = 0; i < n_src; i++) { + if (SPA_UNLIKELY(!SPA_IS_ALIGNED(src[i], 32))) { + unrolled = 0; + break; + } + } + } else + unrolled = 0; + + for (n = 0; n < unrolled; n += 32) { + __m256 in[4]; + + in[0] = _mm256_load_ps(&s[0][n + 0]); + in[1] = _mm256_load_ps(&s[0][n + 8]); + in[2] = _mm256_load_ps(&s[0][n + 16]); + in[3] = _mm256_load_ps(&s[0][n + 24]); + for (i = 1; i < n_src; i++) { + in[0] = _mm256_add_ps(in[0], _mm256_load_ps(&s[i][n + 0])); + in[1] = _mm256_add_ps(in[1], _mm256_load_ps(&s[i][n + 8])); + in[2] = _mm256_add_ps(in[2], _mm256_load_ps(&s[i][n + 16])); + in[3] = _mm256_add_ps(in[3], _mm256_load_ps(&s[i][n + 24])); + } + _mm256_store_ps(&d[n + 0], in[0]); + _mm256_store_ps(&d[n + 8], in[1]); + _mm256_store_ps(&d[n + 16], in[2]); + _mm256_store_ps(&d[n + 24], in[3]); + } + for (; n < n_samples; n++) { + __m128 in[1]; + in[0] = _mm_load_ss(&s[0][n]); + for (i = 1; i < n_src; i++) + in[0] = _mm_add_ss(in[0], _mm_load_ss(&s[i][n])); + _mm_store_ss(&d[n], in[0]); + } + } } diff --git a/spa/plugins/audiomixer/mix-ops-c.c b/spa/plugins/audiomixer/mix-ops-c.c index e2d82e8c1d679a78c4133b83e5c0379fda0653a3..2f79cd80f748f1c533a413c9f710bdf008bab9b8 100644 --- a/spa/plugins/audiomixer/mix-ops-c.c +++ b/spa/plugins/audiomixer/mix-ops-c.c @@ -30,236 +30,39 @@ #include "mix-ops.h" -void -mix_s8_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - int8_t *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(int8_t)); - else if (dst != src[0]) - spa_memcpy(dst, src[0], n_samples * ops->n_channels * sizeof(int8_t)); - - for (i = 1; i < n_src; i++) { - const int8_t *s = src[i]; - for (n = 0; n < n_samples * ops->n_channels; n++) - d[n] = S8_MIX(d[n], s[n]); - } -} - -void -mix_u8_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - uint8_t *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(uint8_t)); - else if (dst != src[0]) - spa_memcpy(dst, src[0], n_samples * ops->n_channels * sizeof(uint8_t)); - - for (i = 1; i < n_src; i++) { - const uint8_t *s = src[i]; - for (n = 0; n < n_samples * ops->n_channels; n++) - d[n] = U8_MIX(d[n], s[n]); - } +#define MAKE_FUNC(name,type,atype,accum,clamp,zero) \ +void mix_ ##name## _c(struct mix_ops *ops, \ + void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], \ + uint32_t n_src, uint32_t n_samples) \ +{ \ + uint32_t i, n; \ + type *d = dst; \ + const type **s = (const type **)src; \ + n_samples *= ops->n_channels; \ + if (n_src == 0 && zero) \ + memset(dst, 0, n_samples * sizeof(type)); \ + else if (n_src == 1) { \ + if (dst != src[0]) \ + spa_memcpy(dst, src[0], n_samples * sizeof(type)); \ + } else { \ + for (n = 0; n < n_samples; n++) { \ + atype ac = 0; \ + for (i = 0; i < n_src; i++) \ + ac = accum (ac, s[i][n]); \ + d[n] = clamp (ac); \ + } \ + } \ } -void -mix_s16_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - int16_t *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(int16_t)); - else if (dst != src[0]) - spa_memcpy(dst, src[0], n_samples * ops->n_channels * sizeof(int16_t)); - - for (i = 1; i < n_src; i++) { - const int16_t *s = src[i]; - for (n = 0; n < n_samples * ops->n_channels; n++) - d[n] = S16_MIX(d[n], s[n]); - } -} - -void -mix_u16_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - uint16_t *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(uint16_t)); - else if (dst != src[0]) - spa_memcpy(dst, src[0], n_samples * ops->n_channels * sizeof(uint16_t)); - - for (i = 1; i < n_src; i++) { - const uint16_t *s = src[i]; - for (n = 0; n < n_samples * ops->n_channels; n++) - d[n] = U16_MIX(d[n], s[n]); - } -} - -void -mix_s24_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - uint8_t *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(uint8_t) * 3); - else if (dst != src[0]) - spa_memcpy(dst, src[0], n_samples * ops->n_channels * sizeof(uint8_t) * 3); - - for (i = 1; i < n_src; i++) { - const uint8_t *s = src[i]; - for (n = 0; n < n_samples * ops->n_channels; n++) { - write_s24(d, S24_MIX(read_s24(d), read_s24(s))); - d += 3; - s += 3; - } - } -} - -void -mix_u24_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - uint8_t *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(uint8_t) * 3); - else if (dst != src[0]) - spa_memcpy(dst, src[0], n_samples * ops->n_channels * sizeof(uint8_t) * 3); - - for (i = 1; i < n_src; i++) { - const uint8_t *s = src[i]; - for (n = 0; n < n_samples * ops->n_channels; n++) { - write_u24(d, U24_MIX(read_u24(d), read_u24(s))); - d += 3; - s += 3; - } - } -} - -void -mix_s32_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - int32_t *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(int32_t)); - else if (dst != src[0]) - spa_memcpy(dst, src[0], n_samples * ops->n_channels * sizeof(int32_t)); - - for (i = 1; i < n_src; i++) { - const int32_t *s = src[i]; - for (n = 0; n < n_samples * ops->n_channels; n++) - d[n] = S32_MIX(d[n], s[n]); - } -} - -void -mix_u32_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - uint32_t *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(uint32_t)); - else if (dst != src[0]) - spa_memcpy(dst, src[0], n_samples * ops->n_channels * sizeof(uint32_t)); - - for (i = 1; i < n_src; i++) { - const uint32_t *s = src[i]; - for (n = 0; n < n_samples * ops->n_channels; n++) - d[n] = U32_MIX(d[n], s[n]); - } -} - -void -mix_s24_32_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - int32_t *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(int32_t)); - else if (dst != src[0]) - spa_memcpy(dst, src[0], n_samples * ops->n_channels * sizeof(int32_t)); - - for (i = 1; i < n_src; i++) { - const int32_t *s = src[i]; - for (n = 0; n < n_samples * ops->n_channels; n++) - d[n] = S24_32_MIX(d[n], s[n]); - } -} - -void -mix_u24_32_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - uint32_t *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(uint32_t)); - else if (dst != src[0]) - spa_memcpy(dst, src[0], n_samples * ops->n_channels * sizeof(uint32_t)); - - for (i = 1; i < n_src; i++) { - const uint32_t *s = src[i]; - for (n = 0; n < n_samples * ops->n_channels; n++) - d[n] = U24_32_MIX(d[n], s[n]); - } -} - -void -mix_f32_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - float *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(float)); - else if (dst != src[0]) - spa_memcpy(dst, src[0], n_samples * ops->n_channels * sizeof(float)); - - for (i = 1; i < n_src; i++) { - const float *s = src[i]; - for (n = 0; n < n_samples * ops->n_channels; n++) - d[n] = F32_MIX(d[n], s[n]); - } -} - -void -mix_f64_c(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i, n; - double *d = dst; - - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(double)); - else if (dst != src[0]) - spa_memcpy(dst, src[0], n_samples * ops->n_channels * sizeof(double)); - - for (i = 1; i < n_src; i++) { - const double *s = src[i]; - for (n = 0; n < n_samples * ops->n_channels; n++) - d[n] = F64_MIX(d[n], s[n]); - } -} +MAKE_FUNC(s8, int8_t, int16_t, S8_ACCUM, S8_CLAMP, true); +MAKE_FUNC(u8, uint8_t, int16_t, U8_ACCUM, U8_CLAMP, false); +MAKE_FUNC(s16, int16_t, int32_t, S16_ACCUM, S16_CLAMP, true); +MAKE_FUNC(u16, uint16_t, int16_t, U16_ACCUM, U16_CLAMP, false); +MAKE_FUNC(s24, int24_t, int32_t, S24_ACCUM, S24_CLAMP, false); +MAKE_FUNC(u24, uint24_t, int32_t, U24_ACCUM, U24_CLAMP, false); +MAKE_FUNC(s32, int32_t, int64_t, S32_ACCUM, S32_CLAMP, true); +MAKE_FUNC(u32, uint32_t, int64_t, U32_ACCUM, U32_CLAMP, false); +MAKE_FUNC(s24_32, int32_t, int32_t, S24_32_ACCUM, S24_32_CLAMP, true); +MAKE_FUNC(u24_32, uint32_t, int32_t, U24_32_ACCUM, U24_32_CLAMP, false); +MAKE_FUNC(f32, float, float, F32_ACCUM, F32_CLAMP, true); +MAKE_FUNC(f64, double, double, F64_ACCUM, F64_CLAMP, true); diff --git a/spa/plugins/audiomixer/mix-ops-sse.c b/spa/plugins/audiomixer/mix-ops-sse.c index cd6e05fc106a41f3fb6523ad1e099150665a0eb0..bae619bad19326e76dcd6138185d5fddd7c10404 100644 --- a/spa/plugins/audiomixer/mix-ops-sse.c +++ b/spa/plugins/audiomixer/mix-ops-sse.c @@ -32,58 +32,56 @@ #include <xmmintrin.h> -static inline void mix_2(float * dst, const float * SPA_RESTRICT src, uint32_t n_samples) -{ - uint32_t n, unrolled; - __m128 in1[4], in2[4]; - - if (SPA_LIKELY(SPA_IS_ALIGNED(src, 16) && - SPA_IS_ALIGNED(dst, 16))) - unrolled = n_samples & ~15; - else - unrolled = 0; - - for (n = 0; n < unrolled; n += 16) { - in1[0] = _mm_load_ps(&dst[n+ 0]); - in1[1] = _mm_load_ps(&dst[n+ 4]); - in1[2] = _mm_load_ps(&dst[n+ 8]); - in1[3] = _mm_load_ps(&dst[n+12]); - - in2[0] = _mm_load_ps(&src[n+ 0]); - in2[1] = _mm_load_ps(&src[n+ 4]); - in2[2] = _mm_load_ps(&src[n+ 8]); - in2[3] = _mm_load_ps(&src[n+12]); - - in1[0] = _mm_add_ps(in1[0], in2[0]); - in1[1] = _mm_add_ps(in1[1], in2[1]); - in1[2] = _mm_add_ps(in1[2], in2[2]); - in1[3] = _mm_add_ps(in1[3], in2[3]); - - _mm_store_ps(&dst[n+ 0], in1[0]); - _mm_store_ps(&dst[n+ 4], in1[1]); - _mm_store_ps(&dst[n+ 8], in1[2]); - _mm_store_ps(&dst[n+12], in1[3]); - } - for (; n < n_samples; n++) { - in1[0] = _mm_load_ss(&dst[n]), - in2[0] = _mm_load_ss(&src[n]), - in1[0] = _mm_add_ss(in1[0], in2[0]); - _mm_store_ss(&dst[n], in1[0]); - } -} - void mix_f32_sse(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples) { - uint32_t i; + n_samples *= ops->n_channels; + + if (n_src == 0) { + memset(dst, 0, n_samples * sizeof(float)); + } else if (n_src == 1) { + if (dst != src[0]) + spa_memcpy(dst, src[0], n_samples * sizeof(float)); + } else { + uint32_t n, i, unrolled; + __m128 in[4]; + const float **s = (const float **)src; + float *d = dst; + + if (SPA_LIKELY(SPA_IS_ALIGNED(dst, 16))) { + unrolled = n_samples & ~15; + for (i = 0; i < n_src; i++) { + if (SPA_UNLIKELY(!SPA_IS_ALIGNED(src[i], 16))) { + unrolled = 0; + break; + } + } + } else + unrolled = 0; - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(float)); - else if (dst != src[0]) - spa_memcpy(dst, src[0], n_samples * ops->n_channels * sizeof(float)); + for (n = 0; n < unrolled; n += 16) { + in[0] = _mm_load_ps(&s[0][n+ 0]); + in[1] = _mm_load_ps(&s[0][n+ 4]); + in[2] = _mm_load_ps(&s[0][n+ 8]); + in[3] = _mm_load_ps(&s[0][n+12]); - for (i = 1; i < n_src; i++) { - mix_2(dst, src[i], n_samples * ops->n_channels); + for (i = 1; i < n_src; i++) { + in[0] = _mm_add_ps(in[0], _mm_load_ps(&s[i][n+ 0])); + in[1] = _mm_add_ps(in[1], _mm_load_ps(&s[i][n+ 4])); + in[2] = _mm_add_ps(in[2], _mm_load_ps(&s[i][n+ 8])); + in[3] = _mm_add_ps(in[3], _mm_load_ps(&s[i][n+12])); + } + _mm_store_ps(&d[n+ 0], in[0]); + _mm_store_ps(&d[n+ 4], in[1]); + _mm_store_ps(&d[n+ 8], in[2]); + _mm_store_ps(&d[n+12], in[3]); + } + for (; n < n_samples; n++) { + in[0] = _mm_load_ss(&s[0][n]); + for (i = 1; i < n_src; i++) + in[0] = _mm_add_ss(in[0], _mm_load_ss(&s[i][n])); + _mm_store_ss(&d[n], in[0]); + } } } diff --git a/spa/plugins/audiomixer/mix-ops-sse2.c b/spa/plugins/audiomixer/mix-ops-sse2.c index 51ad0dbcc72e586f14ce0215c302db68e0f5ceaf..e2f632d44788e42a2033e018bb630f415181ec6b 100644 --- a/spa/plugins/audiomixer/mix-ops-sse2.c +++ b/spa/plugins/audiomixer/mix-ops-sse2.c @@ -32,58 +32,56 @@ #include <emmintrin.h> -static inline void mix_2(double * dst, const double * SPA_RESTRICT src, uint32_t n_samples) -{ - uint32_t n, unrolled; - __m128d in1[4], in2[4]; - - if (SPA_IS_ALIGNED(src, 16) && - SPA_IS_ALIGNED(dst, 16)) - unrolled = n_samples & ~7; - else - unrolled = 0; - - for (n = 0; n < unrolled; n += 8) { - in1[0] = _mm_load_pd(&dst[n+ 0]); - in1[1] = _mm_load_pd(&dst[n+ 2]); - in1[2] = _mm_load_pd(&dst[n+ 4]); - in1[3] = _mm_load_pd(&dst[n+ 6]); - - in2[0] = _mm_load_pd(&src[n+ 0]); - in2[1] = _mm_load_pd(&src[n+ 2]); - in2[2] = _mm_load_pd(&src[n+ 4]); - in2[3] = _mm_load_pd(&src[n+ 6]); - - in1[0] = _mm_add_pd(in1[0], in2[0]); - in1[1] = _mm_add_pd(in1[1], in2[1]); - in1[2] = _mm_add_pd(in1[2], in2[2]); - in1[3] = _mm_add_pd(in1[3], in2[3]); - - _mm_store_pd(&dst[n+ 0], in1[0]); - _mm_store_pd(&dst[n+ 2], in1[1]); - _mm_store_pd(&dst[n+ 4], in1[2]); - _mm_store_pd(&dst[n+ 6], in1[3]); - } - for (; n < n_samples; n++) { - in1[0] = _mm_load_sd(&dst[n]), - in2[0] = _mm_load_sd(&src[n]), - in1[0] = _mm_add_sd(in1[0], in2[0]); - _mm_store_sd(&dst[n], in1[0]); - } -} - void mix_f64_sse2(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples) { - uint32_t i; + n_samples *= ops->n_channels; + + if (n_src == 0) { + memset(dst, 0, n_samples * sizeof(double)); + } else if (n_src == 1) { + if (dst != src[0]) + spa_memcpy(dst, src[0], n_samples * sizeof(double)); + } else { + uint32_t n, i, unrolled; + __m128d in[4]; + const double **s = (const double **)src; + double *d = dst; + + if (SPA_LIKELY(SPA_IS_ALIGNED(dst, 16))) { + unrolled = n_samples & ~15; + for (i = 0; i < n_src; i++) { + if (SPA_UNLIKELY(!SPA_IS_ALIGNED(src[i], 16))) { + unrolled = 0; + break; + } + } + } else + unrolled = 0; - if (n_src == 0) - memset(dst, 0, n_samples * ops->n_channels * sizeof(double)); - else if (dst != src[0]) - spa_memcpy(dst, src[0], n_samples * ops->n_channels * sizeof(double)); + for (n = 0; n < unrolled; n += 8) { + in[0] = _mm_load_pd(&s[0][n+0]); + in[1] = _mm_load_pd(&s[0][n+2]); + in[2] = _mm_load_pd(&s[0][n+4]); + in[3] = _mm_load_pd(&s[0][n+6]); - for (i = 1; i < n_src; i++) { - mix_2(dst, src[i], n_samples * ops->n_channels); + for (i = 1; i < n_src; i++) { + in[0] = _mm_add_pd(in[0], _mm_load_pd(&s[i][n+0])); + in[1] = _mm_add_pd(in[1], _mm_load_pd(&s[i][n+2])); + in[2] = _mm_add_pd(in[2], _mm_load_pd(&s[i][n+4])); + in[3] = _mm_add_pd(in[3], _mm_load_pd(&s[i][n+6])); + } + _mm_store_pd(&d[n+0], in[0]); + _mm_store_pd(&d[n+2], in[1]); + _mm_store_pd(&d[n+4], in[2]); + _mm_store_pd(&d[n+6], in[3]); + } + for (; n < n_samples; n++) { + in[0] = _mm_load_sd(&s[0][n]); + for (i = 1; i < n_src; i++) + in[0] = _mm_add_sd(in[0], _mm_load_sd(&s[i][n])); + _mm_store_sd(&d[n], in[0]); + } } } diff --git a/spa/plugins/audiomixer/mix-ops.h b/spa/plugins/audiomixer/mix-ops.h index a75c9b40b422696a4f7d6570b1ccb6322f371267..11e88dcdf0d3b84d794fd1cad41089189c3e8211 100644 --- a/spa/plugins/audiomixer/mix-ops.h +++ b/spa/plugins/audiomixer/mix-ops.h @@ -24,80 +24,98 @@ #include <spa/utils/defs.h> -static inline uint32_t read_u24(const void *src) -{ - const uint8_t *s = src; +typedef struct { #if __BYTE_ORDER == __LITTLE_ENDIAN - return (((uint32_t)s[2] << 16) | ((uint32_t)(uint8_t)s[1] << 8) | (uint32_t)(uint8_t)s[0]); + uint8_t v3; + uint8_t v2; + uint8_t v1; #else - return (((uint32_t)s[0] << 16) | ((uint32_t)(uint8_t)s[1] << 8) | (uint32_t)(uint8_t)s[2]); + uint8_t v1; + uint8_t v2; + uint8_t v3; #endif -} +} __attribute__ ((packed)) uint24_t; -static inline int32_t read_s24(const void *src) -{ - const int8_t *s = src; +typedef struct { #if __BYTE_ORDER == __LITTLE_ENDIAN - return (((int32_t)s[2] << 16) | ((uint32_t)(uint8_t)s[1] << 8) | (uint32_t)(uint8_t)s[0]); + uint8_t v3; + uint8_t v2; + int8_t v1; #else - return (((int32_t)s[0] << 16) | ((uint32_t)(uint8_t)s[1] << 8) | (uint32_t)(uint8_t)s[2]); + int8_t v1; + uint8_t v2; + uint8_t v3; #endif -} +} __attribute__ ((packed)) int24_t; -static inline void write_u24(void *dst, uint32_t val) +static inline uint32_t u24_to_u32(uint24_t src) { - uint8_t *d = dst; -#if __BYTE_ORDER == __LITTLE_ENDIAN - d[0] = (uint8_t) (val); - d[1] = (uint8_t) (val >> 8); - d[2] = (uint8_t) (val >> 16); -#else - d[0] = (uint8_t) (val >> 16); - d[1] = (uint8_t) (val >> 8); - d[2] = (uint8_t) (val); -#endif + return ((uint32_t)src.v1 << 16) | ((uint32_t)src.v2 << 8) | (uint32_t)src.v3; } -static inline void write_s24(void *dst, int32_t val) +#define U32_TO_U24(s) (uint24_t) { .v1 = (uint8_t)(((uint32_t)s) >> 16), \ + .v2 = (uint8_t)(((uint32_t)s) >> 8), .v3 = (uint8_t)((uint32_t)s) } + +static inline uint24_t u32_to_u24(uint32_t src) { - uint8_t *d = dst; -#if __BYTE_ORDER == __LITTLE_ENDIAN - d[0] = (uint8_t) (val); - d[1] = (uint8_t) (val >> 8); - d[2] = (uint8_t) (val >> 16); -#else - d[0] = (uint8_t) (val >> 16); - d[1] = (uint8_t) (val >> 8); - d[2] = (uint8_t) (val); -#endif + return U32_TO_U24(src); } -#define S8_MIN -127 -#define S8_MAX 127 -#define S8_MIX(a, b) (int8_t)(SPA_CLAMP((int16_t)(a) + (int16_t)(b), S8_MIN, S8_MAX)) -#define U8_MIX(a, b) (uint8_t)((int16_t)S8_MIX((int16_t)(a) - S8_MAX, (int16_t)(b) - S8_MAX) + S8_MAX) - -#define S16_MIN -32767 -#define S16_MAX 32767 -#define S16_MIX(a, b) (int16_t)(SPA_CLAMP((int32_t)(a) + (int32_t)(b), S16_MIN, S16_MAX)) -#define U16_MIX(a, b) (uint16_t)((int32_t)S16_MIX((int32_t)(a) - S16_MAX, (int32_t)(b) - S16_MAX) + S16_MAX) - -#define S24_MIN -8388607 -#define S24_MAX 8388607 -#define S24_MIX(a, b) (int32_t)(SPA_CLAMP((int32_t)(a) + (int32_t)(b), S24_MIN, S24_MAX)) -#define U24_MIX(a, b) (uint32_t)((int32_t)S24_MIX((int32_t)(a) - S24_MAX, (int32_t)(b) - S24_MAX) + S24_MAX) +static inline int32_t s24_to_s32(int24_t src) +{ + return ((int32_t)src.v1 << 16) | ((uint32_t)src.v2 << 8) | (uint32_t)src.v3; +} -#define S32_MIN -2147483647 -#define S32_MAX 2147483647 -#define S32_MIX(a, b) (int32_t)(SPA_CLAMP((int64_t)(a) + (int64_t)(b), S32_MIN, S32_MAX)) -#define U32_MIX(a, b) (uint32_t)((int64_t)S32_MIX((int64_t)(a) - S32_MAX, (int64_t)(b) - S32_MAX) + S32_MAX) +#define S32_TO_S24(s) (int24_t) { .v1 = (int8_t)(((int32_t)s) >> 16), \ + .v2 = (uint8_t)(((uint32_t)s) >> 8), .v3 = (uint8_t)((uint32_t)s) } -#define S24_32_MIX(a, b) S24_MIX (a, b) -#define U24_32_MIX(a, b) U24_MIX (a, b) +static inline int24_t s32_to_s24(int32_t src) +{ + return S32_TO_S24(src); +} -#define F32_MIX(a, b) (float)((float)(a) + (float)(b)) -#define F64_MIX(a, b) (double)((double)(a) + (double)(b)) +#define S8_MIN -128 +#define S8_MAX 127 +#define S8_ACCUM(a,b) ((a) + (int16_t)(b)) +#define S8_CLAMP(a) (int8_t)(SPA_CLAMP((a), S8_MIN, S8_MAX)) +#define U8_OFFS 128 +#define U8_ACCUM(a,b) ((a) + ((int16_t)(b) - U8_OFFS)) +#define U8_CLAMP(a) (uint8_t)(SPA_CLAMP((a), S8_MIN, S8_MAX) + U8_OFFS) + +#define S16_MIN -32768 +#define S16_MAX 32767 +#define S16_ACCUM(a,b) ((a) + (int32_t)(b)) +#define S16_CLAMP(a) (int16_t)(SPA_CLAMP((a), S16_MIN, S16_MAX)) +#define U16_OFFS 32768 +#define U16_ACCUM(a,b) ((a) + ((int32_t)(b) - U16_OFFS)) +#define U16_CLAMP(a) (uint16_t)(SPA_CLAMP((a), S16_MIN, S16_MAX) + U16_OFFS) + +#define S24_32_MIN -8388608 +#define S24_32_MAX 8388607 +#define S24_32_ACCUM(a,b) ((a) + (int32_t)(b)) +#define S24_32_CLAMP(a) (int32_t)(SPA_CLAMP((a), S24_32_MIN, S24_32_MAX)) +#define U24_32_OFFS 8388608 +#define U24_32_ACCUM(a,b) ((a) + ((int32_t)(b) - U24_32_OFFS)) +#define U24_32_CLAMP(a) (uint32_t)(SPA_CLAMP((a), S24_32_MIN, S24_32_MAX) + U24_32_OFFS) + +#define S24_ACCUM(a,b) S24_32_ACCUM(a, s24_to_s32(b)) +#define S24_CLAMP(a) s32_to_s24(S24_32_CLAMP(a)) +#define U24_ACCUM(a,b) U24_32_ACCUM(a, u24_to_u32(b)) +#define U24_CLAMP(a) u32_to_u24(U24_32_CLAMP(a)) + +#define S32_MIN -2147483648 +#define S32_MAX 2147483647 +#define S32_ACCUM(a,b) ((a) + (int64_t)(b)) +#define S32_CLAMP(a) (int32_t)(SPA_CLAMP((a), S32_MIN, S32_MAX)) +#define U32_OFFS 2147483648 +#define U32_ACCUM(a,b) ((a) + ((int64_t)(b) - U32_OFFS)) +#define U32_CLAMP(a) (uint32_t)(SPA_CLAMP((a), S32_MIN, S32_MAX) + U32_OFFS) + +#define F32_ACCUM(a,b) ((a) + (b)) +#define F32_CLAMP(a) (a) +#define F64_ACCUM(a,b) ((a) + (b)) +#define F64_CLAMP(a) (a) struct mix_ops { uint32_t fmt; diff --git a/spa/plugins/audiomixer/mixer-dsp.c b/spa/plugins/audiomixer/mixer-dsp.c index cf926244cca39984ed9dcbc251cce55e1ece1acc..f93796b971bed0b4c3645c8993910f965271b4a7 100644 --- a/spa/plugins/audiomixer/mixer-dsp.c +++ b/spa/plugins/audiomixer/mixer-dsp.c @@ -675,8 +675,8 @@ static int impl_node_process(void *object) spa_return_val_if_fail(this != NULL, -EINVAL); outport = GET_OUT_PORT(this, 0); - outio = outport->io; - spa_return_val_if_fail(outio != NULL, -EIO); + if ((outio = outport->io) == NULL) + return -EIO; spa_log_trace_fp(this->log, "%p: status %p %d %d", this, outio, outio->status, outio->buffer_id); @@ -727,8 +727,10 @@ static int impl_node_process(void *object) i, inio, outio, inio->status, inio->buffer_id, offs, size); - datas[n_buffers] = SPA_PTROFF(bd->data, offs, void); - buffers[n_buffers++] = inb; + if (!SPA_FLAG_IS_SET(bd->chunk->flags, SPA_CHUNK_FLAG_EMPTY)) { + datas[n_buffers] = SPA_PTROFF(bd->data, offs, void); + buffers[n_buffers++] = inb; + } inio->status = SPA_STATUS_NEED_DATA; } @@ -742,6 +744,7 @@ static int impl_node_process(void *object) *outb->buffer = *buffers[0]->buffer; } else { struct spa_data *d = outb->buf.datas; + *outb->buffer = outb->buf; maxsize = SPA_MIN(maxsize, d[0].maxsize); @@ -749,6 +752,7 @@ static int impl_node_process(void *object) d[0].chunk->offset = 0; d[0].chunk->size = maxsize; d[0].chunk->stride = sizeof(float); + SPA_FLAG_UPDATE(d[0].chunk->flags, SPA_CHUNK_FLAG_EMPTY, n_buffers == 0); spa_log_trace_fp(this->log, "%p: %d mix %d", this, n_buffers, maxsize); diff --git a/spa/plugins/audiomixer/test-helper.h b/spa/plugins/audiomixer/test-helper.h new file mode 100644 index 0000000000000000000000000000000000000000..8c789bd45930280a89e80bcf4de9e012bf88594f --- /dev/null +++ b/spa/plugins/audiomixer/test-helper.h @@ -0,0 +1,97 @@ +#include <dlfcn.h> + +#include <spa/support/plugin.h> +#include <spa/utils/type.h> +#include <spa/utils/result.h> +#include <spa/support/cpu.h> +#include <spa/utils/names.h> + +static inline const struct spa_handle_factory *get_factory(spa_handle_factory_enum_func_t enum_func, + const char *name, uint32_t version) +{ + uint32_t i; + int res; + const struct spa_handle_factory *factory; + + for (i = 0;;) { + if ((res = enum_func(&factory, &i)) <= 0) { + if (res < 0) + errno = -res; + break; + } + if (factory->version >= version && + !strcmp(factory->name, name)) + return factory; + } + return NULL; +} + +static inline struct spa_handle *load_handle(const struct spa_support *support, + uint32_t n_support, const char *lib, const char *name) +{ + int res, len; + void *hnd; + spa_handle_factory_enum_func_t enum_func; + const struct spa_handle_factory *factory; + struct spa_handle *handle; + const char *str; + char *path; + + if ((str = getenv("SPA_PLUGIN_DIR")) == NULL) + str = PLUGINDIR; + + len = strlen(str) + strlen(lib) + 2; + path = alloca(len); + snprintf(path, len, "%s/%s", str, lib); + + if ((hnd = dlopen(path, RTLD_NOW)) == NULL) { + fprintf(stderr, "can't load %s: %s\n", lib, dlerror()); + res = -ENOENT; + goto error; + } + if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { + fprintf(stderr, "can't find enum function\n"); + res = -ENXIO; + goto error_close; + } + + if ((factory = get_factory(enum_func, name, SPA_VERSION_HANDLE_FACTORY)) == NULL) { + fprintf(stderr, "can't find factory\n"); + res = -ENOENT; + goto error_close; + } + handle = calloc(1, spa_handle_factory_get_size(factory, NULL)); + if ((res = spa_handle_factory_init(factory, handle, + NULL, support, n_support)) < 0) { + fprintf(stderr, "can't make factory instance: %d\n", res); + goto error_close; + } + return handle; + +error_close: + dlclose(hnd); +error: + errno = -res; + return NULL; +} + +static inline uint32_t get_cpu_flags(void) +{ + struct spa_handle *handle; + uint32_t flags; + void *iface; + int res; + + handle = load_handle(NULL, 0, "support/libspa-support.so", SPA_NAME_SUPPORT_CPU); + if (handle == NULL) + return 0; + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_CPU, &iface)) < 0) { + fprintf(stderr, "can't get CPU interface %s\n", spa_strerror(res)); + return 0; + } + flags = spa_cpu_get_flags((struct spa_cpu*)iface); + + free(handle); + + return flags; +} diff --git a/spa/plugins/audiomixer/test-mix-ops.c b/spa/plugins/audiomixer/test-mix-ops.c new file mode 100644 index 0000000000000000000000000000000000000000..a20f7a4313d43f7c2df985842c807363f0a81ab8 --- /dev/null +++ b/spa/plugins/audiomixer/test-mix-ops.c @@ -0,0 +1,293 @@ +/* Spa + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> + +#include <spa/debug/mem.h> + +#include "test-helper.h" +#include "mix-ops.c" + +static uint32_t cpu_flags; + +#define N_SAMPLES 1024 + +static uint8_t samp_out[N_SAMPLES * 8]; + +static void compare_mem(int i, int j, const void *m1, const void *m2, size_t size) +{ + int res = memcmp(m1, m2, size); + if (res != 0) { + fprintf(stderr, "%d %d %zd:\n", i, j, size); + spa_debug_mem(0, m1, size); + spa_debug_mem(0, m2, size); + } + spa_assert_se(res == 0); +} + +static int run_test(const char *name, const void *src[], uint32_t n_src, const void *dst, + size_t dst_size, uint32_t n_samples, mix_func_t mix) +{ + struct mix_ops ops; + + ops.fmt = SPA_AUDIO_FORMAT_F32; + ops.n_channels = 1; + ops.cpu_flags = cpu_flags; + mix_ops_init(&ops); + + fprintf(stderr, "%s\n", name); + + mix(&ops, (void *)samp_out, src, n_src, n_samples); + compare_mem(0, 0, samp_out, dst, dst_size); + return 0; +} + +static void test_s8(void) +{ + int8_t out[] = { 0x00, 0x00, 0x00, 0x00 }; + int8_t in_1[] = { 0x00, 0x00, 0x00, 0x00 }; + int8_t in_2[] = { 0x7f, 0x80, 0x40, 0xc0 }; + int8_t in_3[] = { 0x40, 0xc0, 0xc0, 0x40 }; + int8_t in_4[] = { 0xc0, 0x40, 0x40, 0xc0 }; + int8_t out_4[] = { 0x7f, 0x80, 0x40, 0xc0 }; + const void *src[6] = { in_1, in_2, in_3, in_4 }; + + run_test("test_s8_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_s8_c); + run_test("test_s8_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_s8_c); + run_test("test_s8_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_s8_c); +} + +static void test_u8(void) +{ + uint8_t out[] = { 0x80, 0x80, 0x80, 0x80 }; + uint8_t in_1[] = { 0x80, 0x80, 0x80, 0x80 }; + uint8_t in_2[] = { 0xff, 0x00, 0xc0, 0x40 }; + uint8_t in_3[] = { 0xc0, 0x40, 0x40, 0xc0 }; + uint8_t in_4[] = { 0x40, 0xc0, 0xc0, 0x40 }; + uint8_t out_4[] = { 0xff, 0x00, 0xc0, 0x40 }; + const void *src[6] = { in_1, in_2, in_3, in_4 }; + + run_test("test_u8_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_u8_c); + run_test("test_u8_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_u8_c); + run_test("test_u8_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_u8_c); +} + +static void test_s16(void) +{ + int16_t out[] = { 0x0000, 0x0000, 0x0000, 0x0000 }; + int16_t in_1[] = { 0x0000, 0x0000, 0x0000, 0x0000 }; + int16_t in_2[] = { 0x7fff, 0x8000, 0x4000, 0xc000 }; + int16_t in_3[] = { 0x4000, 0xc000, 0xc000, 0x4000 }; + int16_t in_4[] = { 0xc000, 0x4000, 0x4000, 0xc000 }; + int16_t out_4[] = { 0x7fff, 0x8000, 0x4000, 0xc000 }; + const void *src[6] = { in_1, in_2, in_3, in_4 }; + + run_test("test_s16_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_s16_c); + run_test("test_s16_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_s16_c); + run_test("test_s16_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_s16_c); +} + +static void test_u16(void) +{ + uint16_t out[] = { 0x8000, 0x8000, 0x8000, 0x8000 }; + uint16_t in_1[] = { 0x8000, 0x8000, 0x8000 , 0x8000}; + uint16_t in_2[] = { 0xffff, 0x0000, 0xc000, 0x4000 }; + uint16_t in_3[] = { 0xc000, 0x4000, 0x4000, 0xc000 }; + uint16_t in_4[] = { 0x4000, 0xc000, 0xc000, 0x4000 }; + uint16_t out_4[] = { 0xffff, 0x0000, 0xc000, 0x4000 }; + const void *src[6] = { in_1, in_2, in_3, in_4 }; + + run_test("test_u16_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_u16_c); + run_test("test_u16_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_u16_c); + run_test("test_u16_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_u16_c); +} + +static void test_s24(void) +{ + int24_t out[] = { S32_TO_S24(0x000000), S32_TO_S24(0x000000), S32_TO_S24(0x000000) }; + int24_t in_1[] = { S32_TO_S24(0x000000), S32_TO_S24(0x000000), S32_TO_S24(0x000000) }; + int24_t in_2[] = { S32_TO_S24(0x7fffff), S32_TO_S24(0xff800000), S32_TO_S24(0x400000) }; + int24_t in_3[] = { S32_TO_S24(0x400000), S32_TO_S24(0xffc00000), S32_TO_S24(0xffc00000) }; + int24_t in_4[] = { S32_TO_S24(0xffc00000), S32_TO_S24(0x400000), S32_TO_S24(0x400000) }; + int24_t out_4[] = { S32_TO_S24(0x7fffff), S32_TO_S24(0xff800000), S32_TO_S24(0x400000) }; + const void *src[6] = { in_1, in_2, in_3, in_4 }; + + run_test("test_s24_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_s24_c); + run_test("test_s24_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_s24_c); + run_test("test_s24_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_s24_c); +} + +static void test_u24(void) +{ + uint24_t out[] = { U32_TO_U24(0x800000), U32_TO_U24(0x800000), U32_TO_U24(0x800000) }; + uint24_t in_1[] = { U32_TO_U24(0x800000), U32_TO_U24(0x800000), U32_TO_U24(0x800000) }; + uint24_t in_2[] = { U32_TO_U24(0xffffffff), U32_TO_U24(0x000000), U32_TO_U24(0xffc00000) }; + uint24_t in_3[] = { U32_TO_U24(0xffc00000), U32_TO_U24(0x400000), U32_TO_U24(0x400000) }; + uint24_t in_4[] = { U32_TO_U24(0x400000), U32_TO_U24(0xffc00000), U32_TO_U24(0xffc00000) }; + uint24_t out_4[] = { U32_TO_U24(0xffffffff), U32_TO_U24(0x000000), U32_TO_U24(0xffc00000) }; + const void *src[6] = { in_1, in_2, in_3, in_4 }; + + run_test("test_u24_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_u24_c); + run_test("test_u24_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_u24_c); + run_test("test_u24_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_u24_c); +} + +static void test_s32(void) +{ + int32_t out[] = { 0x00000000, 0x00000000, 0x00000000, 0x00000000 }; + int32_t in_1[] = { 0x00000000, 0x00000000, 0x00000000, 0x00000000 }; + int32_t in_2[] = { 0x7fffffff, 0x80000000, 0x40000000, 0xc0000000 }; + int32_t in_3[] = { 0x40000000, 0xc0000000, 0xc0000000, 0x40000000 }; + int32_t in_4[] = { 0xc0000000, 0x40000000, 0x40000000, 0xc0000000 }; + int32_t out_4[] = { 0x7fffffff, 0x80000000, 0x40000000, 0xc0000000 }; + const void *src[6] = { in_1, in_2, in_3, in_4 }; + + run_test("test_s32_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_s32_c); + run_test("test_s32_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_s32_c); + run_test("test_s32_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_s32_c); +} + +static void test_u32(void) +{ + uint32_t out[] = { 0x80000000, 0x80000000, 0x80000000, 0x80000000 }; + uint32_t in_1[] = { 0x80000000, 0x80000000, 0x80000000, 0x80000000 }; + uint32_t in_2[] = { 0xffffffff, 0x00000000, 0xc0000000, 0x40000000 }; + uint32_t in_3[] = { 0xc0000000, 0x40000000, 0x40000000, 0xc0000000 }; + uint32_t in_4[] = { 0x40000000, 0xc0000000, 0xc0000000, 0x40000000 }; + uint32_t out_4[] = { 0xffffffff, 0x00000000, 0xc0000000, 0x40000000 }; + const void *src[6] = { in_1, in_2, in_3, in_4 }; + + run_test("test_u32_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_u32_c); + run_test("test_u32_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_u32_c); + run_test("test_u32_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_u32_c); +} + +static void test_s24_32(void) +{ + int32_t out[] = { 0x000000, 0x000000, 0x000000, 0x000000 }; + int32_t in_1[] = { 0x000000, 0x000000, 0x000000, 0x000000 }; + int32_t in_2[] = { 0x7fffff, 0xff800000, 0x400000, 0xffc00000 }; + int32_t in_3[] = { 0x400000, 0xffc00000, 0xffc00000, 0x400000 }; + int32_t in_4[] = { 0xffc00000, 0x400000, 0x400000, 0xffc00000 }; + int32_t out_4[] = { 0x7fffff, 0xff800000, 0x400000, 0xffc00000 }; + const void *src[6] = { in_1, in_2, in_3, in_4 }; + + run_test("test_s24_32_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_s24_32_c); + run_test("test_s24_32_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_s24_32_c); + run_test("test_s24_32_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_s24_32_c); +} + +static void test_u24_32(void) +{ + uint32_t out[] = { 0x800000, 0x800000, 0x800000, 0x800000 }; + uint32_t in_1[] = { 0x800000, 0x800000, 0x800000, 0x800000 }; + uint32_t in_2[] = { 0xffffff, 0x000000, 0xc00000, 0x400000 }; + uint32_t in_3[] = { 0xc00000, 0x400000, 0x400000, 0xc00000 }; + uint32_t in_4[] = { 0x400000, 0xc00000, 0xc00000, 0x400000 }; + uint32_t out_4[] = { 0xffffff, 0x000000, 0xc00000, 0x400000 }; + const void *src[6] = { in_1, in_2, in_3, in_4 }; + + run_test("test_u24_32_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_u24_32_c); + run_test("test_u24_32_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_u24_32_c); + run_test("test_u24_32_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_u24_32_c); +} + +static void test_f32(void) +{ + float out[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float in_1[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float in_2[] = { 1.0f, -1.0f, 0.5f, -0.5f }; + float in_3[] = { 0.5f, -0.5f, -0.5f, 0.5f }; + float in_4[] = { -0.5f, 1.0f, 0.5f, -0.5f }; + float out_4[] = { 1.0f, -0.5f, 0.5f, -0.5f }; + const void *src[6] = { in_1, in_2, in_3, in_4 }; + + run_test("test_f32_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_f32_c); + run_test("test_f32_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_f32_c); + run_test("test_f32_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_f32_c); +#if defined(HAVE_SSE) + if (cpu_flags & SPA_CPU_FLAG_SSE) { + run_test("test_f32_0_sse", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_f32_sse); + run_test("test_f32_1_sse", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_f32_sse); + run_test("test_f32_4_sse", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_f32_sse); + } +#endif +#if defined(HAVE_AVX) + if (cpu_flags & SPA_CPU_FLAG_AVX) { + run_test("test_f32_0_avx", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_f32_avx); + run_test("test_f32_1_avx", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_f32_avx); + run_test("test_f32_4_avx", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_f32_avx); + } +#endif +} + +static void test_f64(void) +{ + double out[] = { 0.0, 0.0, 0.0, 0.0 }; + double in_1[] = { 0.0, 0.0, 0.0, 0.0 }; + double in_2[] = { 1.0, -1.0, 0.5, -0.5 }; + double in_3[] = { 0.5, -0.5, -0.5, 0.5 }; + double in_4[] = { -0.5, 1.0, 0.5, -0.5 }; + double out_4[] = { 1.0, -0.5, 0.5, -0.5 }; + const void *src[6] = { in_1, in_2, in_3, in_4 }; + + run_test("test_f64_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_f64_c); + run_test("test_f64_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_f64_c); + run_test("test_f64_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_f64_c); +#if defined(HAVE_SSE2) + if (cpu_flags & SPA_CPU_FLAG_SSE2) { + run_test("test_f64_0_sse2", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_f64_sse2); + run_test("test_f64_1_sse2", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_f64_sse2); + run_test("test_f64_4_sse2", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_f64_sse2); + } +#endif +} + +int main(int argc, char *argv[]) +{ + cpu_flags = get_cpu_flags(); + printf("got get CPU flags %d\n", cpu_flags); + + test_s8(); + test_u8(); + test_s16(); + test_u16(); + test_s24(); + test_u24(); + test_s32(); + test_u32(); + test_s24_32(); + test_u24_32(); + test_f32(); + test_f64(); + + return 0; +} diff --git a/spa/plugins/audiotestsrc/audiotestsrc.c b/spa/plugins/audiotestsrc/audiotestsrc.c index 2a7174b4c9363e48c4a274b43e113e6577409aa4..d02b735803a1d7cb0fb2d9c1d9b87be1cd79135a 100644 --- a/spa/plugins/audiotestsrc/audiotestsrc.c +++ b/spa/plugins/audiotestsrc/audiotestsrc.c @@ -931,7 +931,8 @@ static int impl_node_process(void *object) port = &this->port; io = port->io; - spa_return_val_if_fail(io != NULL, -EIO); + if ((io = port->io) == NULL) + return -EIO; if (port->io_control) process_control(this, &port->io_control->sequence); diff --git a/spa/plugins/avb/avb-pcm-sink.c b/spa/plugins/avb/avb-pcm-sink.c new file mode 100644 index 0000000000000000000000000000000000000000..00f4e9593433022d88684e7a581af5da9987d7fb --- /dev/null +++ b/spa/plugins/avb/avb-pcm-sink.c @@ -0,0 +1,911 @@ +/* Spa AVB PCM Sink + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <stddef.h> + +#include <spa/node/node.h> +#include <spa/node/utils.h> +#include <spa/node/keys.h> +#include <spa/monitor/device.h> +#include <spa/utils/keys.h> +#include <spa/utils/names.h> +#include <spa/utils/string.h> +#include <spa/param/audio/format.h> +#include <spa/pod/filter.h> +#include <spa/debug/pod.h> + +#include "avb-pcm.h" + +#define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == 0) +#define GET_PORT(this,d,p) (&this->ports[p]) + +static void reset_props(struct props *props) +{ + snprintf(props->ifname, sizeof(props->ifname), "%s", DEFAULT_IFNAME); + parse_addr(props->addr, DEFAULT_ADDR); + props->prio = DEFAULT_PRIO; + parse_streamid(&props->streamid, DEFAULT_STREAMID); + props->mtt = DEFAULT_MTT; + props->t_uncertainty = DEFAULT_TU; + props->frames_per_pdu = DEFAULT_FRAMES_PER_PDU; +} + +static void emit_node_info(struct state *this, bool full) +{ + uint64_t old = full ? this->info.change_mask : 0; + + if (full) + this->info.change_mask = this->info_all; + if (this->info.change_mask) { + struct spa_dict_item items[4]; + uint32_t i, n_items = 0; + + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "avb"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Sink"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_DRIVER, "true"); + this->info.props = &SPA_DICT_INIT(items, n_items); + + 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_node_emit_info(&this->hooks, &this->info); + + this->info.change_mask = old; + } +} + +static void emit_port_info(struct state *this, struct port *port, bool full) +{ + uint64_t old = full ? port->info.change_mask : 0; + + if (full) + port->info.change_mask = port->info_all; + if (port->info.change_mask) { + uint32_t i; + + if (port->info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { + for (i = 0; i < port->info.n_params; i++) { + if (port->params[i].user > 0) { + port->params[i].flags ^= SPA_PARAM_INFO_SERIAL; + port->params[i].user = 0; + } + } + } + spa_node_emit_port_info(&this->hooks, + port->direction, port->id, &port->info); + port->info.change_mask = old; + } +} + +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 state *this = object; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[4096]; + struct spa_result_node_params result; + uint32_t count = 0; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + + result.id = id; + result.next = start; + next: + result.index = result.next++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + switch (id) { + case SPA_PARAM_PropInfo: + { + switch (result.index) { + default: + param = spa_avb_enum_propinfo(this, result.index, &b); + if (param == NULL) + return 0; + } + break; + } + case SPA_PARAM_Props: + { + struct spa_pod_frame f; + + switch (result.index) { + case 0: + spa_pod_builder_push_object(&b, &f, + SPA_TYPE_OBJECT_Props, id); + spa_pod_builder_add(&b, + SPA_PROP_latencyOffsetNsec, SPA_POD_Long(this->process_latency.ns), + 0); + spa_avb_add_prop_params(this, &b); + param = spa_pod_builder_pop(&b, &f); + break; + default: + return 0; + } + break; + } + case SPA_PARAM_IO: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); + break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Position), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_position))); + break; + default: + return 0; + } + break; + + case SPA_PARAM_ProcessLatency: + switch (result.index) { + case 0: + param = spa_process_latency_build(&b, id, &this->process_latency); + break; + default: + return 0; + } + break; + + default: + return -ENOENT; + } + + if (spa_pod_filter(&b, &result.param, param, filter) < 0) + goto next; + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + return 0; +} + +static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) +{ + struct state *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + switch (id) { + case SPA_IO_Clock: + this->clock = data; + break; + case SPA_IO_Position: + this->position = data; + break; + default: + return -ENOENT; + } + spa_avb_reassign_follower(this); + + return 0; +} + +static void handle_process_latency(struct state *this, + const struct spa_process_latency_info *info) +{ + bool ns_changed = this->process_latency.ns != info->ns; + struct port *port = &this->ports[0]; + + if (this->process_latency.quantum == info->quantum && + this->process_latency.rate == info->rate && + !ns_changed) + return; + + this->process_latency = *info; + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + if (ns_changed) + this->params[NODE_Props].user++; + this->params[NODE_ProcessLatency].user++; + + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->params[PORT_Latency].user++; +} + +static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct state *this = object; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + switch (id) { + case SPA_PARAM_Props: + { + struct props *p = &this->props; + struct spa_pod *params = NULL; + int64_t lat_ns = -1; + + if (param == NULL) { + reset_props(p); + return 0; + } + + spa_pod_parse_object(param, + SPA_TYPE_OBJECT_Props, NULL, + SPA_PROP_latencyOffsetNsec, SPA_POD_OPT_Long(&lat_ns), + SPA_PROP_params, SPA_POD_OPT_Pod(¶ms)); + + spa_avb_parse_prop_params(this, params); + if (lat_ns != -1) { + struct spa_process_latency_info info; + info = this->process_latency; + info.ns = lat_ns; + handle_process_latency(this, &info); + } + emit_node_info(this, false); + emit_port_info(this, &this->ports[0], false); + break; + } + case SPA_PARAM_ProcessLatency: + { + struct spa_process_latency_info info; + if ((res = spa_process_latency_parse(param, &info)) < 0) + return res; + + handle_process_latency(this, &info); + + emit_node_info(this, false); + emit_port_info(this, &this->ports[0], false); + break; + } + default: + return -ENOENT; + } + return 0; +} + +static int impl_node_send_command(void *object, const struct spa_command *command) +{ + struct state *this = object; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(command != NULL, -EINVAL); + + switch (SPA_NODE_COMMAND_ID(command)) { + case SPA_NODE_COMMAND_ParamBegin: + break; + case SPA_NODE_COMMAND_ParamEnd: + break; + case SPA_NODE_COMMAND_Start: + if (!this->ports[0].have_format) + return -EIO; + if (this->ports[0].n_buffers == 0) + return -EIO; + if ((res = spa_avb_start(this)) < 0) + return res; + break; + case SPA_NODE_COMMAND_Suspend: + case SPA_NODE_COMMAND_Pause: + if ((res = spa_avb_pause(this)) < 0) + return res; + break; + default: + return -ENOTSUP; + } + return 0; +} + + +static int +impl_node_add_listener(void *object, + struct spa_hook *listener, + const struct spa_node_events *events, + void *data) +{ + struct state *this = object; + struct spa_hook_list save; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_hook_list_isolate(&this->hooks, &save, listener, events, data); + + emit_node_info(this, true); + emit_port_info(this, &this->ports[0], true); + + spa_hook_list_join(&this->hooks, &save); + + return 0; +} + +static int +impl_node_set_callbacks(void *object, + const struct spa_node_callbacks *callbacks, + void *data) +{ + struct state *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); + + return 0; +} + +static int +impl_node_sync(void *object, int seq) +{ + struct state *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_node_emit_result(&this->hooks, seq, 0, 0, NULL); + + return 0; +} + +static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, + const struct spa_dict *props) +{ + return -ENOTSUP; +} + +static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) +{ + return -ENOTSUP; +} + +static int +impl_node_port_enum_params(void *object, int seq, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + + struct state *this = object; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_result_node_params result; + uint32_t count = 0; + struct port *port; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + result.id = id; + result.next = start; + next: + result.index = result.next++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + switch (id) { + case SPA_PARAM_EnumFormat: + return spa_avb_enum_format(this, seq, start, num, filter); + + case SPA_PARAM_Format: + if (!port->have_format) + return -EIO; + if (result.index > 0) + return 0; + + param = spa_format_audio_raw_build(&b, id, + &port->current_format.info.raw); + break; + + case SPA_PARAM_Buffers: + if (!port->have_format) + return -EIO; + if (result.index > 0) + return 0; + + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamBuffers, id, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(this->blocks), + SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( + this->quantum_limit * this->stride, + 16 * this->stride, + INT32_MAX), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(this->stride)); + break; + + case SPA_PARAM_Meta: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamMeta, id, + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), + SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); + break; + default: + return 0; + } + break; + + case SPA_PARAM_IO: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); + break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_RateMatch), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_rate_match))); + break; + default: + return 0; + } + break; + + case SPA_PARAM_Latency: + switch (result.index) { + case 0: case 1: + { + struct spa_latency_info latency = this->latency[result.index]; + if (latency.direction == SPA_DIRECTION_INPUT) + spa_process_latency_info_add(&this->process_latency, &latency); + param = spa_latency_build(&b, id, &latency); + break; + } + default: + return 0; + } + break; + + default: + return -ENOENT; + } + + if (spa_pod_filter(&b, &result.param, param, filter) < 0) + goto next; + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + return 0; +} + +static int clear_buffers(struct state *this, struct port *port) +{ + if (port->n_buffers > 0) { + spa_list_init(&port->ready); + port->n_buffers = 0; + } + return 0; +} + +static int port_set_format(void *object, struct port *port, + uint32_t flags, const struct spa_pod *format) +{ + struct state *this = object; + int err; + + if (format == NULL) { + if (!port->have_format) + return 0; + + spa_log_debug(this->log, "clear format"); + port->have_format = false; + spa_avb_clear_format(this); + clear_buffers(this, port); + } else { + struct spa_audio_info info = { 0 }; + + if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) + return err; + + if (info.media_type != SPA_MEDIA_TYPE_audio || + info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -EINVAL; + + if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) + return -EINVAL; + + if ((err = spa_avb_set_format(this, &info, flags)) < 0) + return err; + + port->current_format = info; + port->have_format = true; + } + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; + emit_node_info(this, false); + + port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; + port->info.rate = SPA_FRACTION(1, this->rate); + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + if (port->have_format) { + port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); + port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); + port->params[PORT_Latency].user++; + } else { + port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + } + emit_port_info(this, port, false); + + return 0; +} + +static int +impl_node_port_set_param(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct state *this = object; + struct port *port; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + switch (id) { + case SPA_PARAM_Format: + res = port_set_format(this, port, flags, param); + break; + case SPA_PARAM_Latency: + { + struct spa_latency_info info; + if ((res = spa_latency_parse(param, &info)) < 0) + return res; + if (direction == info.direction) + return -EINVAL; + + this->latency[info.direction] = info; + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->params[PORT_Latency].user++; + emit_port_info(this, port, false); + break; + } + default: + res = -ENOENT; + break; + } + return res; +} + +static int +impl_node_port_use_buffers(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t flags, + struct spa_buffer **buffers, uint32_t n_buffers) +{ + struct state *this = object; + struct port *port; + uint32_t i; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + spa_log_debug(this->log, "%p: use %d buffers", this, n_buffers); + + if (!port->have_format) + return -EIO; + + if (n_buffers == 0) { + spa_avb_pause(this); + clear_buffers(this, port); + return 0; + } + + for (i = 0; i < n_buffers; i++) { + struct buffer *b = &port->buffers[i]; + struct spa_data *d = buffers[i]->datas; + + b->buf = buffers[i]; + b->id = i; + b->flags = BUFFER_FLAG_OUT; + + b->h = spa_buffer_find_meta_data(b->buf, SPA_META_Header, sizeof(*b->h)); + + if (d[0].data == NULL) { + spa_log_error(this->log, "%p: need mapped memory", this); + return -EINVAL; + } + spa_log_debug(this->log, "%p: %d %p data:%p", this, i, b->buf, d[0].data); + } + port->n_buffers = n_buffers; + + return 0; +} + +static int +impl_node_port_set_io(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t id, + void *data, size_t size) +{ + struct state *this = object; + struct port *port; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + spa_log_debug(this->log, "%p: io %d %p %zd", this, id, data, size); + + switch (id) { + case SPA_IO_Buffers: + port->io = data; + break; + case SPA_IO_RateMatch: + port->rate_match = data; + break; + default: + return -ENOENT; + } + return 0; +} + +static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) +{ + return -ENOTSUP; +} + +static int impl_node_process(void *object) +{ + struct state *this = object; + struct port *port; + struct spa_io_buffers *io; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + port = GET_PORT(this, SPA_DIRECTION_INPUT, 0); + if ((io = port->io) == NULL) + return -EIO; + + spa_log_trace_fp(this->log, "%p: process %d %d/%d", this, io->status, + io->buffer_id, port->n_buffers); + + if (this->position && this->position->clock.flags & SPA_IO_CLOCK_FLAG_FREEWHEEL) { + io->status = SPA_STATUS_NEED_DATA; + return SPA_STATUS_HAVE_DATA; + } + if (io->status == SPA_STATUS_HAVE_DATA && + io->buffer_id < port->n_buffers) { + struct buffer *b = &port->buffers[io->buffer_id]; + + if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { + spa_log_warn(this->log, "%p: buffer %u in use", + this, io->buffer_id); + io->status = -EINVAL; + return -EINVAL; + } + spa_log_trace_fp(this->log, "%p: queue buffer %u", this, io->buffer_id); + spa_list_append(&port->ready, &b->link); + SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); + io->buffer_id = SPA_ID_INVALID; + + spa_avb_write(this); + + io->status = SPA_STATUS_OK; + } + return SPA_STATUS_HAVE_DATA; +} + +static const struct spa_node_methods impl_node = { + SPA_VERSION_NODE_METHODS, + .add_listener = impl_node_add_listener, + .set_callbacks = impl_node_set_callbacks, + .sync = impl_node_sync, + .enum_params = impl_node_enum_params, + .set_param = impl_node_set_param, + .set_io = impl_node_set_io, + .send_command = impl_node_send_command, + .add_port = impl_node_add_port, + .remove_port = impl_node_remove_port, + .port_enum_params = impl_node_port_enum_params, + .port_set_param = impl_node_port_set_param, + .port_use_buffers = impl_node_port_use_buffers, + .port_set_io = impl_node_port_set_io, + .port_reuse_buffer = impl_node_port_reuse_buffer, + .process = impl_node_process, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct state *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + this = (struct state *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) + *interface = &this->node; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + struct state *this; + spa_return_val_if_fail(handle != NULL, -EINVAL); + this = (struct state *) handle; + spa_avb_clear(this); + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct state); +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) +{ + struct state *this; + struct port *port; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + this = (struct state *) handle; + + this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + avb_log_topic_init(this->log); + + this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); + this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); + + if (this->data_loop == NULL) { + spa_log_error(this->log, "a data loop is needed"); + return -EINVAL; + } + if (this->data_system == NULL) { + spa_log_error(this->log, "a data system is needed"); + return -EINVAL; + } + + this->node.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Node, + SPA_VERSION_NODE, + &impl_node, this); + + spa_hook_list_init(&this->hooks); + + + this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | + SPA_NODE_CHANGE_MASK_PROPS | + SPA_NODE_CHANGE_MASK_PARAMS; + this->info = SPA_NODE_INFO_INIT(); + this->info.max_input_ports = 1; + this->info.flags = SPA_NODE_FLAG_RT; + this->params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); + this->params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); + this->params[NODE_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); + this->params[NODE_ProcessLatency] = SPA_PARAM_INFO(SPA_PARAM_ProcessLatency, SPA_PARAM_INFO_READWRITE); + this->info.params = this->params; + this->info.n_params = N_NODE_PARAMS; + + reset_props(&this->props); + + port = GET_PORT(this, SPA_DIRECTION_INPUT, 0); + port->direction = SPA_DIRECTION_INPUT; + + port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | + SPA_PORT_CHANGE_MASK_PARAMS; + port->info = SPA_PORT_INFO_INIT(); + port->info.flags = SPA_PORT_FLAG_LIVE | + SPA_PORT_FLAG_PHYSICAL | + SPA_PORT_FLAG_TERMINAL; + port->params[PORT_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); + port->params[PORT_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); + port->params[PORT_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); + port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + port->params[PORT_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); + port->info.params = port->params; + port->info.n_params = N_PORT_PARAMS; + + spa_list_init(&port->ready); + + this->latency[port->direction] = SPA_LATENCY_INFO( + port->direction, + .min_quantum = 1.0f, + .max_quantum = 1.0f); + this->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); + + return spa_avb_init(this, info); +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE_INTERFACE_Node,}, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *info = &impl_interfaces[*index]; + break; + default: + return 0; + } + (*index)++; + return 1; +} + +static const struct spa_dict_item info_items[] = { + { SPA_KEY_FACTORY_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { SPA_KEY_FACTORY_DESCRIPTION, "Play audio with AVB" }, + { SPA_KEY_FACTORY_USAGE, "[]" }, +}; + +static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); + +const struct spa_handle_factory spa_avb_sink_factory = { + SPA_VERSION_HANDLE_FACTORY, + "avb.pcm.sink", + &info, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; diff --git a/spa/plugins/avb/avb-pcm-source.c b/spa/plugins/avb/avb-pcm-source.c new file mode 100644 index 0000000000000000000000000000000000000000..5bf0a6e51bb64adaaa91bcb3d51e87019ba90e13 --- /dev/null +++ b/spa/plugins/avb/avb-pcm-source.c @@ -0,0 +1,911 @@ +/* Spa AVB PCM Source + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <stddef.h> + +#include <spa/node/node.h> +#include <spa/node/utils.h> +#include <spa/node/keys.h> +#include <spa/monitor/device.h> +#include <spa/utils/keys.h> +#include <spa/utils/names.h> +#include <spa/utils/string.h> +#include <spa/param/audio/format.h> +#include <spa/pod/filter.h> +#include <spa/debug/pod.h> + +#include "avb-pcm.h" + +#define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) == 0) +#define GET_PORT(this,d,p) (&this->ports[p]) + +static void reset_props(struct props *props) +{ + snprintf(props->ifname, sizeof(props->ifname), "%s", DEFAULT_IFNAME); + parse_addr(props->addr, DEFAULT_ADDR); + props->prio = DEFAULT_PRIO; + parse_streamid(&props->streamid, DEFAULT_STREAMID); + props->mtt = DEFAULT_MTT; + props->t_uncertainty = DEFAULT_TU; + props->frames_per_pdu = DEFAULT_FRAMES_PER_PDU; +} + +static void emit_node_info(struct state *this, bool full) +{ + uint64_t old = full ? this->info.change_mask : 0; + + if (full) + this->info.change_mask = this->info_all; + if (this->info.change_mask) { + struct spa_dict_item items[4]; + uint32_t i, n_items = 0; + + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "avb"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Source"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_DRIVER, "true"); + this->info.props = &SPA_DICT_INIT(items, n_items); + + 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_node_emit_info(&this->hooks, &this->info); + + this->info.change_mask = old; + } +} + +static void emit_port_info(struct state *this, struct port *port, bool full) +{ + uint64_t old = full ? port->info.change_mask : 0; + + if (full) + port->info.change_mask = port->info_all; + if (port->info.change_mask) { + uint32_t i; + + if (port->info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { + for (i = 0; i < port->info.n_params; i++) { + if (port->params[i].user > 0) { + port->params[i].flags ^= SPA_PARAM_INFO_SERIAL; + port->params[i].user = 0; + } + } + } + spa_node_emit_port_info(&this->hooks, + port->direction, port->id, &port->info); + port->info.change_mask = old; + } +} + +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 state *this = object; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[4096]; + struct spa_result_node_params result; + uint32_t count = 0; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + + result.id = id; + result.next = start; + next: + result.index = result.next++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + switch (id) { + case SPA_PARAM_PropInfo: + { + switch (result.index) { + default: + param = spa_avb_enum_propinfo(this, result.index, &b); + if (param == NULL) + return 0; + } + break; + } + case SPA_PARAM_Props: + { + struct spa_pod_frame f; + + switch (result.index) { + case 0: + spa_pod_builder_push_object(&b, &f, + SPA_TYPE_OBJECT_Props, id); + spa_pod_builder_add(&b, + SPA_PROP_latencyOffsetNsec, SPA_POD_Long(this->process_latency.ns), + 0); + spa_avb_add_prop_params(this, &b); + param = spa_pod_builder_pop(&b, &f); + break; + default: + return 0; + } + break; + } + case SPA_PARAM_IO: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); + break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Position), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_position))); + break; + default: + return 0; + } + break; + + case SPA_PARAM_ProcessLatency: + switch (result.index) { + case 0: + param = spa_process_latency_build(&b, id, &this->process_latency); + break; + default: + return 0; + } + break; + + default: + return -ENOENT; + } + + if (spa_pod_filter(&b, &result.param, param, filter) < 0) + goto next; + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + return 0; +} + +static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) +{ + struct state *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + switch (id) { + case SPA_IO_Clock: + this->clock = data; + break; + case SPA_IO_Position: + this->position = data; + break; + default: + return -ENOENT; + } + spa_avb_reassign_follower(this); + + return 0; +} + +static void handle_process_latency(struct state *this, + const struct spa_process_latency_info *info) +{ + bool ns_changed = this->process_latency.ns != info->ns; + struct port *port = &this->ports[0]; + + if (this->process_latency.quantum == info->quantum && + this->process_latency.rate == info->rate && + !ns_changed) + return; + + this->process_latency = *info; + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + if (ns_changed) + this->params[NODE_Props].user++; + this->params[NODE_ProcessLatency].user++; + + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->params[PORT_Latency].user++; +} + +static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct state *this = object; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + switch (id) { + case SPA_PARAM_Props: + { + struct props *p = &this->props; + struct spa_pod *params = NULL; + int64_t lat_ns = -1; + + if (param == NULL) { + reset_props(p); + return 0; + } + + spa_pod_parse_object(param, + SPA_TYPE_OBJECT_Props, NULL, + SPA_PROP_latencyOffsetNsec, SPA_POD_OPT_Long(&lat_ns), + SPA_PROP_params, SPA_POD_OPT_Pod(¶ms)); + + spa_avb_parse_prop_params(this, params); + if (lat_ns != -1) { + struct spa_process_latency_info info; + info = this->process_latency; + info.ns = lat_ns; + handle_process_latency(this, &info); + } + emit_node_info(this, false); + emit_port_info(this, &this->ports[0], false); + break; + } + case SPA_PARAM_ProcessLatency: + { + struct spa_process_latency_info info; + if ((res = spa_process_latency_parse(param, &info)) < 0) + return res; + + handle_process_latency(this, &info); + + emit_node_info(this, false); + emit_port_info(this, &this->ports[0], false); + break; + } + default: + return -ENOENT; + } + return 0; +} + +static int impl_node_send_command(void *object, const struct spa_command *command) +{ + struct state *this = object; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(command != NULL, -EINVAL); + + switch (SPA_NODE_COMMAND_ID(command)) { + case SPA_NODE_COMMAND_ParamBegin: + break; + case SPA_NODE_COMMAND_ParamEnd: + break; + case SPA_NODE_COMMAND_Start: + if (!this->ports[0].have_format) + return -EIO; + if (this->ports[0].n_buffers == 0) + return -EIO; + if ((res = spa_avb_start(this)) < 0) + return res; + break; + case SPA_NODE_COMMAND_Suspend: + case SPA_NODE_COMMAND_Pause: + if ((res = spa_avb_pause(this)) < 0) + return res; + break; + default: + return -ENOTSUP; + } + return 0; +} + + +static int +impl_node_add_listener(void *object, + struct spa_hook *listener, + const struct spa_node_events *events, + void *data) +{ + struct state *this = object; + struct spa_hook_list save; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_hook_list_isolate(&this->hooks, &save, listener, events, data); + + emit_node_info(this, true); + emit_port_info(this, &this->ports[0], true); + + spa_hook_list_join(&this->hooks, &save); + + return 0; +} + +static int +impl_node_set_callbacks(void *object, + const struct spa_node_callbacks *callbacks, + void *data) +{ + struct state *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); + + return 0; +} + +static int +impl_node_sync(void *object, int seq) +{ + struct state *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_node_emit_result(&this->hooks, seq, 0, 0, NULL); + + return 0; +} + +static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, + const struct spa_dict *props) +{ + return -ENOTSUP; +} + +static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) +{ + return -ENOTSUP; +} + +static int +impl_node_port_enum_params(void *object, int seq, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + + struct state *this = object; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_result_node_params result; + uint32_t count = 0; + struct port *port; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + result.id = id; + result.next = start; + next: + result.index = result.next++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + switch (id) { + case SPA_PARAM_EnumFormat: + return spa_avb_enum_format(this, seq, start, num, filter); + + case SPA_PARAM_Format: + if (!port->have_format) + return -EIO; + if (result.index > 0) + return 0; + + param = spa_format_audio_raw_build(&b, id, + &port->current_format.info.raw); + break; + + case SPA_PARAM_Buffers: + if (!port->have_format) + return -EIO; + if (result.index > 0) + return 0; + + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamBuffers, id, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(this->blocks), + SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( + this->quantum_limit * this->stride, + 16 * this->stride, + INT32_MAX), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(this->stride)); + break; + + case SPA_PARAM_Meta: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamMeta, id, + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), + SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); + break; + default: + return 0; + } + break; + + case SPA_PARAM_IO: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); + break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_RateMatch), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_rate_match))); + break; + default: + return 0; + } + break; + + case SPA_PARAM_Latency: + switch (result.index) { + case 0: case 1: + { + struct spa_latency_info latency = this->latency[result.index]; + if (latency.direction == SPA_DIRECTION_OUTPUT) + spa_process_latency_info_add(&this->process_latency, &latency); + param = spa_latency_build(&b, id, &latency); + break; + } + default: + return 0; + } + break; + + default: + return -ENOENT; + } + + if (spa_pod_filter(&b, &result.param, param, filter) < 0) + goto next; + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + return 0; +} + +static int clear_buffers(struct state *this, struct port *port) +{ + if (port->n_buffers > 0) { + spa_list_init(&port->ready); + port->n_buffers = 0; + } + return 0; +} + +static int port_set_format(void *object, struct port *port, + uint32_t flags, const struct spa_pod *format) +{ + struct state *this = object; + int err; + + if (format == NULL) { + if (!port->have_format) + return 0; + + spa_log_debug(this->log, "clear format"); + port->have_format = false; + spa_avb_clear_format(this); + clear_buffers(this, port); + } else { + struct spa_audio_info info = { 0 }; + + if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) + return err; + + if (info.media_type != SPA_MEDIA_TYPE_audio || + info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -EINVAL; + + if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) + return -EINVAL; + + if ((err = spa_avb_set_format(this, &info, flags)) < 0) + return err; + + port->current_format = info; + port->have_format = true; + } + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; + emit_node_info(this, false); + + port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; + port->info.rate = SPA_FRACTION(1, this->rate); + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + if (port->have_format) { + port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); + port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); + port->params[PORT_Latency].user++; + } else { + port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + } + emit_port_info(this, port, false); + + return 0; +} + +static int +impl_node_port_set_param(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct state *this = object; + struct port *port; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + switch (id) { + case SPA_PARAM_Format: + res = port_set_format(this, port, flags, param); + break; + case SPA_PARAM_Latency: + { + struct spa_latency_info info; + if ((res = spa_latency_parse(param, &info)) < 0) + return res; + if (direction == info.direction) + return -EINVAL; + + this->latency[info.direction] = info; + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->params[PORT_Latency].user++; + emit_port_info(this, port, false); + break; + } + default: + res = -ENOENT; + break; + } + return res; +} + +static int +impl_node_port_use_buffers(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t flags, + struct spa_buffer **buffers, uint32_t n_buffers) +{ + struct state *this = object; + struct port *port; + uint32_t i; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + spa_log_debug(this->log, "%p: use %d buffers", this, n_buffers); + + if (!port->have_format) + return -EIO; + + if (n_buffers == 0) { + spa_avb_pause(this); + clear_buffers(this, port); + return 0; + } + + for (i = 0; i < n_buffers; i++) { + struct buffer *b = &port->buffers[i]; + struct spa_data *d = buffers[i]->datas; + + b->buf = buffers[i]; + b->id = i; + b->flags = BUFFER_FLAG_OUT; + + b->h = spa_buffer_find_meta_data(b->buf, SPA_META_Header, sizeof(*b->h)); + + if (d[0].data == NULL) { + spa_log_error(this->log, "%p: need mapped memory", this); + return -EINVAL; + } + spa_log_debug(this->log, "%p: %d %p data:%p", this, i, b->buf, d[0].data); + } + port->n_buffers = n_buffers; + + return 0; +} + +static int +impl_node_port_set_io(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t id, + void *data, size_t size) +{ + struct state *this = object; + struct port *port; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + spa_log_debug(this->log, "%p: io %d %p %zd", this, id, data, size); + + switch (id) { + case SPA_IO_Buffers: + port->io = data; + break; + case SPA_IO_RateMatch: + port->rate_match = data; + break; + default: + return -ENOENT; + } + return 0; +} + +static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) +{ + return -ENOTSUP; +} + +static int impl_node_process(void *object) +{ + struct state *this = object; + struct port *port; + struct spa_io_buffers *io; + struct buffer *b; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + port = GET_PORT(this, SPA_DIRECTION_OUTPUT, 0); + if ((io = port->io) == NULL) + return -EIO; + + spa_log_trace_fp(this->log, "%p: process %d %d/%d %d", this, io->status, + io->buffer_id, port->n_buffers, this->following); + + if (io->status == SPA_STATUS_HAVE_DATA) + return SPA_STATUS_HAVE_DATA; + + if (io->buffer_id < port->n_buffers) { + spa_avb_recycle_buffer(this, port, io->buffer_id); + io->buffer_id = SPA_ID_INVALID; + } + + if (spa_list_is_empty(&port->ready) && this->following) { + spa_avb_read(this); + } + if (spa_list_is_empty(&port->ready) || !this->following) + return SPA_STATUS_OK; + + b = spa_list_first(&port->ready, struct buffer, link); + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); + + spa_log_trace_fp(this->log, "%p: dequeue buffer %d", this, b->id); + + io->buffer_id = b->id; + io->status = SPA_STATUS_HAVE_DATA; + + return SPA_STATUS_HAVE_DATA; +} + +static const struct spa_node_methods impl_node = { + SPA_VERSION_NODE_METHODS, + .add_listener = impl_node_add_listener, + .set_callbacks = impl_node_set_callbacks, + .sync = impl_node_sync, + .enum_params = impl_node_enum_params, + .set_param = impl_node_set_param, + .set_io = impl_node_set_io, + .send_command = impl_node_send_command, + .add_port = impl_node_add_port, + .remove_port = impl_node_remove_port, + .port_enum_params = impl_node_port_enum_params, + .port_set_param = impl_node_port_set_param, + .port_use_buffers = impl_node_port_use_buffers, + .port_set_io = impl_node_port_set_io, + .port_reuse_buffer = impl_node_port_reuse_buffer, + .process = impl_node_process, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct state *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + this = (struct state *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) + *interface = &this->node; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + struct state *this; + spa_return_val_if_fail(handle != NULL, -EINVAL); + this = (struct state *) handle; + spa_avb_clear(this); + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct state); +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) +{ + struct state *this; + struct port *port; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + this = (struct state *) handle; + + this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + avb_log_topic_init(this->log); + + this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); + this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); + + if (this->data_loop == NULL) { + spa_log_error(this->log, "a data loop is needed"); + return -EINVAL; + } + if (this->data_system == NULL) { + spa_log_error(this->log, "a data system is needed"); + return -EINVAL; + } + + this->node.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Node, + SPA_VERSION_NODE, + &impl_node, this); + + spa_hook_list_init(&this->hooks); + + this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | + SPA_NODE_CHANGE_MASK_PROPS | + SPA_NODE_CHANGE_MASK_PARAMS; + this->info = SPA_NODE_INFO_INIT(); + this->info.max_output_ports = 1; + this->info.flags = SPA_NODE_FLAG_RT; + this->params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); + this->params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); + this->params[NODE_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); + this->params[NODE_ProcessLatency] = SPA_PARAM_INFO(SPA_PARAM_ProcessLatency, SPA_PARAM_INFO_READWRITE); + this->info.params = this->params; + this->info.n_params = N_NODE_PARAMS; + + reset_props(&this->props); + + port = GET_PORT(this, SPA_DIRECTION_OUTPUT, 0); + port->direction = SPA_DIRECTION_OUTPUT; + + port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | + SPA_PORT_CHANGE_MASK_PARAMS; + port->info = SPA_PORT_INFO_INIT(); + port->info.flags = SPA_PORT_FLAG_LIVE | + SPA_PORT_FLAG_PHYSICAL | + SPA_PORT_FLAG_TERMINAL; + port->params[PORT_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); + port->params[PORT_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); + port->params[PORT_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); + port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + port->params[PORT_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); + port->info.params = port->params; + port->info.n_params = N_PORT_PARAMS; + + spa_list_init(&port->ready); + + this->latency[port->direction] = SPA_LATENCY_INFO( + port->direction, + .min_quantum = 1.0f, + .max_quantum = 1.0f); + this->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); + + return spa_avb_init(this, info); +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE_INTERFACE_Node,}, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *info = &impl_interfaces[*index]; + break; + default: + return 0; + } + (*index)++; + return 1; +} + +static const struct spa_dict_item info_items[] = { + { SPA_KEY_FACTORY_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { SPA_KEY_FACTORY_DESCRIPTION, "Play audio with AVB" }, + { SPA_KEY_FACTORY_USAGE, "[]" }, +}; + +static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); + +const struct spa_handle_factory spa_avb_source_factory = { + SPA_VERSION_HANDLE_FACTORY, + "avb.pcm.source", + &info, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; diff --git a/spa/plugins/avb/avb-pcm.c b/spa/plugins/avb/avb-pcm.c new file mode 100644 index 0000000000000000000000000000000000000000..c7ccb05b666753640a1ceafe5f8d793747de81da --- /dev/null +++ b/spa/plugins/avb/avb-pcm.c @@ -0,0 +1,1217 @@ +/* Spa AVB PCM + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sched.h> +#include <errno.h> +#include <getopt.h> +#include <sys/time.h> +#include <math.h> +#include <limits.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> + +#include <spa/pod/filter.h> +#include <spa/utils/string.h> +#include <spa/support/system.h> +#include <spa/utils/keys.h> + +#include "avb-pcm.h" + +#define TAI_OFFSET (37ULL * SPA_NSEC_PER_SEC) +#define TAI_TO_UTC(t) (t - TAI_OFFSET) + +static int avb_set_param(struct state *state, const char *k, const char *s) +{ + struct props *p = &state->props; + int fmt_change = 0; + if (spa_streq(k, SPA_KEY_AUDIO_CHANNELS)) { + state->default_channels = atoi(s); + fmt_change++; + } else if (spa_streq(k, SPA_KEY_AUDIO_RATE)) { + state->default_rate = atoi(s); + fmt_change++; + } else if (spa_streq(k, SPA_KEY_AUDIO_FORMAT)) { + state->default_format = spa_avb_format_from_name(s, strlen(s)); + fmt_change++; + } else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) { + spa_avb_parse_position(&state->default_pos, s, strlen(s)); + fmt_change++; + } else if (spa_streq(k, SPA_KEY_AUDIO_ALLOWED_RATES)) { + state->n_allowed_rates = spa_avb_parse_rates(state->allowed_rates, + MAX_RATES, s, strlen(s)); + fmt_change++; + } else if (spa_streq(k, "avb.ifname")) { + snprintf(p->ifname, sizeof(p->ifname), "%s", s); + } else if (spa_streq(k, "avb.macaddr")) { + parse_addr(p->addr, s); + } else if (spa_streq(k, "avb.prio")) { + p->prio = atoi(s); + } else if (spa_streq(k, "avb.streamid")) { + parse_streamid(&p->streamid, s); + } else if (spa_streq(k, "avb.mtt")) { + p->mtt = atoi(s); + } else if (spa_streq(k, "avb.time-uncertainty")) { + p->t_uncertainty = atoi(s); + } else if (spa_streq(k, "avb.frames-per-pdu")) { + p->frames_per_pdu = atoi(s); + } else if (spa_streq(k, "avb.ptime-tolerance")) { + p->ptime_tolerance = atoi(s); + } else if (spa_streq(k, "latency.internal.rate")) { + state->process_latency.rate = atoi(s); + } else if (spa_streq(k, "latency.internal.ns")) { + state->process_latency.ns = atoi(s); + } else if (spa_streq(k, "clock.name")) { + spa_scnprintf(state->clock_name, + sizeof(state->clock_name), "%s", s); + } else + return 0; + + if (fmt_change > 0) { + struct port *port = &state->ports[0]; + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->params[PORT_EnumFormat].user++; + } + return 1; +} + +static int position_to_string(struct channel_map *map, char *val, size_t len) +{ + uint32_t i, o = 0; + int r; + o += snprintf(val, len, "[ "); + for (i = 0; i < map->channels; i++) { + r = snprintf(val+o, len-o, "%s%s", i == 0 ? "" : ", ", + spa_debug_type_find_short_name(spa_type_audio_channel, + map->pos[i])); + if (r < 0 || o + r >= len) + return -ENOSPC; + o += r; + } + if (len > o) + o += snprintf(val+o, len-o, " ]"); + return 0; +} + +static int uint32_array_to_string(uint32_t *vals, uint32_t n_vals, char *val, size_t len) +{ + uint32_t i, o = 0; + int r; + o += snprintf(val, len, "[ "); + for (i = 0; i < n_vals; i++) { + r = snprintf(val+o, len-o, "%s%d", i == 0 ? "" : ", ", vals[i]); + if (r < 0 || o + r >= len) + return -ENOSPC; + o += r; + } + if (len > o) + o += snprintf(val+o, len-o, " ]"); + return 0; +} + +struct spa_pod *spa_avb_enum_propinfo(struct state *state, + uint32_t idx, struct spa_pod_builder *b) +{ + struct spa_pod *param; + struct props *p = &state->props; + char tmp[128]; + + switch (idx) { + case 0: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_CHANNELS), + SPA_PROP_INFO_description, SPA_POD_String("Audio Channels"), + SPA_PROP_INFO_type, SPA_POD_Int(state->default_channels), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 1: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_RATE), + SPA_PROP_INFO_description, SPA_POD_String("Audio Rate"), + SPA_PROP_INFO_type, SPA_POD_Int(state->default_rate), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 2: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_FORMAT), + SPA_PROP_INFO_description, SPA_POD_String("Audio Format"), + SPA_PROP_INFO_type, SPA_POD_String( + spa_debug_type_find_short_name(spa_type_audio_format, + state->default_format)), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 3: + { + char buf[1024]; + position_to_string(&state->default_pos, buf, sizeof(buf)); + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_POSITION), + SPA_PROP_INFO_description, SPA_POD_String("Audio Position"), + SPA_PROP_INFO_type, SPA_POD_String(buf), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + } + case 4: + { + char buf[1024]; + uint32_array_to_string(state->allowed_rates, state->n_allowed_rates, buf, sizeof(buf)); + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_ALLOWED_RATES), + SPA_PROP_INFO_description, SPA_POD_String("Audio Allowed Rates"), + SPA_PROP_INFO_type, SPA_POD_String(buf), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + } + case 5: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("avb.ifname"), + SPA_PROP_INFO_description, SPA_POD_String("The AVB interface name"), + SPA_PROP_INFO_type, SPA_POD_Stringn(p->ifname, sizeof(p->ifname)), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 6: + format_addr(tmp, sizeof(tmp), p->addr); + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("avb.macaddr"), + SPA_PROP_INFO_description, SPA_POD_String("The AVB MAC address"), + SPA_PROP_INFO_type, SPA_POD_String(tmp), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 7: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("avb.prio"), + SPA_PROP_INFO_description, SPA_POD_String("The AVB stream priority"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->prio, 0, INT32_MAX), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 8: + format_streamid(tmp, sizeof(tmp), p->streamid); + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("avb.streamid"), + SPA_PROP_INFO_description, SPA_POD_String("The AVB stream id"), + SPA_PROP_INFO_type, SPA_POD_String(tmp), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 9: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("avb.mtt"), + SPA_PROP_INFO_description, SPA_POD_String("The AVB mtt"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->mtt, 0, INT32_MAX), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 10: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("avb.time-uncertainty"), + SPA_PROP_INFO_description, SPA_POD_String("The AVB time uncertainty"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->t_uncertainty, 0, INT32_MAX), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 11: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("avb.frames-per-pdu"), + SPA_PROP_INFO_description, SPA_POD_String("The AVB frames per packet"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->frames_per_pdu, 0, INT32_MAX), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 12: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("avb.ptime-tolerance"), + SPA_PROP_INFO_description, SPA_POD_String("The AVB packet tolerance"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->ptime_tolerance, 0, INT32_MAX), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 13: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("latency.internal.rate"), + SPA_PROP_INFO_description, SPA_POD_String("Internal latency in samples"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(state->process_latency.rate, + 0, 65536), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 14: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("latency.internal.ns"), + SPA_PROP_INFO_description, SPA_POD_String("Internal latency in nanoseconds"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Long(state->process_latency.ns, + 0, 2 * SPA_NSEC_PER_SEC), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 15: + param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, + SPA_PROP_INFO_name, SPA_POD_String("clock.name"), + SPA_PROP_INFO_description, SPA_POD_String("The name of the clock"), + SPA_PROP_INFO_type, SPA_POD_String(state->clock_name), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + default: + return NULL; + } + return param; +} + +int spa_avb_add_prop_params(struct state *state, struct spa_pod_builder *b) +{ + struct props *p = &state->props; + struct spa_pod_frame f[1]; + char buf[1024]; + + spa_pod_builder_prop(b, SPA_PROP_params, 0); + spa_pod_builder_push_struct(b, &f[0]); + + spa_pod_builder_string(b, SPA_KEY_AUDIO_CHANNELS); + spa_pod_builder_int(b, state->default_channels); + + spa_pod_builder_string(b, SPA_KEY_AUDIO_RATE); + spa_pod_builder_int(b, state->default_rate); + + spa_pod_builder_string(b, SPA_KEY_AUDIO_FORMAT); + spa_pod_builder_string(b, + spa_debug_type_find_short_name(spa_type_audio_format, + state->default_format)); + + position_to_string(&state->default_pos, buf, sizeof(buf)); + spa_pod_builder_string(b, SPA_KEY_AUDIO_POSITION); + spa_pod_builder_string(b, buf); + + uint32_array_to_string(state->allowed_rates, state->n_allowed_rates, + buf, sizeof(buf)); + spa_pod_builder_string(b, SPA_KEY_AUDIO_ALLOWED_RATES); + spa_pod_builder_string(b, buf); + + spa_pod_builder_string(b, "avb.ifname"); + spa_pod_builder_string(b, p->ifname); + + format_addr(buf, sizeof(buf), p->addr); + spa_pod_builder_string(b, "avb.macadr"); + spa_pod_builder_string(b, buf); + + spa_pod_builder_string(b, "avb.prio"); + spa_pod_builder_int(b, p->prio); + + format_streamid(buf, sizeof(buf), p->streamid); + spa_pod_builder_string(b, "avb.streamid"); + spa_pod_builder_string(b, buf); + spa_pod_builder_string(b, "avb.mtt"); + spa_pod_builder_int(b, p->mtt); + spa_pod_builder_string(b, "avb.time-uncertainty"); + spa_pod_builder_int(b, p->t_uncertainty); + spa_pod_builder_string(b, "avb.frames-per-pdu"); + spa_pod_builder_int(b, p->frames_per_pdu); + spa_pod_builder_string(b, "avb.ptime-tolerance"); + spa_pod_builder_int(b, p->ptime_tolerance); + + spa_pod_builder_string(b, "latency.internal.rate"); + spa_pod_builder_int(b, state->process_latency.rate); + + spa_pod_builder_string(b, "latency.internal.ns"); + spa_pod_builder_long(b, state->process_latency.ns); + + spa_pod_builder_string(b, "clock.name"); + spa_pod_builder_string(b, state->clock_name); + + spa_pod_builder_pop(b, &f[0]); + return 0; +} + +int spa_avb_parse_prop_params(struct state *state, struct spa_pod *params) +{ + struct spa_pod_parser prs; + struct spa_pod_frame f; + int changed = 0; + + if (params == NULL) + return 0; + + spa_pod_parser_pod(&prs, params); + if (spa_pod_parser_push_struct(&prs, &f) < 0) + return 0; + + while (true) { + const char *name; + struct spa_pod *pod; + char value[512]; + + if (spa_pod_parser_get_string(&prs, &name) < 0) + break; + + if (spa_pod_parser_get_pod(&prs, &pod) < 0) + break; + if (spa_pod_is_string(pod)) { + spa_pod_copy_string(pod, sizeof(value), value); + } else if (spa_pod_is_int(pod)) { + snprintf(value, sizeof(value), "%d", + SPA_POD_VALUE(struct spa_pod_int, pod)); + } else if (spa_pod_is_long(pod)) { + snprintf(value, sizeof(value), "%"PRIi64, + SPA_POD_VALUE(struct spa_pod_long, pod)); + } else if (spa_pod_is_bool(pod)) { + snprintf(value, sizeof(value), "%s", + SPA_POD_VALUE(struct spa_pod_bool, pod) ? + "true" : "false"); + } else + continue; + + spa_log_info(state->log, "key:'%s' val:'%s'", name, value); + avb_set_param(state, name, value); + changed++; + } + if (changed > 0) { + state->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + state->params[NODE_Props].user++; + } + return changed; +} + +int spa_avb_init(struct state *state, const struct spa_dict *info) +{ + uint32_t i; + + state->quantum_limit = 8192; + for (i = 0; info && i < info->n_items; i++) { + const char *k = info->items[i].key; + const char *s = info->items[i].value; + if (spa_streq(k, "clock.quantum-limit")) { + spa_atou32(s, &state->quantum_limit, 0); + } else { + avb_set_param(state, k, s); + } + } + + state->ringbuffer_size = state->quantum_limit * 64; + state->ringbuffer_data = calloc(1, state->ringbuffer_size * 4); + spa_ringbuffer_init(&state->ring); + return 0; +} + +int spa_avb_clear(struct state *state) +{ + return 0; +} + +static int spa_format_to_aaf(uint32_t format) +{ + switch(format) { + case SPA_AUDIO_FORMAT_F32_BE: return SPA_AVBTP_AAF_FORMAT_FLOAT_32BIT; + case SPA_AUDIO_FORMAT_S32_BE: return SPA_AVBTP_AAF_FORMAT_INT_32BIT; + case SPA_AUDIO_FORMAT_S24_BE: return SPA_AVBTP_AAF_FORMAT_INT_24BIT; + case SPA_AUDIO_FORMAT_S16_BE: return SPA_AVBTP_AAF_FORMAT_INT_16BIT; + default: return SPA_AVBTP_AAF_FORMAT_USER; + } +} + +static int frame_size(uint32_t format) +{ + switch(format) { + case SPA_AUDIO_FORMAT_F32_BE: + case SPA_AUDIO_FORMAT_S32_BE: return 4; + case SPA_AUDIO_FORMAT_S24_BE: return 3; + case SPA_AUDIO_FORMAT_S16_BE: return 2; + default: return 0; + } +} + +static int spa_rate_to_aaf(uint32_t rate) +{ + switch(rate) { + case 8000: return SPA_AVBTP_AAF_PCM_NSR_8KHZ; + case 16000: return SPA_AVBTP_AAF_PCM_NSR_16KHZ; + case 24000: return SPA_AVBTP_AAF_PCM_NSR_24KHZ; + case 32000: return SPA_AVBTP_AAF_PCM_NSR_32KHZ; + case 44100: return SPA_AVBTP_AAF_PCM_NSR_44_1KHZ; + case 48000: return SPA_AVBTP_AAF_PCM_NSR_48KHZ; + case 88200: return SPA_AVBTP_AAF_PCM_NSR_88_2KHZ; + case 96000: return SPA_AVBTP_AAF_PCM_NSR_96KHZ; + case 176400: return SPA_AVBTP_AAF_PCM_NSR_176_4KHZ; + case 192000: return SPA_AVBTP_AAF_PCM_NSR_192KHZ; + default: return SPA_AVBTP_AAF_PCM_NSR_USER; + } +} + +int +spa_avb_enum_format(struct state *state, int seq, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + uint8_t buffer[4096]; + struct spa_pod_builder b = { 0 }; + struct spa_pod_frame f[2]; + struct spa_pod *fmt; + int res = 0; + struct spa_result_node_params result; + uint32_t count = 0; + + result.id = SPA_PARAM_EnumFormat; + result.next = start; + +next: + result.index = result.next++; + + if (result.index > 0) + return 0; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); + spa_pod_builder_add(&b, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + 0); + + spa_pod_builder_prop(&b, SPA_FORMAT_AUDIO_format, 0); + if (state->default_format != 0) { + spa_pod_builder_id(&b, state->default_format); + } else { + spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_Enum, 0); + spa_pod_builder_id(&b, SPA_AUDIO_FORMAT_F32_BE); + spa_pod_builder_id(&b, SPA_AUDIO_FORMAT_F32_BE); + spa_pod_builder_id(&b, SPA_AUDIO_FORMAT_S32_BE); + spa_pod_builder_id(&b, SPA_AUDIO_FORMAT_S24_BE); + spa_pod_builder_id(&b, SPA_AUDIO_FORMAT_S16_BE); + spa_pod_builder_pop(&b, &f[1]); + } + spa_pod_builder_prop(&b, SPA_FORMAT_AUDIO_rate, 0); + if (state->default_rate != 0) { + spa_pod_builder_int(&b, state->default_rate); + } else { + spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_Enum, 0); + spa_pod_builder_int(&b, 48000); + spa_pod_builder_int(&b, 8000); + spa_pod_builder_int(&b, 16000); + spa_pod_builder_int(&b, 24000); + spa_pod_builder_int(&b, 32000); + spa_pod_builder_int(&b, 44100); + spa_pod_builder_int(&b, 48000); + spa_pod_builder_int(&b, 88200); + spa_pod_builder_int(&b, 96000); + spa_pod_builder_int(&b, 176400); + spa_pod_builder_int(&b, 192000); + spa_pod_builder_pop(&b, &f[1]); + } + spa_pod_builder_prop(&b, SPA_FORMAT_AUDIO_channels, 0); + if (state->default_channels != 0) { + spa_pod_builder_int(&b, state->default_channels); + } else { + spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_Range, 0); + spa_pod_builder_int(&b, 8); + spa_pod_builder_int(&b, 2); + spa_pod_builder_int(&b, 32); + spa_pod_builder_pop(&b, &f[1]); + } + fmt = spa_pod_builder_pop(&b, &f[0]); + + if (spa_pod_filter(&b, &result.param, fmt, filter) < 0) + goto next; + + spa_node_emit_result(&state->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + return res; +} + +static int setup_socket(struct state *state) +{ + int fd, res; + struct ifreq req; + struct props *p = &state->props; + + fd = socket(AF_PACKET, SOCK_DGRAM|SOCK_NONBLOCK, htons(ETH_P_TSN)); + if (fd < 0) { + spa_log_error(state->log, "socket() failed: %m"); + return -errno; + } + + snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", p->ifname); + res = ioctl(fd, SIOCGIFINDEX, &req); + if (res < 0) { + spa_log_error(state->log, "SIOCGIFINDEX %s failed: %m", p->ifname); + res = -errno; + goto error_close; + } + + state->sock_addr.sll_family = AF_PACKET; + state->sock_addr.sll_protocol = htons(ETH_P_TSN); + state->sock_addr.sll_halen = ETH_ALEN; + state->sock_addr.sll_ifindex = req.ifr_ifindex; + memcpy(&state->sock_addr.sll_addr, p->addr, ETH_ALEN); + + if (state->ports[0].direction == SPA_DIRECTION_INPUT) { + struct sock_txtime txtime_cfg; + + res = setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &p->prio, + sizeof(p->prio)); + if (res < 0) { + spa_log_error(state->log, "setsockopt(SO_PRIORITY %d) failed: %m", p->prio); + res = -errno; + goto error_close; + } + + txtime_cfg.clockid = CLOCK_TAI; + txtime_cfg.flags = 0; + res = setsockopt(fd, SOL_SOCKET, SO_TXTIME, &txtime_cfg, + sizeof(txtime_cfg)); + if (res < 0) { + spa_log_error(state->log, "setsockopt(SO_TXTIME) failed: %m"); + res = -errno; + goto error_close; + } + } else { + struct packet_mreq mreq = { 0 }; + + res = bind(fd, (struct sockaddr *) &state->sock_addr, + sizeof(state->sock_addr)); + if (res < 0) { + spa_log_error(state->log, "bind() failed: %m"); + res = -errno; + goto error_close; + } + + mreq.mr_ifindex = req.ifr_ifindex; + mreq.mr_type = PACKET_MR_MULTICAST; + mreq.mr_alen = ETH_ALEN; + memcpy(&mreq.mr_address, p->addr, ETH_ALEN); + res = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, + &mreq, sizeof(struct packet_mreq)); + if (res < 0) { + spa_log_error(state->log, "setsockopt(ADD_MEMBERSHIP) failed: %m"); + res = -errno; + goto error_close; + } + } + state->sockfd = fd; + return 0; + +error_close: + close(fd); + return res; +} + +static int setup_packet(struct state *state, struct spa_audio_info *fmt) +{ + struct spa_avbtp_packet_aaf *pdu; + struct props *p = &state->props; + ssize_t payload_size, hdr_size, pdu_size; + + hdr_size = sizeof(*pdu); + payload_size = state->stride * p->frames_per_pdu; + pdu_size = hdr_size + payload_size; + if ((pdu = calloc(1, pdu_size)) == NULL) + return -errno; + + SPA_AVBTP_PACKET_AAF_SET_SUBTYPE(pdu, SPA_AVBTP_SUBTYPE_AAF); + + if (state->ports[0].direction == SPA_DIRECTION_INPUT) { + SPA_AVBTP_PACKET_AAF_SET_SV(pdu, 1); + SPA_AVBTP_PACKET_AAF_SET_STREAM_ID(pdu, p->streamid); + SPA_AVBTP_PACKET_AAF_SET_TV(pdu, 1); + SPA_AVBTP_PACKET_AAF_SET_FORMAT(pdu, spa_format_to_aaf(state->format)); + SPA_AVBTP_PACKET_AAF_SET_NSR(pdu, spa_rate_to_aaf(state->rate)); + SPA_AVBTP_PACKET_AAF_SET_CHAN_PER_FRAME(pdu, state->channels); + SPA_AVBTP_PACKET_AAF_SET_BIT_DEPTH(pdu, frame_size(state->format)*8); + SPA_AVBTP_PACKET_AAF_SET_DATA_LEN(pdu, payload_size); + SPA_AVBTP_PACKET_AAF_SET_SP(pdu, SPA_AVBTP_AAF_PCM_SP_NORMAL); + } + state->pdu = pdu; + state->hdr_size = hdr_size; + state->payload_size = payload_size; + state->pdu_size = pdu_size; + return 0; +} + +static int setup_msg(struct state *state) +{ + state->iov[0].iov_base = state->pdu; + state->iov[0].iov_len = state->hdr_size; + state->iov[1].iov_base = state->pdu->payload; + state->iov[1].iov_len = state->payload_size; + state->iov[2].iov_base = state->pdu->payload; + state->iov[2].iov_len = 0; + state->msg.msg_name = &state->sock_addr; + state->msg.msg_namelen = sizeof(state->sock_addr); + state->msg.msg_iov = state->iov; + state->msg.msg_iovlen = 3; + state->msg.msg_control = state->control; + state->msg.msg_controllen = sizeof(state->control); + state->cmsg = CMSG_FIRSTHDR(&state->msg); + state->cmsg->cmsg_level = SOL_SOCKET; + state->cmsg->cmsg_type = SCM_TXTIME; + state->cmsg->cmsg_len = CMSG_LEN(sizeof(__u64)); + return 0; +} + +int spa_avb_clear_format(struct state *state) +{ + close(state->sockfd); + close(state->timerfd); + free(state->pdu); + + return 0; +} + +int spa_avb_set_format(struct state *state, struct spa_audio_info *fmt, uint32_t flags) +{ + int res; + struct props *p = &state->props; + + state->format = fmt->info.raw.format; + state->rate = fmt->info.raw.rate; + state->channels = fmt->info.raw.channels; + state->blocks = 1; + state->stride = state->channels * frame_size(state->format); + + if ((res = setup_socket(state)) < 0) + return res; + + if ((res = spa_system_timerfd_create(state->data_system, + CLOCK_REALTIME, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK)) < 0) + goto error_close_sockfd; + + state->timerfd = res; + + if ((res = setup_packet(state, fmt)) < 0) + return res; + + if ((res = setup_msg(state)) < 0) + return res; + + state->pdu_period = SPA_NSEC_PER_SEC * p->frames_per_pdu / + state->rate; + + return 0; + +error_close_sockfd: + close(state->sockfd); + return res; +} + +void spa_avb_recycle_buffer(struct state *this, struct port *port, uint32_t buffer_id) +{ + struct buffer *b = &port->buffers[buffer_id]; + + if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { + spa_log_trace_fp(this->log, "%p: recycle buffer %u", this, buffer_id); + spa_list_append(&port->free, &b->link); + SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); + } +} + +static void reset_buffers(struct state *this, struct port *port) +{ + uint32_t i; + + spa_list_init(&port->free); + spa_list_init(&port->ready); + + for (i = 0; i < port->n_buffers; i++) { + struct buffer *b = &port->buffers[i]; + if (port->direction == SPA_DIRECTION_INPUT) { + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); + spa_node_call_reuse_buffer(&this->callbacks, 0, b->id); + } else { + spa_list_append(&port->free, &b->link); + SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); + } + } +} + +static inline bool is_pdu_valid(struct state *state) +{ + uint8_t seq_num; + + seq_num = SPA_AVBTP_PACKET_AAF_GET_SEQ_NUM(state->pdu); + + if (state->prev_seq != 0 && (uint8_t)(state->prev_seq + 1) != seq_num) { + spa_log_warn(state->log, "dropped packets %d != %d", state->prev_seq + 1, seq_num); + } + state->prev_seq = seq_num; + return true; +} + +static inline void +set_iovec(struct spa_ringbuffer *rbuf, void *buffer, uint32_t size, + uint32_t offset, struct iovec *iov, uint32_t len) +{ + iov[0].iov_len = SPA_MIN(len, size - offset); + iov[0].iov_base = SPA_PTROFF(buffer, offset, void); + iov[1].iov_len = len - iov[0].iov_len; + iov[1].iov_base = buffer; +} + +static void avb_on_socket_event(struct spa_source *source) +{ + struct state *state = source->data; + ssize_t n; + int32_t filled; + uint32_t subtype, index; + struct spa_avbtp_packet_aaf *pdu = state->pdu; + bool overrun = false; + + filled = spa_ringbuffer_get_write_index(&state->ring, &index); + overrun = filled > (int32_t) state->ringbuffer_size; + if (overrun) { + state->iov[1].iov_base = state->pdu->payload; + state->iov[1].iov_len = state->payload_size; + state->iov[2].iov_len = 0; + } else { + set_iovec(&state->ring, + state->ringbuffer_data, + state->ringbuffer_size, + index % state->ringbuffer_size, + &state->iov[1], state->payload_size); + } + + n = recvmsg(state->sockfd, &state->msg, 0); + if (n < 0) { + spa_log_error(state->log, "recv() failed: %m"); + return; + } + if (n != (ssize_t)state->pdu_size) { + spa_log_error(state->log, "AVB packet dropped: Invalid size"); + return; + } + + subtype = SPA_AVBTP_PACKET_AAF_GET_SUBTYPE(pdu); + if (subtype != SPA_AVBTP_SUBTYPE_AAF) { + spa_log_error(state->log, "non supported subtype %d", subtype); + return; + } + if (!is_pdu_valid(state)) { + spa_log_error(state->log, "AAF PDU invalid"); + return; + } + if (overrun) { + spa_log_warn(state->log, "overrun %d", filled); + return; + } + index += state->payload_size; + spa_ringbuffer_write_update(&state->ring, index); +} + +static void set_timeout(struct state *state, uint64_t next_time) +{ + struct itimerspec ts; + uint64_t time_utc; + + spa_log_trace(state->log, "set timeout %"PRIu64, next_time); + + time_utc = next_time > TAI_OFFSET ? TAI_TO_UTC(next_time) : 0; + ts.it_value.tv_sec = time_utc / SPA_NSEC_PER_SEC; + ts.it_value.tv_nsec = time_utc % SPA_NSEC_PER_SEC; + ts.it_interval.tv_sec = 0; + ts.it_interval.tv_nsec = 0; + spa_system_timerfd_settime(state->data_system, + state->timer_source.fd, SPA_FD_TIMER_ABSTIME, &ts, NULL); +} + +static int flush_write(struct state *state, uint64_t current_time) +{ + int32_t avail, wanted; + uint32_t index; + uint64_t ptime, txtime; + int pdu_count; + struct props *p = &state->props; + struct spa_avbtp_packet_aaf *pdu = state->pdu; + ssize_t n; + + avail = spa_ringbuffer_get_read_index(&state->ring, &index); + wanted = state->duration * state->stride; + if (avail < wanted) { + spa_log_warn(state->log, "underrun %d < %d", avail, wanted); + return -EPIPE; + } + + pdu_count = state->duration / p->frames_per_pdu; + + txtime = current_time + p->t_uncertainty; + ptime = txtime + p->mtt; + + while (pdu_count--) { + *(__u64 *)CMSG_DATA(state->cmsg) = txtime; + + set_iovec(&state->ring, + state->ringbuffer_data, + state->ringbuffer_size, + index % state->ringbuffer_size, + &state->iov[1], state->payload_size); + + SPA_AVBTP_PACKET_AAF_SET_SEQ_NUM(pdu, state->pdu_seq++); + SPA_AVBTP_PACKET_AAF_SET_TIMESTAMP(pdu, ptime); + + n = sendmsg(state->sockfd, &state->msg, 0); + if (n < 0 || n != (ssize_t)state->pdu_size) { + spa_log_error(state->log, "sendmdg() failed: %m"); + } + txtime += state->pdu_period; + ptime += state->pdu_period; + index += state->payload_size; + } + spa_ringbuffer_read_update(&state->ring, index); + return 0; +} + +int spa_avb_write(struct state *state) +{ + int32_t filled; + uint32_t index, to_write; + struct port *port = &state->ports[0]; + + filled = spa_ringbuffer_get_write_index(&state->ring, &index); + if (filled < 0) { + spa_log_warn(state->log, "underrun %d", filled); + } else if (filled > (int32_t)state->ringbuffer_size) { + spa_log_warn(state->log, "overrun %d", filled); + } + to_write = state->ringbuffer_size - filled; + + while (!spa_list_is_empty(&port->ready) && to_write > 0) { + size_t n_bytes; + struct buffer *b; + struct spa_data *d; + uint32_t offs, avail, size; + + b = spa_list_first(&port->ready, struct buffer, link); + d = b->buf->datas; + + offs = SPA_MIN(d[0].chunk->offset + port->ready_offset, d[0].maxsize); + size = SPA_MIN(d[0].chunk->size, d[0].maxsize - offs); + avail = size - offs; + + n_bytes = SPA_MIN(avail, to_write); + if (n_bytes == 0) + break; + + spa_ringbuffer_write_data(&state->ring, + state->ringbuffer_data, + state->ringbuffer_size, + index % state->ringbuffer_size, + SPA_PTROFF(d[0].data, offs, void), + n_bytes); + + port->ready_offset += n_bytes; + + if (port->ready_offset >= size || avail == 0) { + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); + port->io->buffer_id = b->id; + spa_log_trace_fp(state->log, "%p: reuse buffer %u", state, b->id); + + spa_node_call_reuse_buffer(&state->callbacks, 0, b->id); + + port->ready_offset = 0; + } + to_write -= n_bytes; + index += n_bytes; + } + spa_ringbuffer_write_update(&state->ring, index); + + if (state->following) { + flush_write(state, state->position->clock.nsec); + } + return 0; +} + +static int handle_play(struct state *state, uint64_t current_time) +{ + flush_write(state, current_time); + spa_node_call_ready(&state->callbacks, SPA_STATUS_NEED_DATA); + return 0; +} + +int spa_avb_read(struct state *state) +{ + int32_t avail, wanted; + uint32_t index; + struct port *port = &state->ports[0]; + struct buffer *b; + struct spa_data *d; + uint32_t n_bytes; + + if (state->position) + state->duration = state->position->clock.duration; + + avail = spa_ringbuffer_get_read_index(&state->ring, &index); + wanted = state->duration * state->stride; + + if (spa_list_is_empty(&port->free)) { + spa_log_warn(state->log, "out of buffers"); + return -EPIPE; + } + + b = spa_list_first(&port->free, struct buffer, link); + d = b->buf->datas; + + n_bytes = SPA_MIN(d[0].maxsize, (uint32_t)wanted); + + if (avail < wanted) { + spa_log_warn(state->log, "capture underrun %d < %d", avail, wanted); + memset(d[0].data, 0, n_bytes); + } else { + spa_ringbuffer_read_data(&state->ring, + state->ringbuffer_data, + state->ringbuffer_size, + index % state->ringbuffer_size, + d[0].data, n_bytes); + index += n_bytes; + spa_ringbuffer_read_update(&state->ring, index); + } + + d[0].chunk->offset = 0; + d[0].chunk->size = n_bytes; + d[0].chunk->stride = state->stride; + d[0].chunk->flags = 0; + + spa_list_remove(&b->link); + spa_list_append(&port->ready, &b->link); + + return 0; +} + +static int handle_capture(struct state *state, uint64_t current_time) +{ + struct port *port = &state->ports[0]; + struct spa_io_buffers *io; + struct buffer *b; + + spa_avb_read(state); + + if (spa_list_is_empty(&port->ready)) + return 0; + + io = port->io; + if (io != NULL && + (io->status != SPA_STATUS_HAVE_DATA || port->rate_match != NULL)) { + if (io->buffer_id < port->n_buffers) + spa_avb_recycle_buffer(state, port, io->buffer_id); + + b = spa_list_first(&port->ready, struct buffer, link); + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); + + io->buffer_id = b->id; + io->status = SPA_STATUS_HAVE_DATA; + spa_log_trace_fp(state->log, "%p: output buffer:%d", state, b->id); + } + spa_node_call_ready(&state->callbacks, SPA_STATUS_HAVE_DATA); + return 0; +} + +static void avb_on_timeout_event(struct spa_source *source) +{ + struct state *state = source->data; + uint64_t expirations, current_time, duration; + uint32_t rate; + + spa_log_trace(state->log, "timeout"); + + if (spa_system_timerfd_read(state->data_system, + state->timer_source.fd, &expirations) < 0) { + if (errno == EAGAIN) + return; + spa_log_error(state->log, "read timerfd: %m"); + } + + current_time = state->next_time; + if (SPA_LIKELY(state->position)) { + duration = state->position->clock.duration; + rate = state->position->clock.rate.denom; + } else { + duration = 1024; + rate = 48000; + } + state->duration = duration; + + if (state->ports[0].direction == SPA_DIRECTION_INPUT) + handle_play(state, current_time); + else + handle_capture(state, current_time); + + state->next_time = current_time + duration * SPA_NSEC_PER_SEC / rate; + + if (SPA_LIKELY(state->clock)) { + state->clock->nsec = current_time; + state->clock->position += duration; + state->clock->duration = duration; + state->clock->delay = 0; + state->clock->rate_diff = 1.0; + state->clock->next_nsec = state->next_time; + } + + set_timeout(state, state->next_time); +} + +static int set_timers(struct state *state) +{ + struct timespec now; + int res; + + if ((res = spa_system_clock_gettime(state->data_system, CLOCK_TAI, &now)) < 0) + return res; + + state->next_time = SPA_TIMESPEC_TO_NSEC(&now); + + if (state->following) { + set_timeout(state, 0); + } else { + set_timeout(state, state->next_time); + } + return 0; +} + +static inline bool is_following(struct state *state) +{ + return state->position && state->clock && state->position->clock.id != state->clock->id; +} + +static int do_reassign_follower(struct spa_loop *loop, + bool async, + uint32_t seq, + const void *data, + size_t size, + void *user_data) +{ + struct state *state = user_data; + spa_dll_init(&state->dll); + set_timers(state); + return 0; +} + +int spa_avb_reassign_follower(struct state *state) +{ + bool following, freewheel; + + if (!state->started) + return 0; + + following = is_following(state); + if (following != state->following) { + spa_log_debug(state->log, "%p: reassign follower %d->%d", state, state->following, following); + state->following = following; + spa_loop_invoke(state->data_loop, do_reassign_follower, 0, NULL, 0, true, state); + } + + freewheel = state->position && + SPA_FLAG_IS_SET(state->position->clock.flags, SPA_IO_CLOCK_FLAG_FREEWHEEL); + + if (state->freewheel != freewheel) { + spa_log_debug(state->log, "%p: freewheel %d->%d", state, state->freewheel, freewheel); + state->freewheel = freewheel; + } + return 0; +} + +int spa_avb_start(struct state *state) +{ + if (state->started) + return 0; + + if (state->position) { + state->duration = state->position->clock.duration; + state->rate_denom = state->position->clock.rate.denom; + } else { + state->duration = 1024; + state->rate_denom = state->rate; + } + + spa_dll_init(&state->dll); + state->max_error = (256.0 * state->rate) / state->rate_denom; + + state->following = is_following(state); + + state->timer_source.func = avb_on_timeout_event; + state->timer_source.data = state; + state->timer_source.fd = state->timerfd; + state->timer_source.mask = SPA_IO_IN; + state->timer_source.rmask = 0; + spa_loop_add_source(state->data_loop, &state->timer_source); + + state->pdu_seq = 0; + + if (state->ports[0].direction == SPA_DIRECTION_OUTPUT) { + state->sock_source.func = avb_on_socket_event; + state->sock_source.data = state; + state->sock_source.fd = state->sockfd; + state->sock_source.mask = SPA_IO_IN; + state->sock_source.rmask = 0; + spa_loop_add_source(state->data_loop, &state->sock_source); + } + + reset_buffers(state, &state->ports[0]); + + set_timers(state); + + state->started = true; + + return 0; +} + +static int do_remove_source(struct spa_loop *loop, + bool async, + uint32_t seq, + const void *data, + size_t size, + void *user_data) +{ + struct state *state = user_data; + + spa_loop_remove_source(state->data_loop, &state->timer_source); + + if (state->ports[0].direction == SPA_DIRECTION_OUTPUT) { + spa_loop_remove_source(state->data_loop, &state->sock_source); + } + return 0; +} + +int spa_avb_pause(struct state *state) +{ + if (!state->started) + return 0; + + spa_log_debug(state->log, "%p: pause", state); + + spa_loop_invoke(state->data_loop, do_remove_source, 0, NULL, 0, true, state); + + state->started = false; + set_timeout(state, 0); + + return 0; +} diff --git a/spa/plugins/avb/avb-pcm.h b/spa/plugins/avb/avb-pcm.h new file mode 100644 index 0000000000000000000000000000000000000000..bb3bce68472f0376b0529914b368b0d353f9884c --- /dev/null +++ b/spa/plugins/avb/avb-pcm.h @@ -0,0 +1,343 @@ +/* Spa AVB PCM + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef SPA_AVB_PCM_H +#define SPA_AVB_PCM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stddef.h> +#include <math.h> +#include <linux/if_ether.h> +#include <linux/if_packet.h> +#include <linux/net_tstamp.h> +#include <limits.h> +#include <net/if.h> + +#include <avbtp/packets.h> + +#include <spa/support/plugin.h> +#include <spa/support/loop.h> +#include <spa/utils/list.h> +#include <spa/utils/json.h> +#include <spa/utils/dll.h> + +#include <spa/node/node.h> +#include <spa/node/utils.h> +#include <spa/node/io.h> +#include <spa/debug/types.h> +#include <spa/utils/ringbuffer.h> +#include <spa/param/param.h> +#include <spa/param/latency-utils.h> +#include <spa/param/audio/format-utils.h> + +#include "avb.h" + +#define MAX_RATES 16 + +#define DEFAULT_IFNAME "eth0" +#define DEFAULT_ADDR "01:AA:AA:AA:AA:AA" +#define DEFAULT_PRIO 0 +#define DEFAULT_STREAMID "AA:BB:CC:DD:EE:FF:0000" +#define DEFAULT_MTT 5000000 +#define DEFAULT_TU 1000000 +#define DEFAULT_FRAMES_PER_PDU 8 + +#define DEFAULT_PERIOD 1024u +#define DEFAULT_RATE 48000u +#define DEFAULT_CHANNELS 8u + +struct props { + char ifname[IFNAMSIZ]; + unsigned char addr[ETH_ALEN]; + int prio; + uint64_t streamid; + int mtt; + int t_uncertainty; + uint32_t frames_per_pdu; + int ptime_tolerance; +}; + +static inline int parse_addr(unsigned char addr[ETH_ALEN], const char *str) +{ + unsigned char ad[ETH_ALEN]; + if (sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &ad[0], &ad[1], &ad[2], &ad[3], &ad[4], &ad[5]) != 6) + return -EINVAL; + memcpy(addr, ad, sizeof(ad)); + return 0; +} +static inline char *format_addr(char *str, size_t size, const unsigned char addr[ETH_ALEN]) +{ + snprintf(str, size, "%02x:%02x:%02x:%02x:%02x:%02x", + addr[0], addr[1], addr[2], + addr[3], addr[4], addr[5]); + return str; +} + +static inline int parse_streamid(uint64_t *streamid, const char *str) +{ + unsigned char addr[6]; + unsigned short unique_id; + if (sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx:%hx", + &addr[0], &addr[1], &addr[2], &addr[3], + &addr[4], &addr[5], &unique_id) != 7) + return -EINVAL; + *streamid = (uint64_t) addr[0] << 56 | + (uint64_t) addr[1] << 48 | + (uint64_t) addr[2] << 40 | + (uint64_t) addr[3] << 32 | + (uint64_t) addr[4] << 24 | + (uint64_t) addr[5] << 16 | + unique_id; + return 0; +} +static inline char *format_streamid(char *str, size_t size, const uint64_t streamid) +{ + snprintf(str, size, "%02x:%02x:%02x:%02x:%02x:%02x:%04x", + (uint8_t)(streamid >> 56), + (uint8_t)(streamid >> 48), + (uint8_t)(streamid >> 40), + (uint8_t)(streamid >> 32), + (uint8_t)(streamid >> 24), + (uint8_t)(streamid >> 16), + (uint16_t)(streamid)); + return str; +} + +#define MAX_BUFFERS 32 + +struct buffer { + uint32_t id; +#define BUFFER_FLAG_OUT (1<<0) + uint32_t flags; + struct spa_buffer *buf; + struct spa_meta_header *h; + struct spa_list link; +}; + +#define BW_MAX 0.128 +#define BW_MED 0.064 +#define BW_MIN 0.016 +#define BW_PERIOD (3 * SPA_NSEC_PER_SEC) + +struct channel_map { + uint32_t channels; + uint32_t pos[SPA_AUDIO_MAX_CHANNELS]; +}; + +struct port { + enum spa_direction direction; + uint32_t id; + + uint64_t info_all; + struct spa_port_info info; +#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 params[N_PORT_PARAMS]; + + bool have_format; + struct spa_audio_info current_format; + + struct spa_io_buffers *io; + struct spa_io_rate_match *rate_match; + struct buffer buffers[MAX_BUFFERS]; + unsigned int n_buffers; + + struct spa_list free; + struct spa_list ready; + uint32_t ready_offset; +}; + +struct state { + struct spa_handle handle; + struct spa_node node; + + struct spa_log *log; + struct spa_system *data_system; + struct spa_loop *data_loop; + + struct spa_hook_list hooks; + struct spa_callbacks callbacks; + + uint64_t info_all; + struct spa_node_info info; +#define NODE_PropInfo 0 +#define NODE_Props 1 +#define NODE_IO 2 +#define NODE_ProcessLatency 3 +#define N_NODE_PARAMS 4 + struct spa_param_info params[N_NODE_PARAMS]; + struct props props; + + uint32_t default_period_size; + uint32_t default_format; + unsigned int default_channels; + unsigned int default_rate; + uint32_t allowed_rates[MAX_RATES]; + uint32_t n_allowed_rates; + struct channel_map default_pos; + char clock_name[64]; + uint32_t quantum_limit; + + uint32_t format; + uint32_t rate; + uint32_t channels; + uint32_t stride; + uint32_t blocks; + uint32_t rate_denom; + + struct spa_io_clock *clock; + struct spa_io_position *position; + + struct port ports[1]; + + uint32_t duration; + unsigned int following:1; + unsigned int matching:1; + unsigned int resample:1; + unsigned int started:1; + unsigned int freewheel:1; + + int timerfd; + struct spa_source timer_source; + uint64_t next_time; + + int sockfd; + struct spa_source sock_source; + struct sockaddr_ll sock_addr; + + struct spa_avbtp_packet_aaf *pdu; + size_t hdr_size; + size_t payload_size; + size_t pdu_size; + int64_t pdu_period; + uint8_t pdu_seq; + uint8_t prev_seq; + + struct iovec iov[3]; + struct msghdr msg; + char control[CMSG_SPACE(sizeof(__u64))]; + struct cmsghdr *cmsg; + + uint8_t *ringbuffer_data; + uint32_t ringbuffer_size; + struct spa_ringbuffer ring; + + struct spa_dll dll; + double max_error; + + struct spa_latency_info latency[2]; + struct spa_process_latency_info process_latency; +}; + +struct spa_pod *spa_avb_enum_propinfo(struct state *state, + uint32_t idx, struct spa_pod_builder *b); +int spa_avb_add_prop_params(struct state *state, struct spa_pod_builder *b); +int spa_avb_parse_prop_params(struct state *state, struct spa_pod *params); + +int spa_avb_enum_format(struct state *state, int seq, + uint32_t start, uint32_t num, + const struct spa_pod *filter); + +int spa_avb_clear_format(struct state *state); +int spa_avb_set_format(struct state *state, struct spa_audio_info *info, uint32_t flags); + +int spa_avb_init(struct state *state, const struct spa_dict *info); +int spa_avb_clear(struct state *state); + +int spa_avb_start(struct state *state); +int spa_avb_reassign_follower(struct state *state); +int spa_avb_pause(struct state *state); + +int spa_avb_write(struct state *state); +int spa_avb_read(struct state *state); +int spa_avb_skip(struct state *state); + +void spa_avb_recycle_buffer(struct state *state, struct port *port, uint32_t buffer_id); + +static inline uint32_t spa_avb_format_from_name(const char *name, size_t len) +{ + int i; + for (i = 0; spa_type_audio_format[i].name; i++) { + if (strncmp(name, spa_debug_type_short_name(spa_type_audio_format[i].name), len) == 0) + return spa_type_audio_format[i].type; + } + return SPA_AUDIO_FORMAT_UNKNOWN; +} + +static inline uint32_t spa_avb_channel_from_name(const char *name) +{ + int i; + for (i = 0; spa_type_audio_channel[i].name; i++) { + if (strcmp(name, spa_debug_type_short_name(spa_type_audio_channel[i].name)) == 0) + return spa_type_audio_channel[i].type; + } + return SPA_AUDIO_CHANNEL_UNKNOWN; +} + +static inline void spa_avb_parse_position(struct channel_map *map, const char *val, size_t len) +{ + struct spa_json it[2]; + char v[256]; + + spa_json_init(&it[0], val, len); + if (spa_json_enter_array(&it[0], &it[1]) <= 0) + spa_json_init(&it[1], val, len); + + map->channels = 0; + while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && + map->channels < SPA_AUDIO_MAX_CHANNELS) { + map->pos[map->channels++] = spa_avb_channel_from_name(v); + } +} + +static inline uint32_t spa_avb_parse_rates(uint32_t *rates, uint32_t max, const char *val, size_t len) +{ + struct spa_json it[2]; + char v[256]; + uint32_t count; + + spa_json_init(&it[0], val, len); + if (spa_json_enter_array(&it[0], &it[1]) <= 0) + spa_json_init(&it[1], val, len); + + count = 0; + while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && count < max) + rates[count++] = atoi(v); + return count; +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_AVB_PCM_H */ diff --git a/spa/plugins/avb/avb.c b/spa/plugins/avb/avb.c new file mode 100644 index 0000000000000000000000000000000000000000..8f6731025bc50cf7ac1afc927ca4c1d461fd0167 --- /dev/null +++ b/spa/plugins/avb/avb.c @@ -0,0 +1,54 @@ +/* Spa AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <errno.h> + +#include <spa/support/plugin.h> +#include <spa/support/log.h> + +extern const struct spa_handle_factory spa_avb_sink_factory; +extern const struct spa_handle_factory spa_avb_source_factory; + +struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.avb"); +struct spa_log_topic *avb_log_topic = &log_topic; + +SPA_EXPORT +int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *factory = &spa_avb_sink_factory; + break; + case 1: + *factory = &spa_avb_source_factory; + break; + default: + return 0; + } + (*index)++; + return 1; +} diff --git a/spa/plugins/avb/avb.h b/spa/plugins/avb/avb.h new file mode 100644 index 0000000000000000000000000000000000000000..a99a0fed4e48e4ef0993083adf6261682dc5472c --- /dev/null +++ b/spa/plugins/avb/avb.h @@ -0,0 +1,39 @@ +/* Spa AVB + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef SPA_AVB_H +#define SPA_AVB_H + +#include <spa/support/log.h> + +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT avb_log_topic +extern struct spa_log_topic *avb_log_topic; + +static inline void avb_log_topic_init(struct spa_log *log) +{ + spa_log_topic_init(log, avb_log_topic); +} + +#endif /* SPA_AVB_H */ diff --git a/spa/plugins/avb/avbtp/packets.h b/spa/plugins/avb/avbtp/packets.h new file mode 100644 index 0000000000000000000000000000000000000000..3d4a652ee7a249eab3f37d4fbf51753605b0ef50 --- /dev/null +++ b/spa/plugins/avb/avbtp/packets.h @@ -0,0 +1,220 @@ +/* Spa AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef SPA_AVB_PACKETS_H +#define SPA_AVB_PACKETS_H + +#define SPA_AVBTP_SUBTYPE_61883_IIDC 0x00 +#define SPA_AVBTP_SUBTYPE_MMA_STREAM 0x01 +#define SPA_AVBTP_SUBTYPE_AAF 0x02 +#define SPA_AVBTP_SUBTYPE_CVF 0x03 +#define SPA_AVBTP_SUBTYPE_CRF 0x04 +#define SPA_AVBTP_SUBTYPE_TSCF 0x05 +#define SPA_AVBTP_SUBTYPE_SVF 0x06 +#define SPA_AVBTP_SUBTYPE_RVF 0x07 +#define SPA_AVBTP_SUBTYPE_AEF_CONTINUOUS 0x6E +#define SPA_AVBTP_SUBTYPE_VSF_STREAM 0x6F +#define SPA_AVBTP_SUBTYPE_EF_STREAM 0x7F +#define SPA_AVBTP_SUBTYPE_NTSCF 0x82 +#define SPA_AVBTP_SUBTYPE_ESCF 0xEC +#define SPA_AVBTP_SUBTYPE_EECF 0xED +#define SPA_AVBTP_SUBTYPE_AEF_DISCRETE 0xEE +#define SPA_AVBTP_SUBTYPE_ADP 0xFA +#define SPA_AVBTP_SUBTYPE_AECP 0xFB +#define SPA_AVBTP_SUBTYPE_ACMP 0xFC +#define SPA_AVBTP_SUBTYPE_MAAP 0xFE +#define SPA_AVBTP_SUBTYPE_EF_CONTROL 0xFF + +struct spa_avbtp_packet_common { + uint8_t subtype; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned sv:1; /* stream_id valid */ + unsigned version:3; + unsigned subtype_data1:4; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned subtype_data1:4; + unsigned version:3; + unsigned sv:1; +#elif +#error "Unknown byte order" +#endif + uint16_t subtype_data2; + uint64_t stream_id; + uint8_t payload[0]; +} __attribute__ ((__packed__)); + +#define SPA_AVBTP_PACKET_SET_SUBTYPE(p,v) ((p)->subtype = (v)) +#define SPA_AVBTP_PACKET_SET_SV(p,v) ((p)->sv = (v)) +#define SPA_AVBTP_PACKET_SET_VERSION(p,v) ((p)->version = (v)) +#define SPA_AVBTP_PACKET_SET_STREAM_ID(p,v) ((p)->stream_id = htobe64(v)) + +#define SPA_AVBTP_PACKET_GET_SUBTYPE(p) ((p)->subtype) +#define SPA_AVBTP_PACKET_GET_SV(p) ((p)->sv) +#define SPA_AVBTP_PACKET_GET_VERSION(p) ((p)->version) +#define SPA_AVBTP_PACKET_GET_STREAM_ID(p) be64toh((p)->stream_id) + +struct spa_avbtp_packet_cc { + uint8_t subtype; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned sv:1; + unsigned version:3; + unsigned control_data1:4; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned control_data1:4; + unsigned version:3; + unsigned sv:1; +#endif + uint8_t status; + uint16_t control_frame_length; + uint64_t stream_id; + uint8_t payload[0]; +} __attribute__ ((__packed__)); + +#define SPA_AVBTP_PACKET_CC_SET_SUBTYPE(p,v) ((p)->subtype = (v)) +#define SPA_AVBTP_PACKET_CC_SET_SV(p,v) ((p)->sv = (v)) +#define SPA_AVBTP_PACKET_CC_SET_VERSION(p,v) ((p)->version = (v)) +#define SPA_AVBTP_PACKET_CC_SET_STREAM_ID(p,v) ((p)->stream_id = htobe64(v)) +#define SPA_AVBTP_PACKET_CC_SET_STATUS(p,v) ((p)->status = (v)) +#define SPA_AVBTP_PACKET_CC_SET_LENGTH(p,v) ((p)->control_frame_length = htons(v)) + +#define SPA_AVBTP_PACKET_CC_GET_SUBTYPE(p) ((p)->subtype) +#define SPA_AVBTP_PACKET_CC_GET_SV(p) ((p)->sv) +#define SPA_AVBTP_PACKET_CC_GET_VERSION(p) ((p)->version) +#define SPA_AVBTP_PACKET_CC_GET_STREAM_ID(p) be64toh((p)->stream_id) +#define SPA_AVBTP_PACKET_CC_GET_STATUS(p) ((p)->status) +#define SPA_AVBTP_PACKET_CC_GET_LENGTH(p) ntohs((p)->control_frame_length) + +/* AAF */ +struct spa_avbtp_packet_aaf { + uint8_t subtype; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned sv:1; + unsigned version:3; + unsigned mr:1; + unsigned _r1:1; + unsigned gv:1; + unsigned tv:1; + + uint8_t seq_num; + + unsigned _r2:7; + unsigned tu:1; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned tv:1; + unsigned gv:1; + unsigned _r1:1; + unsigned mr:1; + unsigned version:3; + unsigned sv:1; + + uint8_t seq_num; + + unsigned tu:1; + unsigned _r2:7; +#endif + uint64_t stream_id; + uint32_t timestamp; +#define SPA_AVBTP_AAF_FORMAT_USER 0x00 +#define SPA_AVBTP_AAF_FORMAT_FLOAT_32BIT 0x01 +#define SPA_AVBTP_AAF_FORMAT_INT_32BIT 0x02 +#define SPA_AVBTP_AAF_FORMAT_INT_24BIT 0x03 +#define SPA_AVBTP_AAF_FORMAT_INT_16BIT 0x04 +#define SPA_AVBTP_AAF_FORMAT_AES3_32BIT 0x05 + uint8_t format; + +#define SPA_AVBTP_AAF_PCM_NSR_USER 0x00 +#define SPA_AVBTP_AAF_PCM_NSR_8KHZ 0x01 +#define SPA_AVBTP_AAF_PCM_NSR_16KHZ 0x02 +#define SPA_AVBTP_AAF_PCM_NSR_32KHZ 0x03 +#define SPA_AVBTP_AAF_PCM_NSR_44_1KHZ 0x04 +#define SPA_AVBTP_AAF_PCM_NSR_48KHZ 0x05 +#define SPA_AVBTP_AAF_PCM_NSR_88_2KHZ 0x06 +#define SPA_AVBTP_AAF_PCM_NSR_96KHZ 0x07 +#define SPA_AVBTP_AAF_PCM_NSR_176_4KHZ 0x08 +#define SPA_AVBTP_AAF_PCM_NSR_192KHZ 0x09 +#define SPA_AVBTP_AAF_PCM_NSR_24KHZ 0x0A +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned nsr:4; + unsigned _r3:4; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned _r3:4; + unsigned nsr:4; +#endif + uint8_t chan_per_frame; + uint8_t bit_depth; + uint16_t data_len; + +#define SPA_AVBTP_AAF_PCM_SP_NORMAL 0x00 +#define SPA_AVBTP_AAF_PCM_SP_SPARSE 0x01 +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned _r4:3; + unsigned sp:1; + unsigned event:4; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned event:4; + unsigned sp:1; + unsigned _r4:3; +#endif + uint8_t _r5; + uint8_t payload[0]; +} __attribute__ ((__packed__)); + +#define SPA_AVBTP_PACKET_AAF_SET_SUBTYPE(p,v) ((p)->subtype = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_SV(p,v) ((p)->sv = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_VERSION(p,v) ((p)->version = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_MR(p,v) ((p)->mr = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_GV(p,v) ((p)->gv = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_TV(p,v) ((p)->tv = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_SEQ_NUM(p,v) ((p)->seq_num = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_TU(p,v) ((p)->tu = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_STREAM_ID(p,v) ((p)->stream_id = htobe64(v)) +#define SPA_AVBTP_PACKET_AAF_SET_TIMESTAMP(p,v) ((p)->timestamp = htonl(v)) +#define SPA_AVBTP_PACKET_AAF_SET_DATA_LEN(p,v) ((p)->data_len = htons(v)) +#define SPA_AVBTP_PACKET_AAF_SET_FORMAT(p,v) ((p)->format = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_NSR(p,v) ((p)->nsr = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_CHAN_PER_FRAME(p,v) ((p)->chan_per_frame = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_BIT_DEPTH(p,v) ((p)->bit_depth = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_SP(p,v) ((p)->sp = (v)) +#define SPA_AVBTP_PACKET_AAF_SET_EVENT(p,v) ((p)->event = (v)) + +#define SPA_AVBTP_PACKET_AAF_GET_SUBTYPE(p) ((p)->subtype) +#define SPA_AVBTP_PACKET_AAF_GET_SV(p) ((p)->sv) +#define SPA_AVBTP_PACKET_AAF_GET_VERSION(p) ((p)->version) +#define SPA_AVBTP_PACKET_AAF_GET_MR(p) ((p)->mr) +#define SPA_AVBTP_PACKET_AAF_GET_GV(p) ((p)->gv) +#define SPA_AVBTP_PACKET_AAF_GET_TV(p) ((p)->tv) +#define SPA_AVBTP_PACKET_AAF_GET_SEQ_NUM(p) ((p)->seq_num) +#define SPA_AVBTP_PACKET_AAF_GET_TU(p) ((p)->tu) +#define SPA_AVBTP_PACKET_AAF_GET_STREAM_ID(p) be64toh((p)->stream_id) +#define SPA_AVBTP_PACKET_AAF_GET_TIMESTAMP(p) ntohl((p)->timestamp) +#define SPA_AVBTP_PACKET_AAF_GET_DATA_LEN(p) ntohs((p)->data_len) +#define SPA_AVBTP_PACKET_AAF_GET_FORMAT(p) ((p)->format) +#define SPA_AVBTP_PACKET_AAF_GET_NSR(p) ((p)->nsr) +#define SPA_AVBTP_PACKET_AAF_GET_CHAN_PER_FRAME(p) ((p)->chan_per_frame) +#define SPA_AVBTP_PACKET_AAF_GET_BIT_DEPTH(p) ((p)->bit_depth) +#define SPA_AVBTP_PACKET_AAF_GET_SP(p) ((p)->sp) +#define SPA_AVBTP_PACKET_AAF_GET_EVENT(p) ((p)->event) + + +#endif /* SPA_AVB_PACKETS_H */ diff --git a/spa/plugins/avb/meson.build b/spa/plugins/avb/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..2d9759ec794986e86c63dabef9524537abc1dd1d --- /dev/null +++ b/spa/plugins/avb/meson.build @@ -0,0 +1,14 @@ +spa_avb_sources = ['avb.c', + 'avb.h', + 'avb-pcm-sink.c', + 'avb-pcm-source.c', + 'avb-pcm.c' ] + +spa_avb = shared_library( + 'spa-avb', + [ spa_avb_sources ], + include_directories : [configinc], + dependencies : [ spa_dep, mathlib, epoll_shim_dep ], + install : true, + install_dir : spa_plugindir / 'avb' +) diff --git a/spa/plugins/bluez5/README-OPUS-A2DP.md b/spa/plugins/bluez5/README-OPUS-A2DP.md new file mode 100644 index 0000000000000000000000000000000000000000..e94623f7516f99caa5554c86495accca8a7168d7 --- /dev/null +++ b/spa/plugins/bluez5/README-OPUS-A2DP.md @@ -0,0 +1,321 @@ +--- +title: OPUS-A2DP-0.5 specification +author: Pauli Virtanen <pav@iki.fi> +date: Jun 4, 2022 +--- + +# OPUS-A2DP-0.5 specification + +DRAFT + +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 +specification for using Opus as an A2DP vendor codec (to my +knowledge), which is why we need this one. + +[[_TOC_]] + +# A2DP 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: + +| Octet | Bits | Meaning | +|-------|------|-----------------------------------------------| +| 0-5 | 0-7 | Vendor ID Part | +| 6-7 | 0-7 | Channel Configuration | +| 8-11 | 0-7 | Audio Location Configuration | +| 12-14 | 0-7 | Limits Configuration | +| 15-16 | 0-7 | Return Direction Channel Configuration | +| 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. + +## Vendor ID Part + +The fixed value + +| Octet | Bits | Meaning | +|-------|------|-------------------------------| +| 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. + +## Channel Configuration + +The channel configuration consists of the channel count and a bitfield +indicating which of them are encoded in coupled streams. + +| Octet | Bits | Meaning | +|-------|------|------------------------------------------------------------| +| 6 | 0-7 | Channel Count. CAP: maximum number supported. SEL: actual. | +| 7 | 0-7 | Coupled Stream Count. CAP: 0. SEL: actual. | + +The Channel Count indicates the number of logical channels encoded in +the data stream. + +The Coupled Stream Count indicates the number of streams that encode a +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. + +## 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. + +| Octet | Bits | Meaning | +|-------|------|------------------------------------------------------| +| 8-11 | 0-7 | Audio Location bitfield. CAP: available. SEL: actual | + +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. + +The audio location bitfield values defined in [Assigned Numbers, +Generic Audio] are: + +| Channel Order | Bitmask | Audio Location | +|---------------|------------|-------------------------| +| 0 | 0x00000001 | Front Left | +| 1 | 0x00000002 | Front Right | +| 2 | 0x00000400 | Side Left | +| 3 | 0x00000800 | Side Right | +| 4 | 0x00000010 | Back Left | +| 5 | 0x00000020 | Back Right | +| 6 | 0x00000040 | Front Left of Center | +| 7 | 0x00000080 | Front Right of Center | +| 8 | 0x00001000 | Top Front Left | +| 9 | 0x00002000 | Top Front Right | +| 10 | 0x00040000 | Top Side Left | +| 11 | 0x00080000 | Top Side Right | +| 12 | 0x00010000 | Top Back Left | +| 13 | 0x00020000 | Top Back Right | +| 14 | 0x00400000 | Bottom Front Left | +| 15 | 0x00800000 | Bottom Front Right | +| 16 | 0x01000000 | Front Left Wide | +| 17 | 0x02000000 | Front Right Wide | +| 18 | 0x04000000 | Left Surround | +| 19 | 0x08000000 | Right Surround | +| 20 | 0x00000004 | Front Center | +| 21 | 0x00000100 | Back Center | +| 22 | 0x00004000 | Top Front Center | +| 23 | 0x00008000 | Top Center | +| 24 | 0x00100000 | Top Back Center | +| 25 | 0x00200000 | Bottom Front Center | +| 26 | 0x00000008 | Low Frequency Effects 1 | +| 27 | 0x00000200 | Low Frequency Effects 2 | +| 28 | 0x10000000 | RFA | +| 29 | 0x20000000 | RFA | +| 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 +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 +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 +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. + +## Limits Configuration + +The limits for allowed frame durations and maximum bitrate can also be +configured. + +| Octet | Bits | Meaning | +|-------|------|-----------------------------------------------------| +| 16 | 0 | Frame duration 2.5ms. CAP: supported, SEL: selected | +| 16 | 1 | Frame duration 5ms. CAP: supported, SEL: selected | +| 16 | 2 | Frame duration 10ms. CAP: supported, SEL: selected | +| 16 | 3 | Frame duration 20ms. CAP: supported, SEL: selected | +| 16 | 4 | Frame duration 40ms. CAP: supported, SEL: selected | +| 16 | 5-7 | RFA | + +| Octet | Bits | Meaning | +|-------|------|------------------------------------------------| +| 17-18 | 0-7 | Maximum bitrate. CAP: supported, SEL: selected | + +The maximum bitrate is given in units of 1024 bits per second. + +The maximum bitrate field in CAP may contain value 0 to indicate +everything is supported. + +## Bidirectional Audio Configuration + +Bidirectional audio may be supported. Its Channel Configuration, Audio +Location Configuration, and Limits Configuration have identical form +to the forward direction, and represented by exactly similar +structures. + +Namely: + +| Octet | Bits | Meaning | +|-------|------|----------------------------------------------------| +| 19-20 | 0-7 | Channel Configuration fields, for return direction | +| 21-28 | 0-7 | Audio Location fields, for return direction | +| 29-31 | 0-7 | Limits Configuration fields, for return direction | + +If no return channel is supported or selected, the number of channels +is set to 0 in CAP or SEL. + + +# Packet Structure + +Each packet consists of an RTP header, an RTP payload header, and a +payload containing Opus Multistream data. + +| Octet | Bits | Meaning | +|-------|------|--------------------------| +| 0-11 | 0-7 | RTP header | +| 12 | 0-7 | RTP payload header | +| 13-N | 0-7 | Opus Multistream payload | + +For each Bluetooth packet, the payload shall contain exactly one Opus +Multistream packet, or a fragment of one. The Opus Multistream packet +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.) + +## RTP Header + +See [RFC3550]. + +The RTP payload type is pt=96 (dynamic). + +## RTP Payload Header + +The RTP payload header is used to indicate if and how the Opus +Multistream packet is fragmented across several consecutive Bluetooth +packets. + +| Octet | Bits | Meaning +|--------|------|-------------------------------------------------------- +| 0 | 0-3 | Frame Count +| 4 | 4 | RFA +| 4 | 5 | Is Last Fragment +| 4 | 6 | Is First Fragment +| 4 | 7 | Is Fragmented + +In each packet, Frame Count indicates how many Bluetooth packets are +still to be received (including the present packet) before the Opus +Multistream packet is complete. + +The Is Fragment flag indicates whether the present packet contains +fragmented payload. + +The Is Last Fragment flag indicates whether the present packet is the +last part of fragmented payload. + +The Is First Fragment flag indicates whether the present packet is the +first part of fragmented payload. + +In non-fragmented packets, Frame Count shall be (1), and the other bits +in the header zero. + +## Opus Payload + +The Opus payload is a single Opus Multistream packet, or its fragment. + +In case of fragmentation, as indicated by the RTP payload header, +concatenating the payloads of the fragment Bluetooth packets shall +yield the total Opus Multistream packet. + +The SRC should choose encoder parameters such that Bluetooth bandwidth +limitations are not exceeded. + +The SRC may include FEC data. The SNK may enable forward error +correction instead of PLC. + + +# References + +1. IETF RFC 3550: [RFC3550] +2. IETF RFC 7587: [RFC7587] +3. IETF RFC 7845: [RFC7845] + +[RFC3550]: https://datatracker.ietf.org/doc/html/rfc3550 +[RFC7587]: https://datatracker.ietf.org/doc/html/rfc7587 +[RFC7845]: https://datatracker.ietf.org/doc/html/rfc7845 +[Assigned Numbers, Generic Audio]: https://www.bluetooth.com/specifications/assigned-numbers/ diff --git a/spa/plugins/bluez5/a2dp-codec-aac.c b/spa/plugins/bluez5/a2dp-codec-aac.c index 1195def30299ad0ae17e70c8365ecba1cee1d9b8..c14ff2af9af32e7fbc2f9f103b8dd4abe57e2535 100644 --- a/spa/plugins/bluez5/a2dp-codec-aac.c +++ b/spa/plugins/bluez5/a2dp-codec-aac.c @@ -31,10 +31,16 @@ #include <spa/utils/dict.h> #include <fdk-aac/aacenc_lib.h> +#include <fdk-aac/aacdecoder_lib.h> #include "rtp.h" #include "a2dp-codecs.h" +static struct spa_log *log; +static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.codecs.aac"); +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT &log_topic + #define DEFAULT_AAC_BITRATE 320000 #define MIN_AAC_BITRATE 64000 @@ -44,6 +50,7 @@ struct props { struct impl { HANDLE_AACENCODER aacenc; + HANDLE_AACDECODER aacdec; struct rtp_header *header; @@ -170,7 +177,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return sizeof(conf); } -static int codec_enum_config(const struct a2dp_codec *codec, +static int codec_enum_config(const struct a2dp_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { @@ -289,7 +296,7 @@ static int codec_validate_config(const struct a2dp_codec *codec, uint32_t flags, return 0; } -static void *codec_init_props(const struct a2dp_codec *codec, const struct spa_dict *settings) +static void *codec_init_props(const struct a2dp_codec *codec, uint32_t flags, const struct spa_dict *settings) { struct props *p = calloc(1, sizeof(struct props)); const char *str; @@ -412,11 +419,39 @@ static void *codec_init(const struct a2dp_codec *codec, uint32_t flags, this->codesize = enc_info.frameLength * this->channels * this->samplesize; + this->aacdec = aacDecoder_Open(TT_MP4_LATM_MCP1, 1); + if (!this->aacdec) { + res = -EINVAL; + goto error; + } + +#ifdef AACDECODER_LIB_VL0 + res = aacDecoder_SetParam(this->aacdec, AAC_PCM_MIN_OUTPUT_CHANNELS, this->channels); + if (res != AAC_DEC_OK) { + spa_log_debug(log, "Couldn't set min output channels: 0x%04X", res); + goto error; + } + + res = aacDecoder_SetParam(this->aacdec, AAC_PCM_MAX_OUTPUT_CHANNELS, this->channels); + if (res != AAC_DEC_OK) { + spa_log_debug(log, "Couldn't set max output channels: 0x%04X", res); + goto error; + } +#else + res = aacDecoder_SetParam(this->aacdec, AAC_PCM_OUTPUT_CHANNELS, this->channels); + if (res != AAC_DEC_OK) { + spa_log_debug(log, "Couldn't set output channels: 0x%04X", res); + goto error; + } +#endif + return this; error: - if (this->aacenc) + if (this && this->aacenc) aacEncClose(&this->aacenc); + if (this && this->aacdec) + aacDecoder_Close(this->aacdec); free(this); errno = -res; return NULL; @@ -427,6 +462,8 @@ static void codec_deinit(void *data) struct impl *this = data; if (this->aacenc) aacEncClose(&this->aacenc); + if (this->aacdec) + aacDecoder_Close(this->aacdec); free(this); } @@ -502,6 +539,55 @@ static int codec_encode(void *data, return out_args.numInSamples * this->samplesize; } +static int codec_start_decode (void *data, + const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) +{ + const struct rtp_header *header = src; + size_t header_size = sizeof(struct rtp_header); + + spa_return_val_if_fail (src_size > header_size, -EINVAL); + + if (seqnum) + *seqnum = ntohs(header->sequence_number); + if (timestamp) + *timestamp = ntohl(header->timestamp); + + return header_size; +} + +static int codec_decode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out) +{ + struct impl *this = data; + uint data_size = (uint)src_size; + uint bytes_valid = data_size; + CStreamInfo *aacinf; + int res; + + res = aacDecoder_Fill(this->aacdec, (UCHAR **)&src, &data_size, &bytes_valid); + if (res != AAC_DEC_OK) { + spa_log_debug(log, "AAC buffer fill error: 0x%04X", res); + return -EINVAL; + } + + res = aacDecoder_DecodeFrame(this->aacdec, dst, dst_size, 0); + if (res != AAC_DEC_OK) { + spa_log_debug(log, "AAC decode frame error: 0x%04X", res); + return -EINVAL; + } + + aacinf = aacDecoder_GetStreamInfo(this->aacdec); + if (!aacinf) { + spa_log_debug(log, "AAC get stream info failed"); + return -EINVAL; + } + *dst_out = aacinf->frameSize * aacinf->numChannels * this->samplesize; + + return src_size - bytes_valid; +} + static int codec_abr_process (void *data, size_t unsent) { return -ENOTSUP; @@ -538,6 +624,12 @@ static int codec_increase_bitpool(void *data) return codec_change_bitrate(this, (this->cur_bitrate * 4) / 3); } +static void codec_set_log(struct spa_log *global_log) +{ + log = global_log; + spa_log_topic_init(log, &log_topic); +} + const struct a2dp_codec a2dp_codec_aac = { .id = SPA_BLUETOOTH_AUDIO_CODEC_AAC, .codec_id = A2DP_CODEC_MPEG24, @@ -554,9 +646,12 @@ const struct a2dp_codec a2dp_codec_aac = { .get_block_size = codec_get_block_size, .start_encode = codec_start_encode, .encode = codec_encode, + .start_decode = codec_start_decode, + .decode = codec_decode, .abr_process = codec_abr_process, .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, + .set_log = codec_set_log, }; A2DP_CODEC_EXPORT_DEF( diff --git a/spa/plugins/bluez5/a2dp-codec-aptx.c b/spa/plugins/bluez5/a2dp-codec-aptx.c index 9a80134fd5c08c4d19a5411088c35d30c63c7e1b..721b90599adbae76bd627b8d964f93625cd63011 100644 --- a/spa/plugins/bluez5/a2dp-codec-aptx.c +++ b/spa/plugins/bluez5/a2dp-codec-aptx.c @@ -218,7 +218,7 @@ static int codec_select_config_ll(const struct a2dp_codec *codec, uint32_t flags return actual_conf_size; } -static int codec_enum_config(const struct a2dp_codec *codec, +static int codec_enum_config(const struct a2dp_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { @@ -458,7 +458,7 @@ static int codec_decode(void *data, * When connected as SRC to SNK, aptX-LL sink may send back mSBC data. */ -static int msbc_enum_config(const struct a2dp_codec *codec, +static int msbc_enum_config(const struct a2dp_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { @@ -710,6 +710,11 @@ static const struct a2dp_codec aptx_ll_msbc = { .increase_bitpool = msbc_increase_bitpool, }; +static const struct spa_dict_item duplex_info_items[] = { + { "duplex.boost", "true" }, +}; +static const struct spa_dict duplex_info = SPA_DICT_INIT_ARRAY(duplex_info_items); + const struct a2dp_codec a2dp_codec_aptx_ll_duplex_0 = { APTX_LL_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX, @@ -718,6 +723,7 @@ const struct a2dp_codec a2dp_codec_aptx_ll_duplex_0 = { .name = "aptx_ll_duplex", .endpoint_name = "aptx_ll_duplex_0", .duplex_codec = &aptx_ll_msbc, + .info = &duplex_info, }; const struct a2dp_codec a2dp_codec_aptx_ll_duplex_1 = { @@ -728,6 +734,7 @@ const struct a2dp_codec a2dp_codec_aptx_ll_duplex_1 = { .name = "aptx_ll_duplex", .endpoint_name = "aptx_ll_duplex_1", .duplex_codec = &aptx_ll_msbc, + .info = &duplex_info, }; A2DP_CODEC_EXPORT_DEF( diff --git a/spa/plugins/bluez5/a2dp-codec-caps.h b/spa/plugins/bluez5/a2dp-codec-caps.h index 3aa06529e96159cf88a09e43c5aedd095e3f1e1a..9f7259296d3a5d0a2fa4e70586ae69209b86e39e 100644 --- a/spa/plugins/bluez5/a2dp-codec-caps.h +++ b/spa/plugins/bluez5/a2dp-codec-caps.h @@ -233,6 +233,54 @@ #define LC3PLUS_HR_SAMPLING_FREQ_48000 (1 << 8) #define LC3PLUS_HR_SAMPLING_FREQ_96000 (1 << 7) +#define OPUS_05_VENDOR_ID 0x000005f1 +#define OPUS_05_CODEC_ID 0x1005 + +#define OPUS_05_MAPPING_FAMILY_0 (1 << 0) +#define OPUS_05_MAPPING_FAMILY_1 (1 << 1) +#define OPUS_05_MAPPING_FAMILY_255 (1 << 2) + +#define OPUS_05_FRAME_DURATION_25 (1 << 0) +#define OPUS_05_FRAME_DURATION_50 (1 << 1) +#define OPUS_05_FRAME_DURATION_100 (1 << 2) +#define OPUS_05_FRAME_DURATION_200 (1 << 3) +#define OPUS_05_FRAME_DURATION_400 (1 << 4) + +#define OPUS_05_GET_UINT16(a, field) \ + (((a).field ## 2 << 8) | (a).field ## 1) +#define OPUS_05_INIT_UINT16(field, v) \ + .field ## 1 = ((v) & 0xff), \ + .field ## 2 = (((v) >> 8) & 0xff), +#define OPUS_05_SET_UINT16(a, field, v) \ + do { \ + (a).field ## 1 = ((v) & 0xff); \ + (a).field ## 2 = (((v) >> 8) & 0xff); \ + } while (0) +#define OPUS_05_GET_UINT32(a, field) \ + (((a).field ## 4 << 24) | ((a).field ## 3 << 16) | \ + ((a).field ## 2 << 8) | (a).field ## 1) +#define OPUS_05_INIT_UINT32(field, v) \ + .field ## 1 = ((v) & 0xff), \ + .field ## 2 = (((v) >> 8) & 0xff), \ + .field ## 3 = (((v) >> 16) & 0xff), \ + .field ## 4 = (((v) >> 24) & 0xff), +#define OPUS_05_SET_UINT32(a, field, v) \ + do { \ + (a).field ## 1 = ((v) & 0xff); \ + (a).field ## 2 = (((v) >> 8) & 0xff); \ + (a).field ## 3 = (((v) >> 16) & 0xff); \ + (a).field ## 4 = (((v) >> 24) & 0xff); \ + } while (0) + +#define OPUS_05_GET_LOCATION(a) OPUS_05_GET_UINT32(a, location) +#define OPUS_05_INIT_LOCATION(v) OPUS_05_INIT_UINT32(location, v) +#define OPUS_05_SET_LOCATION(a, v) OPUS_05_SET_UINT32(a, location, v) + +#define OPUS_05_GET_BITRATE(a) OPUS_05_GET_UINT16(a, bitrate) +#define OPUS_05_INIT_BITRATE(v) OPUS_05_INIT_UINT16(bitrate, v) +#define OPUS_05_SET_BITRATE(a, v) OPUS_05_SET_UINT16(a, bitrate, v) + + typedef struct { uint32_t vendor_id; uint16_t codec_id; @@ -391,4 +439,22 @@ typedef struct { uint8_t frequency2; } __attribute__ ((packed)) a2dp_lc3plus_hr_t; +typedef struct { + uint8_t channels; + uint8_t coupled_streams; + uint8_t location1; + uint8_t location2; + uint8_t location3; + uint8_t location4; + uint8_t frame_duration; + uint8_t bitrate1; + uint8_t bitrate2; +} __attribute__ ((packed)) a2dp_opus_05_direction_t; + +typedef struct { + a2dp_vendor_codec_t info; + a2dp_opus_05_direction_t main; + a2dp_opus_05_direction_t bidi; +} __attribute__ ((packed)) a2dp_opus_05_t; + #endif diff --git a/spa/plugins/bluez5/a2dp-codec-faststream.c b/spa/plugins/bluez5/a2dp-codec-faststream.c index ddcf331d62d65db97b6a2a5b8fde7eb31c23e031..03911945c3b49d3ac5f08ecaf21f8356629e8220 100644 --- a/spa/plugins/bluez5/a2dp-codec-faststream.c +++ b/spa/plugins/bluez5/a2dp-codec-faststream.c @@ -129,7 +129,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return sizeof(conf); } -static int codec_enum_config(const struct a2dp_codec *codec, +static int codec_enum_config(const struct a2dp_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { @@ -372,7 +372,7 @@ static SPA_UNUSED int codec_decode(void *data, * When connected as SRC to SNK, FastStream sink may send back SBC data. */ -static int duplex_enum_config(const struct a2dp_codec *codec, +static int duplex_enum_config(const struct a2dp_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { @@ -614,17 +614,23 @@ static const struct a2dp_codec duplex_codec = { .reduce_bitpool = codec_reduce_bitpool, \ .increase_bitpool = codec_increase_bitpool -const struct a2dp_codec a2dp_codec_faststream = { +static const struct a2dp_codec a2dp_codec_faststream = { FASTSTREAM_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM, .name = "faststream", }; +static const struct spa_dict_item duplex_info_items[] = { + { "duplex.boost", "true" }, +}; +static const struct spa_dict duplex_info = SPA_DICT_INIT_ARRAY(duplex_info_items); + const struct a2dp_codec a2dp_codec_faststream_duplex = { FASTSTREAM_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX, .name = "faststream_duplex", .duplex_codec = &duplex_codec, + .info = &duplex_info, }; A2DP_CODEC_EXPORT_DEF( diff --git a/spa/plugins/bluez5/a2dp-codec-lc3plus.c b/spa/plugins/bluez5/a2dp-codec-lc3plus.c index b50c947468f479e1ad3915095530660519e140ab..40ca0e0db142fa809560a9599ecbc08d7ef2a1e3 100644 --- a/spa/plugins/bluez5/a2dp-codec-lc3plus.c +++ b/spa/plugins/bluez5/a2dp-codec-lc3plus.c @@ -150,8 +150,8 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return sizeof(conf); } -static int codec_caps_preference_cmp(const struct a2dp_codec *codec, const void *caps1, size_t caps1_size, - const void *caps2, size_t caps2_size, const struct a2dp_codec_audio_info *info) +static int codec_caps_preference_cmp(const struct a2dp_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, + const void *caps2, size_t caps2_size, const struct a2dp_codec_audio_info *info, const struct spa_dict *global_settings) { a2dp_lc3plus_hr_t conf1, conf2; a2dp_lc3plus_hr_t *conf; @@ -160,7 +160,7 @@ static int codec_caps_preference_cmp(const struct a2dp_codec *codec, const void /* Order selected configurations by preference */ res1 = codec->select_config(codec, 0, caps1, caps1_size, info, NULL, (uint8_t *)&conf1); - res2 = codec->select_config(codec, 0, caps2, caps2_size, info , NULL, (uint8_t *)&conf2); + res2 = codec->select_config(codec, 0, caps2, caps2_size, info, NULL, (uint8_t *)&conf2); #define PREFER_EXPR(expr) \ do { \ @@ -190,7 +190,7 @@ static int codec_caps_preference_cmp(const struct a2dp_codec *codec, const void #undef PREFER_BOOL } -static int codec_enum_config(const struct a2dp_codec *codec, +static int codec_enum_config(const struct a2dp_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { diff --git a/spa/plugins/bluez5/a2dp-codec-ldac.c b/spa/plugins/bluez5/a2dp-codec-ldac.c index ee6bc2ebf48300578696ff568c9f8c069a8f3cf9..3906104e87ab4b03d7bfb7cfffceb5550c10c174 100644 --- a/spa/plugins/bluez5/a2dp-codec-ldac.c +++ b/spa/plugins/bluez5/a2dp-codec-ldac.c @@ -150,7 +150,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return sizeof(conf); } -static int codec_enum_config(const struct a2dp_codec *codec, +static int codec_enum_config(const struct a2dp_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { @@ -284,7 +284,7 @@ static int string_to_eqmid(const char * eqmid) return LDACBT_EQMID_AUTO; } -static void *codec_init_props(const struct a2dp_codec *codec, const struct spa_dict *settings) +static void *codec_init_props(const struct a2dp_codec *codec, uint32_t flags, const struct spa_dict *settings) { struct props *p = calloc(1, sizeof(struct props)); const char *str; @@ -469,10 +469,10 @@ static void *codec_init(const struct a2dp_codec *codec, uint32_t flags, error_errno: res = -errno; error: - if (this->ldac) + if (this && this->ldac) ldacBT_free_handle(this->ldac); #ifdef ENABLE_LDAC_ABR - if (this->ldac_abr) + if (this && this->ldac_abr) ldac_ABR_free_handle(this->ldac_abr); #endif free(this); diff --git a/spa/plugins/bluez5/a2dp-codec-opus.c b/spa/plugins/bluez5/a2dp-codec-opus.c new file mode 100644 index 0000000000000000000000000000000000000000..7f1df12df331ed17d565a558ebdc3b9a2b3cf8e5 --- /dev/null +++ b/spa/plugins/bluez5/a2dp-codec-opus.c @@ -0,0 +1,1437 @@ +/* Spa A2DP Opus Codec + * + * Copyright © 2020 Wim Taymans + * Copyright © 2022 Pauli Virtanen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <unistd.h> +#include <string.h> +#include <stddef.h> +#include <errno.h> +#include <arpa/inet.h> +#if __BYTE_ORDER != __LITTLE_ENDIAN +#include <byteswap.h> +#endif + +#include <spa/debug/types.h> +#include <spa/param/audio/type-info.h> +#include <spa/param/audio/raw.h> +#include <spa/utils/string.h> +#include <spa/utils/dict.h> +#include <spa/param/audio/format.h> +#include <spa/param/audio/format-utils.h> + +#include <opus.h> +#include <opus_multistream.h> + +#include "rtp.h" +#include "a2dp-codecs.h" + +static struct spa_log *log; +static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.codecs.opus"); +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT &log_topic + +#define BUFSIZE_FROM_BITRATE(frame_dms,bitrate) ((bitrate)/8 * (frame_dms) / 10000 * 5/4) /* estimate */ + +/* + * Opus CVBR target bitrate. When connecting, it is set to the INITIAL + * value, and after that adjusted according to link quality between the MIN and + * MAX values. The bitrate adjusts up to either MAX or the value at + * which the socket buffer starts filling up, whichever is lower. + * + * With perfect connection quality, the target bitrate converges to the MAX + * value. Under realistic conditions, the upper limit may often be as low as + * 300-500kbit/s, so the INITIAL values are not higher than this. + * + * The MAX is here set to 2-2.5x and INITIAL to 1.5x the upper Opus recommended + * values [1], to be safer quality-wise for CVBR, and MIN to the lower + * recommended value. + * + * [1] https://wiki.xiph.org/Opus_Recommended_Settings + */ +#define BITRATE_INITIAL 192000 +#define BITRATE_MAX 320000 +#define BITRATE_MIN 96000 + +#define BITRATE_INITIAL_51 384000 +#define BITRATE_MAX_51 600000 +#define BITRATE_MIN_51 128000 + +#define BITRATE_INITIAL_71 450000 +#define BITRATE_MAX_71 900000 +#define BITRATE_MIN_71 256000 + +#define BITRATE_DUPLEX_BIDI 160000 + +#define OPUS_05_MAX_BYTES (15 * 1024) + +struct props { + uint32_t channels; + uint32_t coupled_streams; + uint32_t location; + uint32_t max_bitrate; + uint8_t frame_duration; + int application; + + uint32_t bidi_channels; + uint32_t bidi_coupled_streams; + uint32_t bidi_location; + uint32_t bidi_max_bitrate; + uint32_t bidi_frame_duration; + int bidi_application; +}; + +struct dec_data { + int fragment_size; + int fragment_count; + uint8_t fragment[OPUS_05_MAX_BYTES]; +}; + +struct abr { + uint64_t now; + uint64_t last_update; + + uint32_t buffer_level; + uint32_t packet_size; + uint32_t total_size; + bool bad; + + uint64_t last_change; + uint64_t retry_interval; + + bool prev_bad; +}; + +struct enc_data { + struct rtp_header *header; + struct rtp_payload *payload; + + struct abr abr; + + int samples; + int codesize; + + int packet_size; + int fragment_size; + int fragment_count; + void *fragment; + + int bitrate_min; + int bitrate_max; + + int bitrate; + int next_bitrate; + + int frame_dms; + int application; +}; + +struct impl { + OpusMSEncoder *enc; + OpusMSDecoder *dec; + + int mtu; + int samplerate; + int application; + + uint8_t channels; + uint8_t streams; + uint8_t coupled_streams; + + bool is_bidi; + + struct dec_data d; + struct enc_data e; +}; + +struct audio_location { + uint32_t mask; + enum spa_audio_channel position; +}; + +struct surround_encoder_mapping { + uint8_t channels; + uint8_t coupled_streams; + uint32_t location; + uint8_t mapping[8]; /**< permutation streams -> vorbis order */ + uint8_t inv_mapping[8]; /**< permutation vorbis order -> streams */ +}; + +/* Bluetooth SIG, Assigned Numbers, Generic Audio, Audio Location Definitions */ +#define BT_AUDIO_LOCATION_FL 0x00000001 /* Front Left */ +#define BT_AUDIO_LOCATION_FR 0x00000002 /* Front Right */ +#define BT_AUDIO_LOCATION_FC 0x00000004 /* Front Center */ +#define BT_AUDIO_LOCATION_LFE 0x00000008 /* Low Frequency Effects 1 */ +#define BT_AUDIO_LOCATION_RL 0x00000010 /* Back Left */ +#define BT_AUDIO_LOCATION_RR 0x00000020 /* Back Right */ +#define BT_AUDIO_LOCATION_FLC 0x00000040 /* Front Left of Center */ +#define BT_AUDIO_LOCATION_FRC 0x00000080 /* Front Right of Center */ +#define BT_AUDIO_LOCATION_RC 0x00000100 /* Back Center */ +#define BT_AUDIO_LOCATION_LFE2 0x00000200 /* Low Frequency Effects 2 */ +#define BT_AUDIO_LOCATION_SL 0x00000400 /* Side Left */ +#define BT_AUDIO_LOCATION_SR 0x00000800 /* Side Right */ +#define BT_AUDIO_LOCATION_TFL 0x00001000 /* Top Front Left */ +#define BT_AUDIO_LOCATION_TFR 0x00002000 /* Top Front Right */ +#define BT_AUDIO_LOCATION_TFC 0x00004000 /* Top Front Center */ +#define BT_AUDIO_LOCATION_TC 0x00008000 /* Top Center */ +#define BT_AUDIO_LOCATION_TRL 0x00010000 /* Top Back Left */ +#define BT_AUDIO_LOCATION_TRR 0x00020000 /* Top Back Right */ +#define BT_AUDIO_LOCATION_TSL 0x00040000 /* Top Side Left */ +#define BT_AUDIO_LOCATION_TSR 0x00080000 /* Top Side Right */ +#define BT_AUDIO_LOCATION_TRC 0x00100000 /* Top Back Center */ +#define BT_AUDIO_LOCATION_BC 0x00200000 /* Bottom Front Center */ +#define BT_AUDIO_LOCATION_BLC 0x00400000 /* Bottom Front Left */ +#define BT_AUDIO_LOCATION_BRC 0x00800000 /* Bottom Front Right */ +#define BT_AUDIO_LOCATION_FLW 0x01000000 /* Fron Left Wide */ +#define BT_AUDIO_LOCATION_FRW 0x02000000 /* Front Right Wide */ +#define BT_AUDIO_LOCATION_SSL 0x04000000 /* Left Surround */ +#define BT_AUDIO_LOCATION_SSR 0x08000000 /* Right Surround */ + +#define BT_AUDIO_LOCATION_ANY 0x0fffffff + +static const struct audio_location audio_locations[] = { + { BT_AUDIO_LOCATION_FL, SPA_AUDIO_CHANNEL_FL }, + { BT_AUDIO_LOCATION_FR, SPA_AUDIO_CHANNEL_FR }, + { BT_AUDIO_LOCATION_SL, SPA_AUDIO_CHANNEL_SL }, + { BT_AUDIO_LOCATION_SR, SPA_AUDIO_CHANNEL_SR }, + { BT_AUDIO_LOCATION_RL, SPA_AUDIO_CHANNEL_RL }, + { BT_AUDIO_LOCATION_RR, SPA_AUDIO_CHANNEL_RR }, + { BT_AUDIO_LOCATION_FLC, SPA_AUDIO_CHANNEL_FLC }, + { BT_AUDIO_LOCATION_FRC, SPA_AUDIO_CHANNEL_FRC }, + { BT_AUDIO_LOCATION_TFL, SPA_AUDIO_CHANNEL_TFL }, + { BT_AUDIO_LOCATION_TFR, SPA_AUDIO_CHANNEL_TFR }, + { BT_AUDIO_LOCATION_TSL, SPA_AUDIO_CHANNEL_TSL }, + { BT_AUDIO_LOCATION_TSR, SPA_AUDIO_CHANNEL_TSR }, + { BT_AUDIO_LOCATION_TRL, SPA_AUDIO_CHANNEL_TRL }, + { BT_AUDIO_LOCATION_TRR, SPA_AUDIO_CHANNEL_TRR }, + { BT_AUDIO_LOCATION_BLC, SPA_AUDIO_CHANNEL_BLC }, + { BT_AUDIO_LOCATION_BRC, SPA_AUDIO_CHANNEL_BRC }, + { BT_AUDIO_LOCATION_FLW, SPA_AUDIO_CHANNEL_FLW }, + { BT_AUDIO_LOCATION_FRW, SPA_AUDIO_CHANNEL_FRW }, + { BT_AUDIO_LOCATION_SSL, SPA_AUDIO_CHANNEL_SL }, /* ~ Side Left */ + { BT_AUDIO_LOCATION_SSR, SPA_AUDIO_CHANNEL_SR }, /* ~ Side Right */ + { BT_AUDIO_LOCATION_FC, SPA_AUDIO_CHANNEL_FC }, + { BT_AUDIO_LOCATION_RC, SPA_AUDIO_CHANNEL_RC }, + { BT_AUDIO_LOCATION_TFC, SPA_AUDIO_CHANNEL_TFC }, + { BT_AUDIO_LOCATION_TC, SPA_AUDIO_CHANNEL_TC }, + { BT_AUDIO_LOCATION_TRC, SPA_AUDIO_CHANNEL_TRC }, + { BT_AUDIO_LOCATION_BC, SPA_AUDIO_CHANNEL_BC }, + { BT_AUDIO_LOCATION_LFE, SPA_AUDIO_CHANNEL_LFE }, + { BT_AUDIO_LOCATION_LFE2, SPA_AUDIO_CHANNEL_LFE2 }, +}; + +/* Opus surround encoder mapping tables for the supported channel configurations */ +static const struct surround_encoder_mapping surround_encoders[] = { + { 1, 0, (0x0), + { 0 }, { 0 } }, + { 2, 1, (BT_AUDIO_LOCATION_FL | BT_AUDIO_LOCATION_FR), + { 0, 1 }, { 0, 1 } }, + { 3, 1, (BT_AUDIO_LOCATION_FL | BT_AUDIO_LOCATION_FR | BT_AUDIO_LOCATION_FC), + { 0, 2, 1 }, { 0, 2, 1 } }, + { 4, 2, (BT_AUDIO_LOCATION_FL | BT_AUDIO_LOCATION_FR | BT_AUDIO_LOCATION_RL | + BT_AUDIO_LOCATION_RR), + { 0, 1, 2, 3 }, { 0, 1, 2, 3 } }, + { 5, 2, (BT_AUDIO_LOCATION_FL | BT_AUDIO_LOCATION_FR | BT_AUDIO_LOCATION_RL | + BT_AUDIO_LOCATION_RR | BT_AUDIO_LOCATION_FC), + { 0, 4, 1, 2, 3 }, { 0, 2, 3, 4, 1 } }, + { 6, 2, (BT_AUDIO_LOCATION_FL | BT_AUDIO_LOCATION_FR | BT_AUDIO_LOCATION_RL | + BT_AUDIO_LOCATION_RR | BT_AUDIO_LOCATION_FC | + BT_AUDIO_LOCATION_LFE), + { 0, 4, 1, 2, 3, 5 }, { 0, 2, 3, 4, 1, 5 } }, + { 7, 3, (BT_AUDIO_LOCATION_FL | BT_AUDIO_LOCATION_FR | BT_AUDIO_LOCATION_SL | + BT_AUDIO_LOCATION_SR | BT_AUDIO_LOCATION_FC | + BT_AUDIO_LOCATION_RC | BT_AUDIO_LOCATION_LFE), + { 0, 4, 1, 2, 3, 5, 6 }, { 0, 2, 3, 4, 1, 5, 6 } }, + { 8, 3, (BT_AUDIO_LOCATION_FL | BT_AUDIO_LOCATION_FR | BT_AUDIO_LOCATION_SL | + BT_AUDIO_LOCATION_SR | BT_AUDIO_LOCATION_RL | + BT_AUDIO_LOCATION_RR | BT_AUDIO_LOCATION_FC | + BT_AUDIO_LOCATION_LFE), + { 0, 6, 1, 2, 3, 4, 5, 7 }, { 0, 2, 3, 4, 5, 6, 1, 7 } }, +}; + +static uint32_t bt_channel_from_name(const char *name) +{ + size_t i; + enum spa_audio_channel position = SPA_AUDIO_CHANNEL_UNKNOWN; + + 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))) { + position = spa_type_audio_channel[i].type; + break; + } + } + for (i = 0; i < SPA_N_ELEMENTS(audio_locations); i++) { + if (position == audio_locations[i].position) + return audio_locations[i].mask; + } + return 0; +} + +static uint32_t parse_locations(const char *str) +{ + char *s, *p, *save = NULL; + uint32_t location = 0; + + if (!str) + return 0; + + s = strdup(str); + if (s == NULL) + return 0; + + for (p = s; (p = strtok_r(p, ", ", &save)) != NULL; p = NULL) { + if (*p == '\0') + continue; + location |= bt_channel_from_name(p); + } + free(s); + + return location; +} + +static void parse_settings(struct props *props, const struct spa_dict *settings) +{ + const char *str; + uint32_t v; + + /* Pro Audio settings */ + spa_zero(*props); + props->channels = 8; + props->coupled_streams = 0; + props->location = 0; + props->max_bitrate = BITRATE_MAX; + props->frame_duration = OPUS_05_FRAME_DURATION_100; + props->application = OPUS_APPLICATION_AUDIO; + + props->bidi_channels = 1; + props->bidi_coupled_streams = 0; + props->bidi_location = 0; + props->bidi_max_bitrate = BITRATE_DUPLEX_BIDI; + props->bidi_frame_duration = OPUS_05_FRAME_DURATION_400; + props->bidi_application = OPUS_APPLICATION_AUDIO; + + if (settings == NULL) + return; + + if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.channels"), &v, 0)) + props->channels = SPA_CLAMP(v, 1u, SPA_AUDIO_MAX_CHANNELS); + if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.max-bitrate"), &v, 0)) + props->max_bitrate = SPA_MAX(v, (uint32_t)BITRATE_MIN); + if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.coupled-streams"), &v, 0)) + props->coupled_streams = SPA_CLAMP(v, 0u, props->channels / 2); + + if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.bidi.channels"), &v, 0)) + props->bidi_channels = SPA_CLAMP(v, 0u, SPA_AUDIO_MAX_CHANNELS); + if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.bidi.max-bitrate"), &v, 0)) + props->bidi_max_bitrate = SPA_MAX(v, (uint32_t)BITRATE_MIN); + if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.bidi.coupled-streams"), &v, 0)) + props->bidi_coupled_streams = SPA_CLAMP(v, 0u, props->bidi_channels / 2); + + str = spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.locations"); + props->location = parse_locations(str); + + str = spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.bidi.locations"); + props->bidi_location = parse_locations(str); + + str = spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.frame-dms"); + if (spa_streq(str, "25")) + props->frame_duration = OPUS_05_FRAME_DURATION_25; + else if (spa_streq(str, "50")) + props->frame_duration = OPUS_05_FRAME_DURATION_50; + else if (spa_streq(str, "100")) + props->frame_duration = OPUS_05_FRAME_DURATION_100; + else if (spa_streq(str, "200")) + props->frame_duration = OPUS_05_FRAME_DURATION_200; + else if (spa_streq(str, "400")) + props->frame_duration = OPUS_05_FRAME_DURATION_400; + + str = spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.bidi.frame-dms"); + if (spa_streq(str, "25")) + props->bidi_frame_duration = OPUS_05_FRAME_DURATION_25; + else if (spa_streq(str, "50")) + props->bidi_frame_duration = OPUS_05_FRAME_DURATION_50; + else if (spa_streq(str, "100")) + props->bidi_frame_duration = OPUS_05_FRAME_DURATION_100; + else if (spa_streq(str, "200")) + props->bidi_frame_duration = OPUS_05_FRAME_DURATION_200; + else if (spa_streq(str, "400")) + props->bidi_frame_duration = OPUS_05_FRAME_DURATION_400; + + str = spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.application"); + if (spa_streq(str, "audio")) + props->application = OPUS_APPLICATION_AUDIO; + else if (spa_streq(str, "voip")) + props->application = OPUS_APPLICATION_VOIP; + else if (spa_streq(str, "lowdelay")) + props->application = OPUS_APPLICATION_RESTRICTED_LOWDELAY; + + + str = spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.bidi.application"); + if (spa_streq(str, "audio")) + props->bidi_application = OPUS_APPLICATION_AUDIO; + else if (spa_streq(str, "voip")) + props->bidi_application = OPUS_APPLICATION_VOIP; + else if (spa_streq(str, "lowdelay")) + props->bidi_application = OPUS_APPLICATION_RESTRICTED_LOWDELAY; +} + +static int set_channel_conf(const struct a2dp_codec *codec, a2dp_opus_05_t *caps, const struct props *props) +{ + /* + * Predefined codec profiles + */ + if (caps->main.channels < 1) + return -EINVAL; + + caps->main.coupled_streams = 0; + OPUS_05_SET_LOCATION(caps->main, 0); + + caps->bidi.coupled_streams = 0; + OPUS_05_SET_LOCATION(caps->bidi, 0); + + switch (codec->id) { + case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05: + caps->main.channels = SPA_MIN(2, caps->main.channels); + if (caps->main.channels == 2) { + caps->main.coupled_streams = surround_encoders[1].coupled_streams; + OPUS_05_SET_LOCATION(caps->main, surround_encoders[1].location); + } + caps->bidi.channels = 0; + break; + case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_51: + if (caps->main.channels < 6) + return -EINVAL; + caps->main.channels = surround_encoders[5].channels; + caps->main.coupled_streams = surround_encoders[5].coupled_streams; + OPUS_05_SET_LOCATION(caps->main, surround_encoders[5].location); + caps->bidi.channels = 0; + break; + case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_71: + if (caps->main.channels < 8) + return -EINVAL; + caps->main.channels = surround_encoders[7].channels; + caps->main.coupled_streams = surround_encoders[7].coupled_streams; + OPUS_05_SET_LOCATION(caps->main, surround_encoders[7].location); + caps->bidi.channels = 0; + break; + case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_DUPLEX: + if (caps->bidi.channels < 1) + return -EINVAL; + caps->main.channels = SPA_MIN(2, caps->main.channels); + if (caps->main.channels == 2) { + caps->main.coupled_streams = surround_encoders[1].coupled_streams; + OPUS_05_SET_LOCATION(caps->main, surround_encoders[1].location); + } + caps->bidi.channels = SPA_MIN(2, caps->bidi.channels); + if (caps->bidi.channels == 2) { + caps->bidi.coupled_streams = surround_encoders[1].coupled_streams; + OPUS_05_SET_LOCATION(caps->bidi, surround_encoders[1].location); + } + break; + case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO: + if (caps->main.channels < props->channels) + return -EINVAL; + if (props->bidi_channels == 0 && caps->bidi.channels != 0) + return -EINVAL; + if (caps->bidi.channels < props->bidi_channels) + return -EINVAL; + caps->main.channels = props->channels; + caps->main.coupled_streams = props->coupled_streams; + OPUS_05_SET_LOCATION(caps->main, props->location); + caps->bidi.channels = props->bidi_channels; + caps->bidi.coupled_streams = props->bidi_coupled_streams; + OPUS_05_SET_LOCATION(caps->bidi, props->bidi_location); + break; + default: + spa_assert(false); + }; + + return 0; +} + +static void get_default_bitrates(const struct a2dp_codec *codec, bool bidi, int *min, int *max, int *init) +{ + int tmp; + + if (min == NULL) + min = &tmp; + if (max == NULL) + max = &tmp; + if (init == NULL) + init = &tmp; + + if (bidi) { + *min = SPA_MIN(BITRATE_MIN, BITRATE_DUPLEX_BIDI); + *max = BITRATE_DUPLEX_BIDI; + *init = BITRATE_DUPLEX_BIDI; + return; + } + + switch (codec->id) { + case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05: + *min = BITRATE_MIN; + *max = BITRATE_MAX; + *init = BITRATE_INITIAL; + break; + case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_51: + *min = BITRATE_MIN_51; + *max = BITRATE_MAX_51; + *init = BITRATE_INITIAL_51; + break; + case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_71: + *min = BITRATE_MIN_71; + *max = BITRATE_MAX_71; + *init = BITRATE_INITIAL_71; + break; + case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_DUPLEX: + *min = BITRATE_MIN; + *max = BITRATE_MAX; + *init = BITRATE_INITIAL; + break; + case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO: + default: + spa_assert_not_reached(); + }; +} + +static int get_mapping(const struct a2dp_codec *codec, const a2dp_opus_05_direction_t *conf, + bool use_surround_encoder, uint8_t *streams_ret, uint8_t *coupled_streams_ret, + const uint8_t **surround_mapping, uint32_t *positions) +{ + const uint8_t channels = conf->channels; + const uint32_t location = OPUS_05_GET_LOCATION(*conf); + const uint8_t coupled_streams = conf->coupled_streams; + const uint8_t *permutation = NULL; + size_t i, j; + + if (channels > SPA_AUDIO_MAX_CHANNELS) + return -EINVAL; + if (2 * coupled_streams > channels) + return -EINVAL; + + if (streams_ret) + *streams_ret = channels - coupled_streams; + if (coupled_streams_ret) + *coupled_streams_ret = coupled_streams; + + if (channels == 0) + return 0; + + if (use_surround_encoder) { + /* Opus surround encoder supports only some channel configurations, and + * needs a specific input channel ordering */ + for (i = 0; i < SPA_N_ELEMENTS(surround_encoders); ++i) { + const struct surround_encoder_mapping *m = &surround_encoders[i]; + + if (m->channels == channels && + m->coupled_streams == coupled_streams && + m->location == location) + { + spa_assert(channels <= SPA_N_ELEMENTS(m->inv_mapping)); + permutation = m->inv_mapping; + if (surround_mapping) + *surround_mapping = m->mapping; + break; + } + } + if (permutation == NULL && surround_mapping) + *surround_mapping = NULL; + } + + if (positions) { + for (i = 0, j = 0; i < SPA_N_ELEMENTS(audio_locations) && j < channels; ++i) { + const struct audio_location loc = audio_locations[i]; + + if (location & loc.mask) { + if (permutation) + positions[permutation[j++]] = loc.position; + else + positions[j++] = loc.position; + } + } + for (i = SPA_AUDIO_CHANNEL_START_Aux; j < channels; ++i, ++j) + positions[j] = i; + } + + return 0; +} + +static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, + uint8_t caps[A2DP_MAX_CAPS_SIZE]) +{ + a2dp_opus_05_t a2dp_opus_05 = { + .info = codec->vendor, + .main = { + .channels = SPA_AUDIO_MAX_CHANNELS, + .frame_duration = (OPUS_05_FRAME_DURATION_25 | + OPUS_05_FRAME_DURATION_50 | + OPUS_05_FRAME_DURATION_100 | + OPUS_05_FRAME_DURATION_200 | + OPUS_05_FRAME_DURATION_400), + OPUS_05_INIT_LOCATION(BT_AUDIO_LOCATION_ANY) + OPUS_05_INIT_BITRATE(0) + }, + .bidi = { + .channels = SPA_AUDIO_MAX_CHANNELS, + .frame_duration = (OPUS_05_FRAME_DURATION_25 | + OPUS_05_FRAME_DURATION_50 | + OPUS_05_FRAME_DURATION_100 | + OPUS_05_FRAME_DURATION_200 | + OPUS_05_FRAME_DURATION_400), + OPUS_05_INIT_LOCATION(BT_AUDIO_LOCATION_ANY) + OPUS_05_INIT_BITRATE(0) + } + }; + + /* Only duplex/pro codec has bidi, since bluez5-device has to know early + * whether to show nodes or not. */ + if (codec->id != SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_DUPLEX && + codec->id != SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO) + spa_zero(a2dp_opus_05.bidi); + + memcpy(caps, &a2dp_opus_05, sizeof(a2dp_opus_05)); + return sizeof(a2dp_opus_05); +} + +static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, + const struct a2dp_codec_audio_info *info, + const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE]) +{ + struct props props; + a2dp_opus_05_t conf; + int res; + int max; + + if (caps_size < sizeof(conf)) + return -EINVAL; + + memcpy(&conf, caps, sizeof(conf)); + + if (codec->vendor.vendor_id != conf.info.vendor_id || + codec->vendor.codec_id != conf.info.codec_id) + return -ENOTSUP; + + parse_settings(&props, global_settings); + + /* Channel Configuration & Audio Location */ + if ((res = set_channel_conf(codec, &conf, &props)) < 0) + return res; + + /* Limits */ + if (codec->id == SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO) { + max = props.max_bitrate; + if (OPUS_05_GET_BITRATE(conf.main) != 0) + OPUS_05_SET_BITRATE(conf.main, SPA_MIN(OPUS_05_GET_BITRATE(conf.main), max / 1024)); + else + OPUS_05_SET_BITRATE(conf.main, max / 1024); + + max = props.bidi_max_bitrate; + if (OPUS_05_GET_BITRATE(conf.bidi) != 0) + OPUS_05_SET_BITRATE(conf.bidi, SPA_MIN(OPUS_05_GET_BITRATE(conf.bidi), max / 1024)); + else + OPUS_05_SET_BITRATE(conf.bidi, max / 1024); + + if (conf.main.frame_duration & props.frame_duration) + conf.main.frame_duration = props.frame_duration; + else + return -EINVAL; + + if (conf.bidi.channels == 0) + true; + else if (conf.bidi.frame_duration & props.bidi_frame_duration) + conf.bidi.frame_duration = props.bidi_frame_duration; + else + return -EINVAL; + } else { + if (conf.main.frame_duration & OPUS_05_FRAME_DURATION_100) + conf.main.frame_duration = OPUS_05_FRAME_DURATION_100; + else if (conf.main.frame_duration & OPUS_05_FRAME_DURATION_200) + conf.main.frame_duration = OPUS_05_FRAME_DURATION_200; + else if (conf.main.frame_duration & OPUS_05_FRAME_DURATION_400) + conf.main.frame_duration = OPUS_05_FRAME_DURATION_400; + else if (conf.main.frame_duration & OPUS_05_FRAME_DURATION_50) + conf.main.frame_duration = OPUS_05_FRAME_DURATION_50; + else if (conf.main.frame_duration & OPUS_05_FRAME_DURATION_25) + conf.main.frame_duration = OPUS_05_FRAME_DURATION_25; + else + return -EINVAL; + + get_default_bitrates(codec, false, NULL, &max, NULL); + + if (OPUS_05_GET_BITRATE(conf.main) != 0) + OPUS_05_SET_BITRATE(conf.main, SPA_MIN(OPUS_05_GET_BITRATE(conf.main), max / 1024)); + else + OPUS_05_SET_BITRATE(conf.main, max / 1024); + + /* longer bidi frames appear to work better */ + if (conf.bidi.channels == 0) + true; + else if (conf.bidi.frame_duration & OPUS_05_FRAME_DURATION_200) + conf.bidi.frame_duration = OPUS_05_FRAME_DURATION_200; + else if (conf.bidi.frame_duration & OPUS_05_FRAME_DURATION_100) + conf.bidi.frame_duration = OPUS_05_FRAME_DURATION_100; + else if (conf.bidi.frame_duration & OPUS_05_FRAME_DURATION_400) + conf.bidi.frame_duration = OPUS_05_FRAME_DURATION_400; + else if (conf.bidi.frame_duration & OPUS_05_FRAME_DURATION_50) + conf.bidi.frame_duration = OPUS_05_FRAME_DURATION_50; + else if (conf.bidi.frame_duration & OPUS_05_FRAME_DURATION_25) + conf.bidi.frame_duration = OPUS_05_FRAME_DURATION_25; + else + return -EINVAL; + + get_default_bitrates(codec, true, NULL, &max, NULL); + + if (conf.bidi.channels == 0) + true; + else if (OPUS_05_GET_BITRATE(conf.bidi) != 0) + OPUS_05_SET_BITRATE(conf.bidi, SPA_MIN(OPUS_05_GET_BITRATE(conf.bidi), max / 1024)); + else + OPUS_05_SET_BITRATE(conf.bidi, max / 1024); + } + + memcpy(config, &conf, sizeof(conf)); + + return sizeof(conf); +} + +static int codec_caps_preference_cmp(const struct a2dp_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, + const void *caps2, size_t caps2_size, const struct a2dp_codec_audio_info *info, + const struct spa_dict *global_settings) +{ + a2dp_opus_05_t conf1, conf2, cap1, cap2; + a2dp_opus_05_t *conf; + int res1, res2; + int a, b; + + /* Order selected configurations by preference */ + res1 = codec->select_config(codec, flags, caps1, caps1_size, info, global_settings, (uint8_t *)&conf1); + res2 = codec->select_config(codec, flags, caps2, caps2_size, info, global_settings, (uint8_t *)&conf2); + +#define PREFER_EXPR(expr) \ + do { \ + conf = &conf1; \ + a = (expr); \ + conf = &conf2; \ + b = (expr); \ + if (a != b) \ + return b - a; \ + } while (0) + +#define PREFER_BOOL(expr) PREFER_EXPR((expr) ? 1 : 0) + + /* Prefer valid */ + a = (res1 > 0 && (size_t)res1 == sizeof(a2dp_opus_05_t)) ? 1 : 0; + b = (res2 > 0 && (size_t)res2 == sizeof(a2dp_opus_05_t)) ? 1 : 0; + if (!a || !b) + return b - a; + + memcpy(&cap1, caps1, sizeof(cap1)); + memcpy(&cap2, caps2, sizeof(cap2)); + + if (conf1.bidi.channels == 0 && conf2.bidi.channels == 0) { + /* If no bidi, prefer the SEP that has none */ + a = (cap1.bidi.channels == 0); + b = (cap2.bidi.channels == 0); + if (a != b) + return b - a; + } + + PREFER_EXPR(conf->main.channels); + PREFER_EXPR(conf->bidi.channels); + PREFER_EXPR(OPUS_05_GET_BITRATE(conf->main)); + PREFER_EXPR(OPUS_05_GET_BITRATE(conf->bidi)); + + return 0; + +#undef PREFER_EXPR +#undef PREFER_BOOL +} + +static bool is_duplex_codec(const struct a2dp_codec *codec) +{ + return codec->id == 0; +} + +static bool use_surround_encoder(const struct a2dp_codec *codec, bool is_sink) +{ + if (codec->id == SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO) + return false; + + if (is_duplex_codec(codec)) + return is_sink; + else + return !is_sink; +} + +static int codec_enum_config(const struct a2dp_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, uint32_t id, uint32_t idx, + struct spa_pod_builder *b, struct spa_pod **param) +{ + const bool surround_encoder = use_surround_encoder(codec, flags & A2DP_CODEC_FLAG_SINK); + a2dp_opus_05_t conf; + a2dp_opus_05_direction_t *dir; + struct spa_pod_frame f[1]; + uint32_t position[SPA_AUDIO_MAX_CHANNELS]; + + if (caps_size < sizeof(conf)) + return -EINVAL; + + memcpy(&conf, caps, sizeof(conf)); + + if (idx > 0) + return 0; + + dir = !is_duplex_codec(codec) ? &conf.main : &conf.bidi; + + if (get_mapping(codec, dir, surround_encoder, NULL, NULL, NULL, position) < 0) + return -EINVAL; + + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); + spa_pod_builder_add(b, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_F32), + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_ENUM_Int(6, + 48000, 48000, 24000, 16000, 12000, 8000), + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(dir->channels), + SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), + SPA_TYPE_Id, dir->channels, position), + 0); + + *param = spa_pod_builder_pop(b, &f[0]); + return *param == NULL ? -EIO : 1; +} + +static int codec_validate_config(const struct a2dp_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, + struct spa_audio_info *info) +{ + const bool surround_encoder = use_surround_encoder(codec, flags & A2DP_CODEC_FLAG_SINK); + const a2dp_opus_05_direction_t *dir1, *dir2; + const a2dp_opus_05_t *conf; + + if (caps == NULL || caps_size < sizeof(*conf)) + return -EINVAL; + + conf = caps; + + spa_zero(*info); + info->media_type = SPA_MEDIA_TYPE_audio; + info->media_subtype = SPA_MEDIA_SUBTYPE_raw; + info->info.raw.format = SPA_AUDIO_FORMAT_F32; + info->info.raw.rate = 0; /* not specified by config */ + + if (2 * conf->main.coupled_streams > conf->main.channels) + return -EINVAL; + if (2 * conf->bidi.coupled_streams > conf->bidi.channels) + return -EINVAL; + + if (!is_duplex_codec(codec)) { + dir1 = &conf->main; + dir2 = &conf->bidi; + } else { + dir1 = &conf->bidi; + dir2 = &conf->main; + } + + info->info.raw.channels = dir1->channels; + if (get_mapping(codec, dir1, surround_encoder, NULL, NULL, NULL, info->info.raw.position) < 0) + return -EINVAL; + if (get_mapping(codec, dir2, surround_encoder, NULL, NULL, NULL, NULL) < 0) + return -EINVAL; + + return 0; +} + +static size_t ceildiv(size_t v, size_t divisor) +{ + if (v % divisor == 0) + return v / divisor; + else + return v / divisor + 1; +} + +static bool check_bitrate_vs_frame_dms(struct impl *this, size_t bitrate) +{ + size_t header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); + size_t max_fragments = 0xf; + size_t payload_size = BUFSIZE_FROM_BITRATE(bitrate, this->e.frame_dms); + return (size_t)this->mtu >= header_size + ceildiv(payload_size, max_fragments); +} + +static int parse_frame_dms(int bitfield) +{ + switch (bitfield) { + case OPUS_05_FRAME_DURATION_25: + return 25; + case OPUS_05_FRAME_DURATION_50: + return 50; + case OPUS_05_FRAME_DURATION_100: + return 100; + case OPUS_05_FRAME_DURATION_200: + return 200; + case OPUS_05_FRAME_DURATION_400: + return 400; + default: + return -EINVAL; + } +} + +static void *codec_init_props(const struct a2dp_codec *codec, uint32_t flags, const struct spa_dict *settings) +{ + struct props *p; + + if (codec->id != SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO) + return NULL; + + p = calloc(1, sizeof(struct props)); + if (p == NULL) + return NULL; + + parse_settings(p, settings); + + return p; +} + +static void codec_clear_props(void *props) +{ + free(props); +} + +static void *codec_init(const struct a2dp_codec *codec, uint32_t flags, + void *config, size_t config_len, const struct spa_audio_info *info, + void *props, size_t mtu) +{ + const bool surround_encoder = use_surround_encoder(codec, flags & A2DP_CODEC_FLAG_SINK); + a2dp_opus_05_t *conf = config; + a2dp_opus_05_direction_t *dir; + struct impl *this = NULL; + struct spa_audio_info config_info; + const uint8_t *enc_mapping = NULL; + unsigned char mapping[256]; + size_t i; + int res; + + if (info->media_type != SPA_MEDIA_TYPE_audio || + info->media_subtype != SPA_MEDIA_SUBTYPE_raw || + info->info.raw.format != SPA_AUDIO_FORMAT_F32) { + res = -EINVAL; + goto error; + } + + if ((this = calloc(1, sizeof(struct impl))) == NULL) + goto error_errno; + + this->is_bidi = is_duplex_codec(codec); + dir = !this->is_bidi ? &conf->main : &conf->bidi; + + if ((res = codec_validate_config(codec, flags, config, config_len, &config_info)) < 0) + goto error; + if ((res = get_mapping(codec, dir, surround_encoder, &this->streams, &this->coupled_streams, + &enc_mapping, NULL)) < 0) + goto error; + if (config_info.info.raw.channels != info->info.raw.channels) { + res = -EINVAL; + goto error; + } + + this->mtu = mtu; + this->samplerate = info->info.raw.rate; + this->channels = config_info.info.raw.channels; + this->application = OPUS_APPLICATION_AUDIO; + + if (codec->id == SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO && props) { + struct props *p = props; + this->application = !this->is_bidi ? p->application : + p->bidi_application; + } + + /* + * Setup encoder + */ + if (enc_mapping) { + int streams, coupled_streams; + bool incompatible_opus_surround_encoder = false; + + this->enc = opus_multistream_surround_encoder_create( + this->samplerate, this->channels, 1, &streams, &coupled_streams, + mapping, this->application, &res); + + if (this->enc) { + /* Check surround encoder channel mapping is what we want */ + if (streams != this->streams || coupled_streams != this->coupled_streams) + incompatible_opus_surround_encoder = true; + for (i = 0; i < this->channels; ++i) + if (enc_mapping[i] != mapping[i]) + incompatible_opus_surround_encoder = true; + } + + /* Assert: this should never happen */ + spa_assert(!incompatible_opus_surround_encoder); + if (incompatible_opus_surround_encoder) { + res = -EINVAL; + goto error; + } + } else { + for (i = 0; i < this->channels; ++i) + mapping[i] = i; + this->enc = opus_multistream_encoder_create( + this->samplerate, this->channels, this->streams, this->coupled_streams, + mapping, this->application, &res); + } + if (this->enc == NULL) { + res = -EINVAL; + goto error; + } + + if ((this->e.frame_dms = parse_frame_dms(dir->frame_duration)) < 0) { + res = -EINVAL; + goto error; + } + + if (codec->id != SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO) { + get_default_bitrates(codec, this->is_bidi, &this->e.bitrate_min, + &this->e.bitrate_max, &this->e.bitrate); + this->e.bitrate_max = SPA_MIN(this->e.bitrate_max, + OPUS_05_GET_BITRATE(*dir) * 1024); + } else { + this->e.bitrate_max = OPUS_05_GET_BITRATE(*dir) * 1024; + this->e.bitrate_min = BITRATE_MIN; + this->e.bitrate = BITRATE_INITIAL; + } + + this->e.bitrate_min = SPA_MIN(this->e.bitrate_min, this->e.bitrate_max); + this->e.bitrate = SPA_CLAMP(this->e.bitrate, this->e.bitrate_min, this->e.bitrate_max); + + this->e.next_bitrate = this->e.bitrate; + opus_multistream_encoder_ctl(this->enc, OPUS_SET_BITRATE(this->e.bitrate)); + + this->e.samples = this->e.frame_dms * this->samplerate / 10000; + this->e.codesize = this->e.samples * (int)this->channels * sizeof(float); + + + /* + * Setup decoder + */ + for (i = 0; i < this->channels; ++i) + mapping[i] = i; + this->dec = opus_multistream_decoder_create( + this->samplerate, this->channels, + this->streams, this->coupled_streams, + mapping, &res); + if (this->dec == NULL) { + res = -EINVAL; + goto error; + } + + return this; + +error_errno: + res = -errno; + goto error; + +error: + if (this && this->enc) + opus_multistream_encoder_destroy(this->enc); + if (this && this->dec) + opus_multistream_decoder_destroy(this->dec); + free(this); + errno = -res; + return NULL; +} + +static void codec_deinit(void *data) +{ + struct impl *this = data; + opus_multistream_encoder_destroy(this->enc); + opus_multistream_decoder_destroy(this->dec); + free(this); +} + +static int codec_get_block_size(void *data) +{ + struct impl *this = data; + return this->e.codesize; +} + +static int codec_update_bitrate(struct impl *this) +{ + this->e.next_bitrate = SPA_CLAMP(this->e.next_bitrate, + this->e.bitrate_min, this->e.bitrate_max); + + if (!check_bitrate_vs_frame_dms(this, this->e.next_bitrate)) { + this->e.next_bitrate = this->e.bitrate; + return 0; + } + + this->e.bitrate = this->e.next_bitrate; + opus_multistream_encoder_ctl(this->enc, OPUS_SET_BITRATE(this->e.bitrate)); + return 0; +} + +static int codec_start_encode (void *data, + void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) +{ + struct impl *this = data; + size_t header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); + + if (dst_size <= header_size) + return -EINVAL; + + codec_update_bitrate(this); + + this->e.header = (struct rtp_header *)dst; + this->e.payload = SPA_PTROFF(dst, sizeof(struct rtp_header), struct rtp_payload); + memset(dst, 0, header_size); + + this->e.payload->frame_count = 0; + this->e.header->v = 2; + this->e.header->pt = 96; + this->e.header->sequence_number = htons(seqnum); + this->e.header->timestamp = htonl(timestamp); + this->e.header->ssrc = htonl(1); + + this->e.packet_size = header_size; + return this->e.packet_size; +} + +static int codec_encode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out, int *need_flush) +{ + struct impl *this = data; + const int header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); + int size; + int res; + + if (src == NULL) { + /* Produce fragment packets. + * + * We assume the caller gives the same buffer here as in the previous + * calls to encode(), without changes in the buffer content. + */ + if (this->e.fragment == NULL || + this->e.fragment_count <= 1 || + this->e.fragment < dst || + SPA_PTROFF(this->e.fragment, this->e.fragment_size, void) > SPA_PTROFF(dst, dst_size, void)) { + this->e.fragment = NULL; + return -EINVAL; + } + + size = SPA_MIN(this->mtu - header_size, this->e.fragment_size); + memmove(dst, this->e.fragment, size); + *dst_out = size; + + this->e.payload->is_fragmented = 1; + this->e.payload->frame_count = --this->e.fragment_count; + this->e.payload->is_last_fragment = (this->e.fragment_count == 1); + + if (this->e.fragment_size > size && this->e.fragment_count > 1) { + this->e.fragment = SPA_PTROFF(this->e.fragment, size, void); + this->e.fragment_size -= size; + *need_flush = NEED_FLUSH_FRAGMENT; + } else { + this->e.fragment = NULL; + *need_flush = NEED_FLUSH_ALL; + } + return 0; + } + + if (src_size < (size_t)this->e.codesize) { + *dst_out = 0; + return 0; + } + + res = opus_multistream_encode_float( + this->enc, src, this->e.samples, dst, dst_size); + if (res < 0) + return -EINVAL; + *dst_out = res; + + this->e.packet_size += res; + this->e.payload->frame_count++; + + if (this->e.packet_size > this->mtu) { + /* Fragment packet */ + this->e.fragment_count = ceildiv(this->e.packet_size - header_size, + this->mtu - header_size); + + this->e.payload->is_fragmented = 1; + this->e.payload->is_first_fragment = 1; + this->e.payload->frame_count = this->e.fragment_count; + + this->e.fragment_size = this->e.packet_size - this->mtu; + this->e.fragment = SPA_PTROFF(dst, *dst_out - this->e.fragment_size, void); + *need_flush = NEED_FLUSH_FRAGMENT; + + /* + * We keep the rest of the encoded frame in the same buffer, and rely + * that the caller won't overwrite it before the next call to encode() + */ + *dst_out = SPA_PTRDIFF(this->e.fragment, dst); + } else { + *need_flush = NEED_FLUSH_ALL; + } + + return this->e.codesize; +} + +static SPA_UNUSED int codec_start_decode (void *data, + const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) +{ + struct impl *this = data; + const struct rtp_header *header = src; + const struct rtp_payload *payload = SPA_PTROFF(src, sizeof(struct rtp_header), void); + size_t header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); + + spa_return_val_if_fail (src_size > header_size, -EINVAL); + + if (seqnum) + *seqnum = ntohs(header->sequence_number); + if (timestamp) + *timestamp = ntohl(header->timestamp); + + if (payload->is_fragmented) { + if (payload->is_first_fragment) { + this->d.fragment_size = 0; + } else if (payload->frame_count + 1 != this->d.fragment_count || + (payload->frame_count == 1 && !payload->is_last_fragment)){ + /* Fragments not in right order: drop packet */ + return -EINVAL; + } + this->d.fragment_count = payload->frame_count; + } else { + if (payload->frame_count != 1) + return -EINVAL; + this->d.fragment_count = 0; + } + + return header_size; +} + +static SPA_UNUSED int codec_decode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out) +{ + struct impl *this = data; + int consumed = src_size; + int res; + int dst_samples; + + if (this->d.fragment_count > 0) { + /* Fragmented frame */ + size_t avail; + avail = SPA_MIN(sizeof(this->d.fragment) - this->d.fragment_size, src_size); + memcpy(SPA_PTROFF(this->d.fragment, this->d.fragment_size, void), src, avail); + + this->d.fragment_size += avail; + + if (this->d.fragment_count > 1) { + /* More fragments to come */ + *dst_out = 0; + return consumed; + } + + src = this->d.fragment; + src_size = this->d.fragment_size; + + this->d.fragment_count = 0; + this->d.fragment_size = 0; + } + + dst_samples = dst_size / (sizeof(float) * this->channels); + res = opus_multistream_decode_float(this->dec, src, src_size, dst, dst_samples, 0); + if (res < 0) + return -EINVAL; + *dst_out = (size_t)res * this->channels * sizeof(float); + + return consumed; +} + +static int codec_abr_process(void *data, size_t unsent) +{ + const uint64_t interval = SPA_NSEC_PER_SEC; + struct impl *this = data; + struct abr *abr = &this->e.abr; + bool level_bad, level_good; + uint32_t actual_bitrate; + + abr->total_size += this->e.packet_size; + + if (this->e.payload->is_fragmented && !this->e.payload->is_first_fragment) + return 0; + + abr->now += this->e.frame_dms * SPA_NSEC_PER_MSEC / 10; + + abr->buffer_level = SPA_MAX(abr->buffer_level, unsent); + abr->packet_size = SPA_MAX(abr->packet_size, (uint32_t)this->e.packet_size); + abr->packet_size = SPA_MAX(abr->packet_size, 128u); + + level_bad = abr->buffer_level > 2 * (uint32_t)this->mtu || abr->bad; + level_good = abr->buffer_level == 0; + + if (!(abr->last_update + interval <= abr->now || + (level_bad && abr->last_change + interval <= abr->now))) + return 0; + + actual_bitrate = (uint64_t)abr->total_size*8*SPA_NSEC_PER_SEC + / SPA_MAX(1u, abr->now - abr->last_update); + + spa_log_debug(log, "opus ABR bitrate:%d actual:%d level:%d (%s) bad:%d retry:%ds size:%d", + (int)this->e.bitrate, + (int)actual_bitrate, + (int)abr->buffer_level, + level_bad ? "bad" : (level_good ? "good" : "-"), + (int)abr->bad, + (int)(abr->retry_interval / SPA_NSEC_PER_SEC), + (int)abr->packet_size); + + if (level_bad) { + this->e.next_bitrate = this->e.bitrate * 11 / 12; + abr->last_change = abr->now; + abr->retry_interval = SPA_MIN(abr->retry_interval + 10*interval, + 30 * interval); + } else if (!level_good) { + abr->last_change = abr->now; + } else if (abr->now < abr->last_change + abr->retry_interval) { + /* noop */ + } else if (actual_bitrate*3/2 < (uint32_t)this->e.bitrate) { + /* actual bitrate is small compared to target; probably silence */ + } else { + this->e.next_bitrate = this->e.bitrate + + SPA_MAX(1, this->e.bitrate_max / 40); + abr->last_change = abr->now; + abr->retry_interval = SPA_MAX(abr->retry_interval, (5+4)*interval) + - 4*interval; + } + + abr->last_update = abr->now; + abr->buffer_level = 0; + abr->bad = false; + abr->packet_size = 0; + abr->total_size = 0; + + return 0; +} + +static int codec_reduce_bitpool(void *data) +{ + struct impl *this = data; + struct abr *abr = &this->e.abr; + abr->bad = true; + return 0; +} + +static int codec_increase_bitpool(void *data) +{ + return 0; +} + +static void codec_set_log(struct spa_log *global_log) +{ + log = global_log; + spa_log_topic_init(log, &log_topic); +} + +#define OPUS_05_COMMON_DEFS \ + .codec_id = A2DP_CODEC_VENDOR, \ + .vendor = { .vendor_id = OPUS_05_VENDOR_ID, \ + .codec_id = OPUS_05_CODEC_ID }, \ + .fill_caps = codec_fill_caps, \ + .select_config = codec_select_config, \ + .enum_config = codec_enum_config, \ + .validate_config = codec_validate_config, \ + .caps_preference_cmp = codec_caps_preference_cmp, \ + .init = codec_init, \ + .deinit = codec_deinit, \ + .get_block_size = codec_get_block_size, \ + .abr_process = codec_abr_process, \ + .start_encode = codec_start_encode, \ + .encode = codec_encode, \ + .reduce_bitpool = codec_reduce_bitpool, \ + .increase_bitpool = codec_increase_bitpool, \ + .set_log = codec_set_log + +#define OPUS_05_COMMON_FULL_DEFS \ + OPUS_05_COMMON_DEFS, \ + .start_decode = codec_start_decode, \ + .decode = codec_decode + +const struct a2dp_codec a2dp_codec_opus_05 = { + OPUS_05_COMMON_FULL_DEFS, + .id = SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05, + .name = "opus_05", + .description = "Opus", +}; + +const struct a2dp_codec a2dp_codec_opus_05_51 = { + OPUS_05_COMMON_DEFS, + .id = SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_51, + .name = "opus_05_51", + .description = "Opus 5.1 Surround", +}; + +const struct a2dp_codec a2dp_codec_opus_05_71 = { + OPUS_05_COMMON_DEFS, + .id = SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_71, + .name = "opus_05_71", + .description = "Opus 7.1 Surround", +}; + +/* Bidi return channel codec: doesn't have endpoints */ +const struct a2dp_codec a2dp_codec_opus_05_return = { + OPUS_05_COMMON_FULL_DEFS, + .id = 0, + .name = "opus_05_duplex_bidi", + .description = "Opus Duplex Bidi channel", +}; + +const struct a2dp_codec a2dp_codec_opus_05_duplex = { + OPUS_05_COMMON_FULL_DEFS, + .id = SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_DUPLEX, + .name = "opus_05_duplex", + .description = "Opus Duplex", + .duplex_codec = &a2dp_codec_opus_05_return, +}; + +const struct a2dp_codec a2dp_codec_opus_05_pro = { + OPUS_05_COMMON_DEFS, + .id = SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO, + .name = "opus_05_pro", + .description = "Opus Pro Audio", + .init_props = codec_init_props, + .clear_props = codec_clear_props, + .duplex_codec = &a2dp_codec_opus_05_return, +}; + +A2DP_CODEC_EXPORT_DEF( + "opus", + &a2dp_codec_opus_05, + &a2dp_codec_opus_05_51, + &a2dp_codec_opus_05_71, + &a2dp_codec_opus_05_duplex, + &a2dp_codec_opus_05_pro +); diff --git a/spa/plugins/bluez5/a2dp-codec-sbc.c b/spa/plugins/bluez5/a2dp-codec-sbc.c index 6a43c666a6dc4010477f1c32d5ba57ef75776314..35d55355a1c028bc765909932d3d15b794acb5f6 100644 --- a/spa/plugins/bluez5/a2dp-codec-sbc.c +++ b/spa/plugins/bluez5/a2dp-codec-sbc.c @@ -229,8 +229,8 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return sizeof(conf); } -static int codec_caps_preference_cmp(const struct a2dp_codec *codec, const void *caps1, size_t caps1_size, - const void *caps2, size_t caps2_size, const struct a2dp_codec_audio_info *info) +static int codec_caps_preference_cmp(const struct a2dp_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, + const void *caps2, size_t caps2_size, const struct a2dp_codec_audio_info *info, const struct spa_dict *global_settings) { a2dp_sbc_t conf1, conf2; a2dp_sbc_t *conf; @@ -356,7 +356,7 @@ static int codec_set_bitpool(struct impl *this, int bitpool) return this->sbc.bitpool; } -static int codec_enum_config(const struct a2dp_codec *codec, +static int codec_enum_config(const struct a2dp_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { diff --git a/spa/plugins/bluez5/a2dp-codecs.c b/spa/plugins/bluez5/a2dp-codecs.c index c78b2f261f3e4f8aed5c92f0fc7278dfee2d0b5a..42c7a193c1a759b6ecd5892928157a5d4b968e68 100644 --- a/spa/plugins/bluez5/a2dp-codecs.c +++ b/spa/plugins/bluez5/a2dp-codecs.c @@ -62,7 +62,8 @@ int a2dp_codec_select_config(const struct a2dp_codec_config configs[], size_t n, bool a2dp_codec_check_caps(const struct a2dp_codec *codec, unsigned int codec_id, const void *caps, size_t caps_size, - const struct a2dp_codec_audio_info *info) + const struct a2dp_codec_audio_info *info, + const struct spa_dict *global_settings) { uint8_t config[A2DP_MAX_CAPS_SIZE]; int res; @@ -73,7 +74,7 @@ bool a2dp_codec_check_caps(const struct a2dp_codec *codec, unsigned int codec_id if (caps == NULL) return false; - res = codec->select_config(codec, 0, caps, caps_size, info, NULL, config); + res = codec->select_config(codec, 0, caps, caps_size, info, global_settings, config); if (res < 0) return false; diff --git a/spa/plugins/bluez5/a2dp-codecs.h b/spa/plugins/bluez5/a2dp-codecs.h index bd041b29efda980c721b5c9959c115e7313d22e9..7fb5cd510611b6c48591b97d822d58649955633b 100644 --- a/spa/plugins/bluez5/a2dp-codecs.h +++ b/spa/plugins/bluez5/a2dp-codecs.h @@ -33,6 +33,7 @@ #include <spa/support/plugin.h> #include <spa/pod/pod.h> #include <spa/pod/builder.h> +#include <spa/support/log.h> #include "a2dp-codec-caps.h" @@ -43,7 +44,7 @@ #define SPA_TYPE_INTERFACE_Bluez5CodecA2DP SPA_TYPE_INFO_INTERFACE_BASE "Bluez5:Codec:A2DP:Private" -#define SPA_VERSION_BLUEZ5_CODEC_A2DP 1 +#define SPA_VERSION_BLUEZ5_CODEC_A2DP 5 struct spa_bluez5_codec_a2dp { struct spa_interface iface; @@ -62,6 +63,7 @@ extern const struct a2dp_codec * const * const codec_plugin_a2dp_codecs; extern const char *codec_plugin_factory_name; #endif +#define A2DP_CODEC_FLAG_SINK (1 << 0) #define A2DP_CODEC_DEFAULT_RATE 48000 #define A2DP_CODEC_DEFAULT_CHANNELS 2 @@ -96,8 +98,8 @@ struct a2dp_codec { int (*select_config) (const struct a2dp_codec *codec, uint32_t flags, const void *caps, size_t caps_size, const struct a2dp_codec_audio_info *info, - const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE]); - int (*enum_config) (const struct a2dp_codec *codec, + const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE]); + int (*enum_config) (const struct a2dp_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *builder, struct spa_pod **param); int (*validate_config) (const struct a2dp_codec *codec, uint32_t flags, @@ -109,10 +111,11 @@ struct a2dp_codec { * The caps handed in correspond to this codec_id, but are * otherwise not checked beforehand. */ - int (*caps_preference_cmp) (const struct a2dp_codec *codec, const void *caps1, size_t caps1_size, - const void *caps2, size_t caps2_size, const struct a2dp_codec_audio_info *info); + int (*caps_preference_cmp) (const struct a2dp_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, + const void *caps2, size_t caps2_size, const struct a2dp_codec_audio_info *info, + const struct spa_dict *global_settings); - void *(*init_props) (const struct a2dp_codec *codec, const struct spa_dict *settings); + void *(*init_props) (const struct a2dp_codec *codec, uint32_t flags, const struct spa_dict *settings); void (*clear_props) (void *); int (*enum_props) (void *props, const struct spa_dict *settings, uint32_t id, uint32_t idx, struct spa_pod_builder *builder, struct spa_pod **param); @@ -144,6 +147,8 @@ struct a2dp_codec { int (*reduce_bitpool) (void *data); int (*increase_bitpool) (void *data); + + void (*set_log) (struct spa_log *global_log); }; struct a2dp_codec_config { @@ -156,6 +161,7 @@ int a2dp_codec_select_config(const struct a2dp_codec_config configs[], size_t n, uint32_t cap, int preferred_value); bool a2dp_codec_check_caps(const struct a2dp_codec *codec, unsigned int codec_id, - const void *caps, size_t caps_size, const struct a2dp_codec_audio_info *info); + const void *caps, size_t caps_size, const struct a2dp_codec_audio_info *info, + const struct spa_dict *global_settings); #endif diff --git a/spa/plugins/bluez5/a2dp-sink.c b/spa/plugins/bluez5/a2dp-sink.c index 35a2a60d3a145d68dc397b8718d91ab4f9c6405c..fac3b57a7c5071974f213dae06d6e6b2e89eb1b3 100644 --- a/spa/plugins/bluez5/a2dp-sink.c +++ b/spa/plugins/bluez5/a2dp-sink.c @@ -137,6 +137,8 @@ struct impl { unsigned int started:1; unsigned int following:1; + unsigned int is_duplex:1; + struct spa_source source; int timerfd; struct spa_source flush_source; @@ -743,7 +745,7 @@ again: * => timeout = (quantum - max_excess)/quantum * packet_time */ uint64_t max_excess = 2*256; - uint64_t packet_samples = this->frame_count * this->block_size / port->frame_size; + uint64_t packet_samples = (uint64_t)this->frame_count * this->block_size / port->frame_size; uint64_t packet_time = packet_samples * SPA_NSEC_PER_SEC / port->current_format.info.raw.rate; uint64_t quantum = SPA_LIKELY(this->clock) ? this->clock->duration : 0; uint64_t timeout = (quantum > max_excess) ? @@ -843,7 +845,7 @@ static void a2dp_on_timeout(struct spa_source *source) prev_time = this->current_time; now_time = this->current_time = this->next_time; - spa_log_debug(this->log, "%p: timeout %"PRIu64" %"PRIu64"", this, + spa_log_debug(this->log, "%p: timer %"PRIu64" %"PRIu64"", this, now_time, now_time - prev_time); if (SPA_LIKELY(this->position)) { @@ -908,7 +910,8 @@ static int do_start(struct impl *this) for (i = 0; i < size; i++) spa_log_debug(this->log, " %d: %02x", i, conf[i]); - this->codec_data = this->codec->init(this->codec, 0, + this->codec_data = this->codec->init(this->codec, + this->is_duplex ? A2DP_CODEC_FLAG_SINK : 0, this->transport->configuration, this->transport->configuration_len, &port->current_format, @@ -1195,6 +1198,7 @@ impl_node_port_enum_params(void *object, int seq, return -EIO; if ((res = this->codec->enum_config(this->codec, + this->is_duplex ? A2DP_CODEC_FLAG_SINK : 0, this->transport->configuration, this->transport->configuration_len, id, result.index, &b, ¶m)) != 1) @@ -1457,8 +1461,8 @@ static int impl_node_process(void *object) spa_return_val_if_fail(this != NULL, -EINVAL); port = &this->port; - io = port->io; - spa_return_val_if_fail(io != NULL, -EIO); + if ((io = port->io) == NULL) + return -EIO; if (this->position && this->position->clock.flags & SPA_IO_CLOCK_FLAG_FREEWHEEL) { io->status = SPA_STATUS_NEED_DATA; @@ -1697,6 +1701,9 @@ impl_init(const struct spa_handle_factory *factory, spa_list_init(&port->ready); + if (info && (str = spa_dict_lookup(info, "api.bluez5.a2dp-duplex")) != NULL) + this->is_duplex = spa_atob(str); + if (info && (str = spa_dict_lookup(info, SPA_KEY_API_BLUEZ5_TRANSPORT))) sscanf(str, "pointer:%p", &this->transport); @@ -1708,9 +1715,20 @@ impl_init(const struct spa_handle_factory *factory, spa_log_error(this->log, "a transport codec is needed"); return -EINVAL; } + this->codec = this->transport->a2dp_codec; + + if (this->is_duplex) { + if (!this->codec->duplex_codec) { + spa_log_error(this->log, "transport codec doesn't support duplex"); + return -EINVAL; + } + this->codec = this->codec->duplex_codec; + } + if (this->codec->init_props != NULL) this->codec_props = this->codec->init_props(this->codec, + this->is_duplex ? A2DP_CODEC_FLAG_SINK : 0, this->transport->device->settings); reset_props(this, &this->props); diff --git a/spa/plugins/bluez5/a2dp-source.c b/spa/plugins/bluez5/a2dp-source.c index e5618ee272680a33d89e7624f141d264ade426a6..0082b9d1e3ee25a4fa60160b49885b7a6667598d 100644 --- a/spa/plugins/bluez5/a2dp-source.c +++ b/spa/plugins/bluez5/a2dp-source.c @@ -60,18 +60,16 @@ static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.source.a2dp #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic +#include "decode-buffer.h" + #define DEFAULT_CLOCK_NAME "clock.system.monotonic" struct props { - uint32_t min_latency; - uint32_t max_latency; char clock_name[64]; }; #define FILL_FRAMES 2 #define MAX_BUFFERS 32 -#define MIN_LATENCY 512 -#define MAX_LATENCY 1024 struct buffer { uint32_t id; @@ -89,6 +87,7 @@ struct port { uint64_t info_all; struct spa_port_info info; struct spa_io_buffers *io; + struct spa_io_rate_match *rate_match; struct spa_latency_info latency; #define IDX_EnumFormat 0 #define IDX_Meta 1 @@ -105,8 +104,7 @@ struct port { struct spa_list free; struct spa_list ready; - struct buffer *current_buffer; - uint32_t ready_offset; + struct spa_bt_decode_buffer buffer; }; struct impl { @@ -120,6 +118,8 @@ struct impl { struct spa_hook_list hooks; struct spa_callbacks callbacks; + uint32_t quantum_limit; + uint64_t info_all; struct spa_node_info info; #define IDX_PropInfo 0 @@ -137,16 +137,25 @@ struct impl { unsigned int started:1; unsigned int transport_acquired:1; unsigned int following:1; + unsigned int matching:1; + unsigned int resampling:1; unsigned int is_input:1; unsigned int is_duplex:1; + unsigned int use_duplex_source:1; int fd; struct spa_source source; + struct spa_source timer_source; + int timerfd; + struct spa_io_clock *clock; struct spa_io_position *position; + uint64_t current_time; + uint64_t next_time; + const struct a2dp_codec *codec; bool codec_props_changed; void *codec_props; @@ -154,10 +163,8 @@ struct impl { struct spa_audio_info codec_format; uint8_t buffer_read[4096]; - uint8_t buffer_decoded[65536]; struct timespec now; uint64_t sample_count; - uint64_t skip_count; int duplex_timerfd; uint64_t duplex_timeout; @@ -165,13 +172,8 @@ struct impl { #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) == 0) -static const uint32_t default_min_latency = MIN_LATENCY; -static const uint32_t default_max_latency = MAX_LATENCY; - static void reset_props(struct props *props) { - props->min_latency = default_min_latency; - props->max_latency = default_max_latency; strncpy(props->clock_name, DEFAULT_CLOCK_NAME, sizeof(props->clock_name)); } @@ -200,43 +202,19 @@ static int impl_node_enum_params(void *object, int seq, switch (id) { case SPA_PARAM_PropInfo: { - struct props *p = &this->props; - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_minLatency), - SPA_PROP_INFO_description, SPA_POD_String("The minimum latency"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->min_latency, 1, INT32_MAX)); - break; - case 1: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_maxLatency), - SPA_PROP_INFO_description, SPA_POD_String("The maximum latency"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->max_latency, 1, INT32_MAX)); - break; default: enum_codec = true; - index_offset = 2; + index_offset = 0; } break; } case SPA_PARAM_Props: { - struct props *p = &this->props; - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_Props, id, - SPA_PROP_minLatency, SPA_POD_Int(p->min_latency), - SPA_PROP_maxLatency, SPA_POD_Int(p->max_latency)); - break; default: enum_codec = true; - index_offset = 1; + index_offset = 0; } break; } @@ -267,13 +245,38 @@ static int impl_node_enum_params(void *object, int seq, return 0; } -static int do_reassing_follower(struct spa_loop *loop, +static int set_timeout(struct impl *this, uint64_t time) +{ + struct itimerspec ts; + ts.it_value.tv_sec = time / SPA_NSEC_PER_SEC; + ts.it_value.tv_nsec = time % SPA_NSEC_PER_SEC; + ts.it_interval.tv_sec = 0; + ts.it_interval.tv_nsec = 0; + return spa_system_timerfd_settime(this->data_system, + this->timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); +} + +static int set_timers(struct impl *this) +{ + struct timespec now; + + spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); + this->next_time = SPA_TIMESPEC_TO_NSEC(&now); + + return set_timeout(this, this->following ? 0 : this->next_time); +} + +static int do_reassign_follower(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { + struct impl *this = user_data; + struct port *port = &this->port; + + spa_bt_decode_buffer_recover(&port->buffer); return 0; } @@ -309,7 +312,7 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) if (this->started && following != this->following) { spa_log_debug(this->log, "%p: reassign follower %d->%d", this, this->following, following); this->following = following; - spa_loop_invoke(this->data_loop, do_reassing_follower, 0, NULL, 0, true, this); + spa_loop_invoke(this->data_loop, do_reassign_follower, 0, NULL, 0, true, this); } return 0; } @@ -324,10 +327,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param) if (param == NULL) { reset_props(&new_props); } else { - spa_pod_parse_object(param, - SPA_TYPE_OBJECT_Props, NULL, - SPA_PROP_minLatency, SPA_POD_OPT_Int(&new_props.min_latency), - SPA_PROP_maxLatency, SPA_POD_OPT_Int(&new_props.max_latency)); + /* noop */ } changed = (memcmp(&new_props, &this->props, sizeof(struct props)) != 0); @@ -372,7 +372,6 @@ static void reset_buffers(struct port *port) spa_list_init(&port->free); spa_list_init(&port->ready); - port->current_buffer = NULL; for (i = 0; i < port->n_buffers; i++) { struct buffer *b = &port->buffers[i]; @@ -449,30 +448,15 @@ static int32_t decode_data(struct impl *this, uint8_t *src, uint32_t src_size, return dst_size - avail; } -static void skip_ready_buffers(struct impl *this) -{ - struct port *port = &this->port; - - /* Move all buffers from ready to free */ - while (!spa_list_is_empty(&port->ready)) { - struct buffer *b; - b = spa_list_first(&port->ready, struct buffer, link); - spa_list_remove(&b->link); - spa_list_append(&port->free, &b->link); - spa_assert(!b->outstanding); - this->skip_count += b->buf->datas[0].chunk->size / port->frame_size; - } -} - static void a2dp_on_ready_read(struct spa_source *source) { struct impl *this = source->data; struct port *port = &this->port; - struct spa_io_buffers *io = port->io; - int32_t size_read, decoded, avail; - struct spa_data *datas; - struct buffer *buffer; - uint32_t min_data; + struct timespec now; + void *buf; + int32_t size_read, decoded; + uint32_t avail; + uint64_t dt; /* make sure the source is an input */ if ((source->rmask & SPA_IO_IN) == 0) { @@ -486,9 +470,6 @@ static void a2dp_on_ready_read(struct spa_source *source) spa_log_trace(this->log, "socket poll"); - /* update the current pts */ - spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &this->now); - /* read */ size_read = read_data (this); if (size_read == 0) @@ -497,7 +478,9 @@ static void a2dp_on_ready_read(struct spa_source *source) spa_log_error(this->log, "failed to read data: %s", spa_strerror(size_read)); goto stop; } - spa_log_trace(this->log, "read socket data %d", size_read); + + /* update the current pts */ + spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); if (this->codec_props_changed && this->codec_props && this->codec->update_props) { @@ -505,111 +488,33 @@ static void a2dp_on_ready_read(struct spa_source *source) this->codec_props_changed = false; } - /* decode */ - decoded = decode_data(this, this->buffer_read, size_read, - this->buffer_decoded, sizeof (this->buffer_decoded)); + /* decode to buffer */ + buf = spa_bt_decode_buffer_get_write(&port->buffer, &avail); + spa_log_trace(this->log, "read socket data size:%d, avail:%d", size_read, avail); + decoded = decode_data(this, this->buffer_read, size_read, buf, avail); if (decoded < 0) { spa_log_debug(this->log, "failed to decode data: %d", decoded); return; } - if (decoded == 0) + if (decoded == 0) { + spa_log_trace(this->log, "no decoded socket data"); return; - - spa_log_trace(this->log, "decoded socket data %d", decoded); + } /* discard when not started */ if (!this->started) return; - /* get buffer */ - if (!port->current_buffer) { - if (spa_list_is_empty(&port->free)) { - /* xrun, skip ahead */ - skip_ready_buffers(this); - this->skip_count += decoded / port->frame_size; - this->sample_count += decoded / port->frame_size; - return; - } - if (this->skip_count > 0) { - spa_log_info(this->log, "%p: xrun, skipped %"PRIu64" usec", - this, (uint64_t)(this->skip_count * SPA_USEC_PER_SEC / port->current_format.info.raw.rate)); - this->skip_count = 0; - } - - buffer = spa_list_first(&port->free, struct buffer, link); - spa_list_remove(&buffer->link); - - port->current_buffer = buffer; - port->ready_offset = 0; - spa_log_trace(this->log, "dequeue %d", buffer->id); - - if (buffer->h) { - buffer->h->seq = this->sample_count; - buffer->h->pts = SPA_TIMESPEC_TO_NSEC(&this->now); - buffer->h->dts_offset = 0; - } - } else { - buffer = port->current_buffer; - } - datas = buffer->buf->datas; - - /* copy data into buffer */ - avail = SPA_MIN(decoded, (int32_t)(datas[0].maxsize - port->ready_offset)); - if (avail < decoded) - spa_log_warn(this->log, "buffer too small (%d > %d)", decoded, avail); - memcpy ((uint8_t *)datas[0].data + port->ready_offset, this->buffer_decoded, avail); - port->ready_offset += avail; - this->sample_count += decoded / port->frame_size; - - /* send buffer if full */ - min_data = SPA_MIN(this->props.min_latency * port->frame_size, datas[0].maxsize / 2); - if (port->ready_offset >= min_data) { - uint64_t sample_count; - - datas[0].chunk->offset = 0; - datas[0].chunk->size = port->ready_offset; - datas[0].chunk->stride = port->frame_size; - - sample_count = datas[0].chunk->size / port->frame_size; - - spa_log_trace(this->log, "queue %d", buffer->id); - spa_list_append(&port->ready, &buffer->link); - port->current_buffer = NULL; - - if (!this->following && this->clock) { - this->clock->nsec = SPA_TIMESPEC_TO_NSEC(&this->now); - this->clock->duration = sample_count * this->clock->rate.denom / port->current_format.info.raw.rate; - this->clock->position = this->sample_count * this->clock->rate.denom / port->current_format.info.raw.rate; - this->clock->delay = 0; - this->clock->rate_diff = 1.0f; - this->clock->next_nsec = this->clock->nsec + (uint64_t)sample_count * SPA_NSEC_PER_SEC / port->current_format.info.raw.rate; - } - } - - /* done if there are no buffers ready */ - if (spa_list_is_empty(&port->ready)) - return; - - if (this->following) - return; - - /* process the buffer if IO does not have any */ - if (io != NULL && io->status != SPA_STATUS_HAVE_DATA) { - struct buffer *b; + spa_bt_decode_buffer_write_packet(&port->buffer, decoded); - if (io->buffer_id < port->n_buffers) - recycle_buffer(this, port, io->buffer_id); + dt = SPA_TIMESPEC_TO_NSEC(&this->now); + this->now = now; + dt = SPA_TIMESPEC_TO_NSEC(&this->now) - dt; - b = spa_list_first(&port->ready, struct buffer, link); - spa_list_remove(&b->link); - b->outstanding = true; + spa_log_trace(this->log, "decoded socket data size:%d frames:%d dt:%d dms", + (int)decoded, (int)decoded/port->frame_size, + (int)(dt / 100000)); - io->buffer_id = b->id; - io->status = SPA_STATUS_HAVE_DATA; - } - - /* notify ready */ - spa_node_call_ready(&this->callbacks, SPA_STATUS_HAVE_DATA); return; stop: @@ -641,6 +546,75 @@ static void a2dp_on_duplex_timeout(struct spa_source *source) a2dp_on_ready_read(source); } +static int setup_matching(struct impl *this) +{ + struct port *port = &this->port; + + if (this->position && port->rate_match) { + port->rate_match->rate = 1 / port->buffer.corr; + + this->matching = this->following; + this->resampling = this->matching || + (port->current_format.info.raw.rate != this->position->clock.rate.denom); + } else { + this->matching = false; + this->resampling = false; + } + + if (port->rate_match) + SPA_FLAG_UPDATE(port->rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE, this->matching); + + return 0; +} + +static void a2dp_on_timeout(struct spa_source *source) +{ + struct impl *this = source->data; + struct port *port = &this->port; + uint64_t exp, duration; + uint32_t rate; + struct spa_io_buffers *io = port->io; + uint64_t prev_time, now_time; + + if (this->transport == NULL) + return; + + if (this->started && spa_system_timerfd_read(this->data_system, this->timerfd, &exp) < 0) + spa_log_warn(this->log, "error reading timerfd: %s", strerror(errno)); + + prev_time = this->current_time; + now_time = this->current_time = this->next_time; + + spa_log_trace(this->log, "%p: timer %"PRIu64" %"PRIu64"", this, + now_time, now_time - prev_time); + + if (SPA_LIKELY(this->position)) { + duration = this->position->clock.duration; + rate = this->position->clock.rate.denom; + } else { + duration = 1024; + rate = 48000; + } + + setup_matching(this); + + this->next_time = now_time + duration * SPA_NSEC_PER_SEC / port->buffer.corr / rate; + + if (SPA_LIKELY(this->clock)) { + this->clock->nsec = now_time; + this->clock->position += duration; + this->clock->duration = duration; + this->clock->rate_diff = port->buffer.corr; + this->clock->next_nsec = this->next_time; + } + + spa_log_trace(this->log, "%p: %d", this, io->status); + io->status = SPA_STATUS_HAVE_DATA; + spa_node_call_ready(&this->callbacks, SPA_STATUS_HAVE_DATA); + + set_timeout(this, this->next_time); +} + static int transport_start(struct impl *this) { int res, val; @@ -656,7 +630,8 @@ static int transport_start(struct impl *this) this->transport_acquired = true; - this->codec_data = this->codec->init(this->codec, 0, + this->codec_data = this->codec->init(this->codec, + this->is_duplex ? 0 : A2DP_CODEC_FLAG_SINK, this->transport->configuration, this->transport->configuration_len, &port->current_format, @@ -683,13 +658,19 @@ static int transport_start(struct impl *this) if (setsockopt(this->transport->fd, SOL_SOCKET, SO_PRIORITY, &val, sizeof(val)) < 0) spa_log_warn(this->log, "SO_PRIORITY failed: %m"); - reset_buffers(&this->port); + reset_buffers(port); + + spa_bt_decode_buffer_clear(&port->buffer); + if ((res = spa_bt_decode_buffer_init(&port->buffer, this->log, + port->frame_size, port->current_format.info.raw.rate, + this->quantum_limit, this->quantum_limit)) < 0) + return res; this->fd = this->transport->fd; this->source.data = this; - if (!this->is_duplex) { + if (!this->use_duplex_source) { this->source.fd = this->transport->fd; this->source.func = a2dp_on_ready_read; this->source.mask = SPA_IO_IN; @@ -704,7 +685,8 @@ static int transport_start(struct impl *this) * XXX: The reason for this should be found and fixed. * XXX: To work around this, for now we just do the stupid thing and poll * XXX: on a timer, chosen so that it's fast enough for the aptX-LL codec - * XXX: we currently support (which sends mSBC data). + * XXX: we currently support (which sends mSBC data), and also for Opus + * XXX: forward stream. */ this->source.fd = this->duplex_timerfd; this->source.func = a2dp_on_duplex_timeout; @@ -712,12 +694,22 @@ static int transport_start(struct impl *this) this->source.rmask = 0; spa_loop_add_source(this->data_loop, &this->source); - this->duplex_timeout = SPA_NSEC_PER_MSEC * 75/10; + this->duplex_timeout = SPA_NSEC_PER_MSEC * 25/10; set_duplex_timeout(this, this->duplex_timeout); } + this->timer_source.data = this; + this->timer_source.fd = this->timerfd; + this->timer_source.func = a2dp_on_timeout; + this->timer_source.mask = SPA_IO_IN; + this->timer_source.rmask = 0; + spa_loop_add_source(this->data_loop, &this->timer_source); + this->sample_count = 0; - this->skip_count = 0; + + setup_matching(this); + + set_timers(this); return 0; } @@ -729,13 +721,13 @@ static int do_start(struct impl *this) if (this->started) return 0; + spa_return_val_if_fail(this->transport != NULL, -EIO); + this->following = is_following(this); spa_log_debug(this->log, "%p: start state:%d following:%d", this, this->transport->state, this->following); - spa_return_val_if_fail(this->transport != NULL, -EIO); - if (this->transport->state >= SPA_BT_TRANSPORT_STATE_PENDING || this->is_duplex) res = transport_start(this); @@ -753,6 +745,7 @@ static int do_remove_source(struct spa_loop *loop, void *user_data) { struct impl *this = user_data; + struct itimerspec ts; spa_log_debug(this->log, "%p: remove source", this); @@ -761,11 +754,20 @@ static int do_remove_source(struct spa_loop *loop, if (this->source.loop) spa_loop_remove_source(this->data_loop, &this->source); + if (this->timer_source.loop) + spa_loop_remove_source(this->data_loop, &this->timer_source); + ts.it_value.tv_sec = 0; + ts.it_value.tv_nsec = 0; + ts.it_interval.tv_sec = 0; + ts.it_interval.tv_nsec = 0; + spa_system_timerfd_settime(this->data_system, this->timerfd, 0, &ts, NULL); + return 0; } static int transport_stop(struct impl *this) { + struct port *port = &this->port; int res; spa_log_debug(this->log, "%p: transport stop", this); @@ -783,6 +785,8 @@ static int transport_stop(struct impl *this) this->codec->deinit(this->codec_data); this->codec_data = NULL; + spa_bt_decode_buffer_clear(&port->buffer); + return res; } @@ -836,24 +840,20 @@ static int impl_node_send_command(void *object, const struct spa_command *comman static void emit_node_info(struct impl *this, bool full) { - char latency[64] = SPA_STRINGIFY(MIN_LATENCY)"/48000"; uint64_t old = full ? this->info.change_mask : 0; struct spa_dict_item node_info_items[] = { { SPA_KEY_DEVICE_API, "bluez5" }, { SPA_KEY_MEDIA_CLASS, this->is_input ? "Audio/Source" : "Stream/Output/Audio" }, - { SPA_KEY_NODE_LATENCY, latency }, + { SPA_KEY_NODE_LATENCY, this->is_input ? "" : "512/48000" }, { "media.name", ((this->transport && this->transport->device->name) ? - this->transport->device->name : "A2DP") }, - { SPA_KEY_NODE_DRIVER, this->is_input ? "true" : "false" }, + this->transport->device->name : "A2DP") }, + { SPA_KEY_NODE_DRIVER, this->is_input ? "true" : "false" }, }; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { - if (this->transport && this->port.have_format) - snprintf(latency, sizeof(latency), "%d/%d", (int)this->props.min_latency, - (int)this->port.current_format.info.raw.rate); this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items); spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; @@ -968,6 +968,7 @@ impl_node_port_enum_params(void *object, int seq, return -EIO; if ((res = this->codec->enum_config(this->codec, + this->is_duplex ? 0 : A2DP_CODEC_FLAG_SINK, this->transport->configuration, this->transport->configuration_len, id, result.index, &b, ¶m)) != 1) @@ -991,11 +992,11 @@ impl_node_port_enum_params(void *object, int seq, param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 8, MAX_BUFFERS), + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( - this->props.max_latency * port->frame_size, - this->props.min_latency * port->frame_size, + this->quantum_limit * port->frame_size, + 16 * port->frame_size, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->frame_size)); break; @@ -1021,6 +1022,12 @@ impl_node_port_enum_params(void *object, int seq, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_RateMatch), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_rate_match))); + break; default: return 0; } @@ -1059,7 +1066,6 @@ static int clear_buffers(struct impl *this, struct port *port) spa_list_init(&port->ready); port->n_buffers = 0; } - port->current_buffer = NULL; return 0; } @@ -1217,6 +1223,9 @@ impl_node_port_set_io(void *object, case SPA_IO_Buffers: port->io = data; break; + case SPA_IO_RateMatch: + port->rate_match = data; + break; default: return -ENOENT; } @@ -1244,6 +1253,88 @@ static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t return 0; } +static uint32_t get_samples(struct impl *this, uint32_t *duration) +{ + struct port *port = &this->port; + uint32_t samples; + + if (SPA_LIKELY(port->rate_match) && this->resampling) { + samples = port->rate_match->size; + } else { + if (SPA_LIKELY(this->position)) + samples = this->position->clock.duration * port->current_format.info.raw.rate + / this->position->clock.rate.denom; + else + samples = 1024; + } + + if (SPA_LIKELY(this->position)) + *duration = this->position->clock.duration * port->current_format.info.raw.rate + / this->position->clock.rate.denom; + else if (SPA_LIKELY(this->clock)) + *duration = this->clock->duration * port->current_format.info.raw.rate + / this->clock->rate.denom; + else + *duration = 1024 * port->current_format.info.raw.rate / 48000; + + return samples; +} + +static void process_buffering(struct impl *this) +{ + struct port *port = &this->port; + uint32_t duration; + const uint32_t samples = get_samples(this, &duration); + uint32_t avail; + void *buf; + + spa_bt_decode_buffer_process(&port->buffer, samples, duration); + + setup_matching(this); + + buf = spa_bt_decode_buffer_get_read(&port->buffer, &avail); + + /* copy data to buffers */ + if (!spa_list_is_empty(&port->free) && avail > 0) { + struct buffer *buffer; + struct spa_data *datas; + uint32_t data_size; + + data_size = samples * port->frame_size; + + avail = SPA_MIN(avail, data_size); + + spa_bt_decode_buffer_read(&port->buffer, avail); + + buffer = spa_list_first(&port->free, struct buffer, link); + spa_list_remove(&buffer->link); + + spa_log_trace(this->log, "dequeue %d", buffer->id); + + if (buffer->h) { + buffer->h->seq = this->sample_count; + buffer->h->pts = SPA_TIMESPEC_TO_NSEC(&this->now); + buffer->h->dts_offset = 0; + } + + datas = buffer->buf->datas; + + spa_assert(datas[0].maxsize >= data_size); + + datas[0].chunk->offset = 0; + datas[0].chunk->size = avail; + datas[0].chunk->stride = port->frame_size; + + memcpy(datas[0].data, buf, avail); + + this->sample_count += avail / port->frame_size; + + /* ready buffer if full */ + spa_log_trace(this->log, "queue %d frames:%d", buffer->id, (int)avail / port->frame_size); + spa_list_append(&port->ready, &buffer->link); + } +} + static int impl_node_process(void *object) { struct impl *this = object; @@ -1254,8 +1345,8 @@ static int impl_node_process(void *object) spa_return_val_if_fail(this != NULL, -EINVAL); port = &this->port; - io = port->io; - spa_return_val_if_fail(io != NULL, -EIO); + if ((io = port->io) == NULL) + return -EIO; spa_log_trace(this->log, "%p status:%d", this, io->status); @@ -1269,6 +1360,9 @@ static int impl_node_process(void *object) io->buffer_id = SPA_ID_INVALID; } + /* Handle buffering delay */ + process_buffering(this); + /* Return if there are no buffers ready to be processed */ if (spa_list_is_empty(&port->ready)) return SPA_STATUS_OK; @@ -1350,16 +1444,19 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void static int impl_clear(struct spa_handle *handle) { struct impl *this = (struct impl *) handle; + struct port *port = &this->port; if (this->codec_data) this->codec->deinit(this->codec_data); if (this->codec_props && this->codec->clear_props) this->codec->clear_props(this->codec_props); if (this->transport) spa_hook_remove(&this->transport_listener); + spa_system_close(this->data_system, this->timerfd); if (this->duplex_timerfd >= 0) { spa_system_close(this->data_system, this->duplex_timerfd); this->duplex_timerfd = -1; } + spa_bt_decode_buffer_clear(&port->buffer); return 0; } @@ -1451,7 +1548,11 @@ impl_init(const struct spa_handle_factory *factory, spa_list_init(&port->ready); spa_list_init(&port->free); + this->quantum_limit = 8192; + if (info != NULL) { + if (info && (str = spa_dict_lookup(info, "clock.quantum-limit"))) + spa_atou32(str, &this->quantum_limit, 0); if ((str = spa_dict_lookup(info, SPA_KEY_API_BLUEZ5_TRANSPORT)) != NULL) sscanf(str, "pointer:%p", &this->transport); if ((str = spa_dict_lookup(info, "bluez5.a2dp-source-role")) != NULL) @@ -1478,15 +1579,20 @@ impl_init(const struct spa_handle_factory *factory, this->codec = this->codec->duplex_codec; this->is_input = true; } + this->use_duplex_source = this->is_duplex || (this->codec->duplex_codec != NULL); if (this->codec->init_props != NULL) this->codec_props = this->codec->init_props(this->codec, + this->is_duplex ? 0 : A2DP_CODEC_FLAG_SINK, this->transport->device->settings); spa_bt_transport_add_listener(this->transport, &this->transport_listener, &transport_events, this); - if (this->is_duplex) { + this->timerfd = spa_system_timerfd_create(this->data_system, + CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); + + if (this->use_duplex_source) { this->duplex_timerfd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); } else { diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index f5c781dfb0ddbcba8d0e88fcccab15e2343cb76a..c38656bbbd5b4e9d188e55c5910b593204c2dcb7 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -29,6 +29,7 @@ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> +#include <poll.h> #include <bluetooth/bluetooth.h> #include <bluetooth/sco.h> @@ -73,6 +74,7 @@ struct impl { struct spa_log *log; struct spa_loop *main_loop; struct spa_system *main_system; + struct spa_loop_utils *loop_utils; struct spa_dbus *dbus; DBusConnection *conn; @@ -127,6 +129,7 @@ struct rfcomm { struct spa_hook transport_listener; enum spa_bt_profile profile; struct spa_source timer; + struct spa_source *volume_sync_timer; char* path; bool has_volume; struct rfcomm_volume volumes[SPA_BT_VOLUME_ID_TERM]; @@ -225,6 +228,8 @@ finish: static int codec_switch_stop_timer(struct rfcomm *rfcomm); +static void volume_sync_stop_timer(struct rfcomm *rfcomm); + static void rfcomm_free(struct rfcomm *rfcomm) { codec_switch_stop_timer(rfcomm); @@ -247,6 +252,8 @@ static void rfcomm_free(struct rfcomm *rfcomm) close (rfcomm->source.fd); rfcomm->source.fd = -1; } + if (rfcomm->volume_sync_timer) + spa_loop_utils_destroy_source(rfcomm->backend->loop_utils, rfcomm->volume_sync_timer); free(rfcomm); } @@ -809,6 +816,7 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) rfcomm->hfp_ag_switching_codec = false; rfcomm->hfp_ag_initial_codec_setup = HFP_AG_INITIAL_CODEC_SETUP_NONE; codec_switch_stop_timer(rfcomm); + volume_sync_stop_timer(rfcomm); if (selected_codec != HFP_AUDIO_CODEC_CVSD && selected_codec != HFP_AUDIO_CODEC_MSBC) { spa_log_warn(backend->log, "unsupported codec negotiation: %d", selected_codec); @@ -1203,6 +1211,18 @@ fail_close: return -1; } +static int rfcomm_ag_sync_volume(struct rfcomm *rfcomm, bool later); + +static void wait_for_socket(int fd) +{ + struct pollfd fds[1]; + const int timeout_ms = 500; + + fds[0].fd = fd; + fds[0].events = POLLIN | POLLERR | POLLHUP; + poll(fds, 1, timeout_ms); +} + static int sco_acquire_cb(void *data, bool optional) { struct spa_bt_transport *t = data; @@ -1225,6 +1245,25 @@ static int sco_acquire_cb(void *data, bool optional) rfcomm_hfp_ag_set_cind(td->rfcomm, true); #endif + /* + * Send RFCOMM volume after connection is ready, and also after + * a timeout. + * + * Some headsets adjust their HFP volume when in A2DP mode + * without reporting via RFCOMM to us, so the volume level can + * be out of sync, and we can't know what it is. Moreover, they may + * take the first +VGS command after connection only partially + * into account, and need a long enough timeout. + * + * E.g. with Sennheiser HD-250BT, the first +VGS changes the + * actual volume, but does not update the level in the hardware + * volume buttons, which is updated by an +VGS event only after + * sufficient time is elapsed from the connection. + */ + wait_for_socket(sock); + rfcomm_ag_sync_volume(td->rfcomm, false); + rfcomm_ag_sync_volume(td->rfcomm, true); + t->fd = sock; /* Fallback value */ @@ -1471,10 +1510,8 @@ fail_close: return -1; } -static int sco_set_volume_cb(void *data, int id, float volume) +static int rfcomm_ag_set_volume(struct spa_bt_transport *t, int id) { - struct spa_bt_transport *t = data; - struct spa_bt_transport_volume *t_volume = &t->volumes[id]; struct transport_data *td = t->user_data; struct rfcomm *rfcomm = td->rfcomm; const char *format; @@ -1485,12 +1522,7 @@ static int sco_set_volume_cb(void *data, int id, float volume) || !(rfcomm->has_volume && rfcomm->volumes[id].active)) return -ENOTSUP; - value = spa_bt_volume_linear_to_hw(volume, t_volume->hw_volume_max); - t_volume->volume = volume; - - if (rfcomm->volumes[id].hw_volume == value) - return 0; - rfcomm->volumes[id].hw_volume = value; + value = rfcomm->volumes[id].hw_volume; if (id == SPA_BT_VOLUME_ID_RX) if (rfcomm->profile & SPA_BT_PROFILE_HFP_HF) @@ -1511,6 +1543,29 @@ static int sco_set_volume_cb(void *data, int id, float volume) return 0; } +static int sco_set_volume_cb(void *data, int id, float volume) +{ + struct spa_bt_transport *t = data; + struct spa_bt_transport_volume *t_volume = &t->volumes[id]; + struct transport_data *td = t->user_data; + struct rfcomm *rfcomm = td->rfcomm; + int value; + + if (!rfcomm_volume_enabled(rfcomm) + || !(rfcomm->profile & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) + || !(rfcomm->has_volume && rfcomm->volumes[id].active)) + return -ENOTSUP; + + value = spa_bt_volume_linear_to_hw(volume, t_volume->hw_volume_max); + t_volume->volume = volume; + + if (rfcomm->volumes[id].hw_volume == value) + return 0; + rfcomm->volumes[id].hw_volume = value; + + return rfcomm_ag_set_volume(t, id); +} + static const struct spa_bt_transport_implementation sco_transport_impl = { SPA_VERSION_BT_TRANSPORT_IMPLEMENTATION, .acquire = sco_acquire_cb, @@ -1570,6 +1625,60 @@ static int codec_switch_stop_timer(struct rfcomm *rfcomm) return 0; } +static void volume_sync_stop_timer(struct rfcomm *rfcomm) +{ + if (rfcomm->volume_sync_timer) + spa_loop_utils_update_timer(rfcomm->backend->loop_utils, rfcomm->volume_sync_timer, + NULL, NULL, false); +} + +static void volume_sync_timer_event(void *data, uint64_t expirations) +{ + struct rfcomm *rfcomm = data; + + volume_sync_stop_timer(rfcomm); + + if (rfcomm->transport) { + rfcomm_ag_set_volume(rfcomm->transport, SPA_BT_VOLUME_ID_TX); + rfcomm_ag_set_volume(rfcomm->transport, SPA_BT_VOLUME_ID_RX); + } +} + +static int volume_sync_start_timer(struct rfcomm *rfcomm) +{ + struct timespec ts; + const uint64_t timeout = 1500 * SPA_NSEC_PER_MSEC; + + if (rfcomm->volume_sync_timer == NULL) + rfcomm->volume_sync_timer = spa_loop_utils_add_timer(rfcomm->backend->loop_utils, + volume_sync_timer_event, rfcomm); + + if (rfcomm->volume_sync_timer == NULL) + return -EIO; + + ts.tv_sec = timeout / SPA_NSEC_PER_SEC; + ts.tv_nsec = timeout % SPA_NSEC_PER_SEC; + spa_loop_utils_update_timer(rfcomm->backend->loop_utils, rfcomm->volume_sync_timer, + &ts, NULL, false); + + return 0; +} + +static int rfcomm_ag_sync_volume(struct rfcomm *rfcomm, bool later) +{ + if (rfcomm->transport == NULL) + return -ENOENT; + + if (!later) { + rfcomm_ag_set_volume(rfcomm->transport, SPA_BT_VOLUME_ID_TX); + rfcomm_ag_set_volume(rfcomm->transport, SPA_BT_VOLUME_ID_RX); + } else { + volume_sync_start_timer(rfcomm); + } + + return 0; +} + static void codec_switch_timer_event(struct spa_source *source) { struct rfcomm *rfcomm = source->data; @@ -2222,6 +2331,7 @@ struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor, backend->dbus = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DBus); backend->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); backend->main_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System); + backend->loop_utils = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_LoopUtils); backend->conn = dbus_connection; backend->sco.fd = -1; diff --git a/spa/plugins/bluez5/bluez-hardware.conf b/spa/plugins/bluez5/bluez-hardware.conf index 8f8cd0290b08cf3537ac70f9457469909d64ad09..0247f75d5630460a4ccd50e25b3ac461536eb22d 100644 --- a/spa/plugins/bluez5/bluez-hardware.conf +++ b/spa/plugins/bluez5/bluez-hardware.conf @@ -39,6 +39,7 @@ bluez5.features.device = [ { name = "Motorola DC800", no-features = [ sbc-xq ] }, # #pipewire-1590 { name = "Motorola S305", no-features = [ sbc-xq ] }, # #pipewire-1590 { name = "Soundcore Life P2-L", no-features = [ msbc-alt1, msbc-alt1-rtl ] }, + { name = "Soundcore Motion B", no-features = [ hw-volume ] }, { name = "SoundCore mini", no-features = [ hw-volume ] }, # #pipewire-1686 { name = "SoundCore 2", no-features = [ sbc-xq ] }, # #pipewire-2291 { name = "Tribit MAXSound Plus", no-features = [ hw-volume ] }, # #pipewire-1592 diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 8b57911ab5c3bcc74b5424437113db91e69c796c..f43d4b57d2ff59049b98b8256ed357fafb29223a 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -125,6 +125,10 @@ struct spa_bt_monitor { struct spa_bt_quirks *quirks; +#define MAX_SETTINGS 128 + struct spa_dict_item global_setting_items[MAX_SETTINGS]; + struct spa_dict global_settings; + /* A reference audio info for A2DP codec configuration. */ struct a2dp_codec_audio_info default_audio_info; }; @@ -437,18 +441,21 @@ static int a2dp_codec_to_endpoint(const struct a2dp_codec *codec, return 0; } -static const struct a2dp_codec *a2dp_endpoint_to_codec(struct spa_bt_monitor *monitor, const char *endpoint) +static const struct a2dp_codec *a2dp_endpoint_to_codec(struct spa_bt_monitor *monitor, const char *endpoint, bool *sink) { const char *ep_name; const struct a2dp_codec * const * const a2dp_codecs = monitor->a2dp_codecs; int i; - if (spa_strstartswith(endpoint, A2DP_SINK_ENDPOINT "/")) + if (spa_strstartswith(endpoint, A2DP_SINK_ENDPOINT "/")) { ep_name = endpoint + strlen(A2DP_SINK_ENDPOINT "/"); - else if (spa_strstartswith(endpoint, A2DP_SOURCE_ENDPOINT "/")) + *sink = true; + } else if (spa_strstartswith(endpoint, A2DP_SOURCE_ENDPOINT "/")) { ep_name = endpoint + strlen(A2DP_SOURCE_ENDPOINT "/"); - else + *sink = false; + } else { return NULL; + } for (i = 0; a2dp_codecs[i]; i++) { const struct a2dp_codec *codec = a2dp_codecs[i]; @@ -486,6 +493,7 @@ static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBu DBusError err; int i, size, res; const struct a2dp_codec *codec; + bool sink; dbus_error_init(&err); @@ -501,14 +509,15 @@ static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBu for (i = 0; i < size; i++) spa_log_debug(monitor->log, " %d: %02x", i, cap[i]); - codec = a2dp_endpoint_to_codec(monitor, path); + codec = a2dp_endpoint_to_codec(monitor, path, &sink); if (codec != NULL) /* FIXME: We can't determine which device the SelectConfiguration() * call is associated with, therefore device settings are not passed. * This causes inconsistency with SelectConfiguration() triggered * by codec switching. */ - res = codec->select_config(codec, 0, cap, size, &monitor->default_audio_info, NULL, config); + res = codec->select_config(codec, sink ? A2DP_CODEC_FLAG_SINK : 0, cap, size, &monitor->default_audio_info, + &monitor->global_settings, config); else res = -ENOTSUP; @@ -1543,7 +1552,7 @@ static bool device_props_ready(struct spa_bt_device *device) return device->adapter && device->address; } -bool spa_bt_device_supports_a2dp_codec(struct spa_bt_device *device, const struct a2dp_codec *codec) +bool spa_bt_device_supports_a2dp_codec(struct spa_bt_device *device, const struct a2dp_codec *codec, bool sink) { struct spa_bt_monitor *monitor = device->monitor; struct spa_bt_remote_endpoint *ep; @@ -1579,15 +1588,22 @@ bool spa_bt_device_supports_a2dp_codec(struct spa_bt_device *device, const struc } spa_list_for_each(ep, &device->remote_endpoint_list, device_link) { + const enum spa_bt_profile profile = spa_bt_profile_from_uuid(ep->uuid); + const enum spa_bt_profile expected = sink ? + SPA_BT_PROFILE_A2DP_SINK : SPA_BT_PROFILE_A2DP_SOURCE; + + if (profile != expected) + continue; + if (a2dp_codec_check_caps(codec, ep->codec, ep->capabilities, ep->capabilities_len, - &ep->monitor->default_audio_info)) + &ep->monitor->default_audio_info, &monitor->global_settings)) return true; } return false; } -const struct a2dp_codec **spa_bt_device_get_supported_a2dp_codecs(struct spa_bt_device *device, size_t *count) +const struct a2dp_codec **spa_bt_device_get_supported_a2dp_codecs(struct spa_bt_device *device, size_t *count, bool sink) { struct spa_bt_monitor *monitor = device->monitor; const struct a2dp_codec * const * const a2dp_codecs = monitor->a2dp_codecs; @@ -1603,7 +1619,7 @@ const struct a2dp_codec **spa_bt_device_get_supported_a2dp_codecs(struct spa_bt_ j = 0; for (i = 0; a2dp_codecs[i] != NULL; ++i) { - if (spa_bt_device_supports_a2dp_codec(device, a2dp_codecs[i])) { + if (spa_bt_device_supports_a2dp_codec(device, a2dp_codecs[i], sink)) { supported_codecs[j] = a2dp_codecs[i]; ++j; } @@ -2511,7 +2527,7 @@ static const struct spa_bt_transport_implementation transport_impl = { .set_volume = transport_set_volume, }; -static void append_basic_array_variant_dict_entry(DBusMessageIter *dict, int key_type_int, void* key, const char* variant_type_str, const char* array_type_str, int array_type_int, void* data, int data_size); +static void append_basic_array_variant_dict_entry(DBusMessageIter *dict, const char* key, const char* variant_type_str, const char* array_type_str, int array_type_int, void* data, int data_size); static void a2dp_codec_switch_reply(DBusPendingCall *pending, void *userdata); @@ -2569,10 +2585,10 @@ static bool a2dp_codec_switch_process_current(struct spa_bt_a2dp_codec_switch *s char *local_endpoint = NULL; int res, config_size; dbus_bool_t dbus_ret; - const char *str; DBusMessage *m; DBusMessageIter iter, d; int i; + bool sink; /* Try setting configuration for current codec on current endpoint in list */ @@ -2603,8 +2619,10 @@ static bool a2dp_codec_switch_process_current(struct spa_bt_a2dp_codec_switch *s if (sw->profile & SPA_BT_PROFILE_A2DP_SINK) { local_endpoint_base = A2DP_SOURCE_ENDPOINT; + sink = false; } else if (sw->profile & SPA_BT_PROFILE_A2DP_SOURCE) { local_endpoint_base = A2DP_SINK_ENDPOINT; + sink = true; } else { spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: bad profile (%d), try next", sw, sw->profile); @@ -2630,9 +2648,9 @@ static bool a2dp_codec_switch_process_current(struct spa_bt_a2dp_codec_switch *s } } - res = codec->select_config(codec, 0, ep->capabilities, ep->capabilities_len, + res = codec->select_config(codec, sink ? A2DP_CODEC_FLAG_SINK : 0, ep->capabilities, ep->capabilities_len, &sw->device->monitor->default_audio_info, - sw->device->settings, config); + &sw->device->monitor->global_settings, config); if (res < 0) { spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: incompatible capabilities (%d), try next", sw, res); @@ -2659,8 +2677,7 @@ static bool a2dp_codec_switch_process_current(struct spa_bt_a2dp_codec_switch *s dbus_message_iter_init_append(m, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &local_endpoint); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &d); - str = "Capabilities"; - append_basic_array_variant_dict_entry(&d, DBUS_TYPE_STRING, &str, "ay", "y", DBUS_TYPE_BYTE, config, config_size); + append_basic_array_variant_dict_entry(&d, "Capabilities", "ay", "y", DBUS_TYPE_BYTE, config, config_size); dbus_message_iter_close_container(&iter, &d); spa_assert(sw->pending == NULL); @@ -2878,6 +2895,7 @@ static int a2dp_codec_switch_cmp(const void *a, const void *b) const struct a2dp_codec *codec = *sw->codec_iter; const char *path1 = *(char **)a, *path2 = *(char **)b; struct spa_bt_remote_endpoint *ep1, *ep2; + uint32_t flags; ep1 = device_remote_endpoint_find(sw->device, path1); ep2 = device_remote_endpoint_find(sw->device, path2); @@ -2886,6 +2904,10 @@ static int a2dp_codec_switch_cmp(const void *a, const void *b) ep1 = NULL; if (ep2 != NULL && (ep2->uuid == NULL || ep2->codec != codec->codec_id || ep2->capabilities == NULL)) ep2 = NULL; + if (ep1 && ep2 && !spa_streq(ep1->uuid, ep2->uuid)) { + ep1 = NULL; + ep2 = NULL; + } if (ep1 == NULL && ep2 == NULL) return 0; @@ -2894,8 +2916,11 @@ static int a2dp_codec_switch_cmp(const void *a, const void *b) else if (ep2 == NULL) return -1; - return codec->caps_preference_cmp(codec, ep1->capabilities, ep1->capabilities_len, - ep2->capabilities, ep2->capabilities_len, &sw->device->monitor->default_audio_info); + flags = spa_streq(ep1->uuid, SPA_BT_UUID_A2DP_SOURCE) ? A2DP_CODEC_FLAG_SINK : 0; + + return codec->caps_preference_cmp(codec, flags, ep1->capabilities, ep1->capabilities_len, + ep2->capabilities, ep2->capabilities_len, &sw->device->monitor->default_audio_info, + &sw->device->monitor->global_settings); } /* Ensure there's a transport for at least one of the listed codecs */ @@ -2913,7 +2938,7 @@ int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a } for (i = 0; codecs[i] != NULL; ++i) { - if (spa_bt_device_supports_a2dp_codec(device, codecs[i])) { + if (spa_bt_device_supports_a2dp_codec(device, codecs[i], true)) { preferred_codec = codecs[i]; break; } @@ -3031,6 +3056,7 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, struct spa_bt_transport *transport; const struct a2dp_codec *codec; int profile; + bool sink; if (!dbus_message_has_signature(m, "oa{sv}")) { spa_log_warn(monitor->log, "invalid SetConfiguration() signature"); @@ -3039,7 +3065,7 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, endpoint = dbus_message_get_path(m); profile = a2dp_endpoint_to_profile(endpoint); - codec = a2dp_endpoint_to_codec(monitor, endpoint); + codec = a2dp_endpoint_to_codec(monitor, endpoint, &sink); if (codec == NULL) { spa_log_warn(monitor->log, "unknown SetConfiguration() codec"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; @@ -3100,7 +3126,7 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, if (codec->validate_config) { struct spa_audio_info info; - if (codec->validate_config(codec, 0, + if (codec->validate_config(codec, sink ? A2DP_CODEC_FLAG_SINK : 0, transport->configuration, transport->configuration_len, &info) < 0) { spa_log_error(monitor->log, "invalid transport configuration"); @@ -3256,10 +3282,10 @@ static void bluez_register_endpoint_reply(DBusPendingCall *pending, void *user_d dbus_pending_call_unref(pending); } -static void append_basic_variant_dict_entry(DBusMessageIter *dict, int key_type_int, void* key, int variant_type_int, const char* variant_type_str, void* variant) { +static void append_basic_variant_dict_entry(DBusMessageIter *dict, const char* key, int variant_type_int, const char* variant_type_str, void* variant) { DBusMessageIter dict_entry_it, variant_it; dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_it); - dbus_message_iter_append_basic(&dict_entry_it, key_type_int, key); + dbus_message_iter_append_basic(&dict_entry_it, DBUS_TYPE_STRING, &key); dbus_message_iter_open_container(&dict_entry_it, DBUS_TYPE_VARIANT, variant_type_str, &variant_it); dbus_message_iter_append_basic(&variant_it, variant_type_int, variant); @@ -3267,10 +3293,10 @@ static void append_basic_variant_dict_entry(DBusMessageIter *dict, int key_type_ dbus_message_iter_close_container(dict, &dict_entry_it); } -static void append_basic_array_variant_dict_entry(DBusMessageIter *dict, int key_type_int, void* key, const char* variant_type_str, const char* array_type_str, int array_type_int, void* data, int data_size) { +static void append_basic_array_variant_dict_entry(DBusMessageIter *dict, const char* key, const char* variant_type_str, const char* array_type_str, int array_type_int, void* data, int data_size) { DBusMessageIter dict_entry_it, variant_it, array_it; dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_it); - dbus_message_iter_append_basic(&dict_entry_it, key_type_int, key); + dbus_message_iter_append_basic(&dict_entry_it, DBUS_TYPE_STRING, &key); dbus_message_iter_open_container(&dict_entry_it, DBUS_TYPE_VARIANT, variant_type_str, &variant_it); dbus_message_iter_open_container(&variant_it, DBUS_TYPE_ARRAY, array_type_str, &array_it); @@ -3283,40 +3309,42 @@ static void append_basic_array_variant_dict_entry(DBusMessageIter *dict, int key static int bluez_register_endpoint(struct spa_bt_monitor *monitor, const char *path, const char *endpoint, const char *uuid, const struct a2dp_codec *codec) { - char *str, *object_path = NULL; + char *object_path = NULL; DBusMessage *m; DBusMessageIter object_it, dict_it; DBusPendingCall *call; uint8_t caps[A2DP_MAX_CAPS_SIZE]; int ret, caps_size; uint16_t codec_id = codec->codec_id; + bool sink; ret = a2dp_codec_to_endpoint(codec, endpoint, &object_path); if (ret < 0) - return ret; + goto error; - caps_size = codec->fill_caps(codec, 0, caps); - if (caps_size < 0) - return caps_size; + sink = spa_streq(endpoint, A2DP_SINK_ENDPOINT); + + ret = caps_size = codec->fill_caps(codec, sink ? A2DP_CODEC_FLAG_SINK : 0, caps); + if (ret < 0) + goto error; m = dbus_message_new_method_call(BLUEZ_SERVICE, path, BLUEZ_MEDIA_INTERFACE, "RegisterEndpoint"); - if (m == NULL) - return -EIO; + if (m == NULL) { + ret = -EIO; + goto error; + } dbus_message_iter_init_append(m, &object_it); dbus_message_iter_append_basic(&object_it, DBUS_TYPE_OBJECT_PATH, &object_path); dbus_message_iter_open_container(&object_it, DBUS_TYPE_ARRAY, "{sv}", &dict_it); - str = "UUID"; - append_basic_variant_dict_entry(&dict_it, DBUS_TYPE_STRING, &str, DBUS_TYPE_STRING, "s", &uuid); - str = "Codec"; - append_basic_variant_dict_entry(&dict_it, DBUS_TYPE_STRING, &str, DBUS_TYPE_BYTE, "y", &codec_id); - str = "Capabilities"; - append_basic_array_variant_dict_entry(&dict_it, DBUS_TYPE_STRING, &str, "ay", "y", DBUS_TYPE_BYTE, caps, caps_size); + append_basic_variant_dict_entry(&dict_it,"UUID", DBUS_TYPE_STRING, "s", &uuid); + append_basic_variant_dict_entry(&dict_it, "Codec", DBUS_TYPE_BYTE, "y", &codec_id); + append_basic_array_variant_dict_entry(&dict_it, "Capabilities", "ay", "y", DBUS_TYPE_BYTE, caps, caps_size); dbus_message_iter_close_container(&object_it, &dict_it); @@ -3327,6 +3355,10 @@ static int bluez_register_endpoint(struct spa_bt_monitor *monitor, free(object_path); return 0; + +error: + free(object_path); + return ret; } static int register_a2dp_endpoint(struct spa_bt_monitor *monitor, @@ -3416,7 +3448,6 @@ static int adapter_register_endpoints(struct spa_bt_adapter *a) static void append_a2dp_object(DBusMessageIter *iter, const char *endpoint, const char *uuid, uint8_t codec_id, uint8_t *caps, size_t caps_size) { - char* str; const char *interface_name = BLUEZ_MEDIA_ENDPOINT_INTERFACE; DBusMessageIter object, array, entry, dict; dbus_bool_t delay_reporting; @@ -3431,16 +3462,12 @@ static void append_a2dp_object(DBusMessageIter *iter, const char *endpoint, dbus_message_iter_open_container(&entry, DBUS_TYPE_ARRAY, "{sv}", &dict); - str = "UUID"; - append_basic_variant_dict_entry(&dict, DBUS_TYPE_STRING, &str, DBUS_TYPE_STRING, "s", &uuid); - str = "Codec"; - append_basic_variant_dict_entry(&dict, DBUS_TYPE_STRING, &str, DBUS_TYPE_BYTE, "y", &codec_id); - str = "Capabilities"; - append_basic_array_variant_dict_entry(&dict, DBUS_TYPE_STRING, &str, "ay", "y", DBUS_TYPE_BYTE, caps, caps_size); + append_basic_variant_dict_entry(&dict, "UUID", DBUS_TYPE_STRING, "s", &uuid); + append_basic_variant_dict_entry(&dict, "Codec", DBUS_TYPE_BYTE, "y", &codec_id); + append_basic_array_variant_dict_entry(&dict, "Capabilities", "ay", "y", DBUS_TYPE_BYTE, caps, caps_size); if (spa_bt_profile_from_uuid(uuid) & SPA_BT_PROFILE_A2DP_SOURCE) { - str = "DelayReporting"; delay_reporting = TRUE; - append_basic_variant_dict_entry(&dict, DBUS_TYPE_STRING, &str, DBUS_TYPE_BOOLEAN, "b", &delay_reporting); + append_basic_variant_dict_entry(&dict, "DelayReporting", DBUS_TYPE_BOOLEAN, "b", &delay_reporting); } dbus_message_iter_close_container(&entry, &dict); @@ -3495,11 +3522,11 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage * if (!is_a2dp_codec_enabled(monitor, codec)) continue; - caps_size = codec->fill_caps(codec, 0, caps); - if (caps_size < 0) - continue; - if (codec->decode != NULL) { + caps_size = codec->fill_caps(codec, A2DP_CODEC_FLAG_SINK, caps); + if (caps_size < 0) + continue; + ret = a2dp_codec_to_endpoint(codec, A2DP_SINK_ENDPOINT, &endpoint); if (ret == 0) { spa_log_info(monitor->log, "register A2DP sink codec %s: %s", a2dp_codecs[i]->name, endpoint); @@ -3510,6 +3537,10 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage * } if (codec->encode != NULL) { + caps_size = codec->fill_caps(codec, 0, caps); + if (caps_size < 0) + continue; + ret = a2dp_codec_to_endpoint(codec, A2DP_SOURCE_ENDPOINT, &endpoint); if (ret == 0) { spa_log_info(monitor->log, "register A2DP source codec %s: %s", a2dp_codecs[i]->name, endpoint); @@ -4280,6 +4311,7 @@ static int impl_clear(struct spa_handle *handle) struct spa_bt_device *d; struct spa_bt_remote_endpoint *ep; struct spa_bt_transport *t; + const struct spa_dict_item *it; size_t i; monitor = (struct spa_bt_monitor *) handle; @@ -4311,6 +4343,11 @@ static int impl_clear(struct spa_handle *handle) monitor->backends[i] = NULL; } + spa_dict_for_each(it, &monitor->global_settings) { + free((void *)it->key); + free((void *)it->value); + } + free((void*)monitor->enabled_codecs.items); spa_zero(monitor->enabled_codecs); @@ -4447,6 +4484,26 @@ fallback: return 0; } +static void get_global_settings(struct spa_bt_monitor *this, const struct spa_dict *dict) +{ + uint32_t n_items = 0; + uint32_t i; + + if (dict == NULL) { + this->global_settings = SPA_DICT_INIT(this->global_setting_items, 0); + return; + } + + for (i = 0; i < dict->n_items && n_items < SPA_N_ELEMENTS(this->global_setting_items); i++) { + const struct spa_dict_item *it = &dict->items[i]; + if (spa_strstartswith(it->key, "bluez5.") && it->value != NULL) + this->global_setting_items[n_items++] = + SPA_DICT_ITEM_INIT(strdup(it->key), strdup(it->value)); + } + + this->global_settings = SPA_DICT_INIT(this->global_setting_items, n_items); +} + static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, @@ -4540,6 +4597,8 @@ impl_init(const struct spa_handle_factory *factory, this->backend_selection = BACKEND_NATIVE; + get_global_settings(this, info); + if (info) { const char *str; uint32_t tmp; diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 6c45a028b25a98478137eba40871490dab7bd165..5a23ea4409c1c0842957ae9952b1be1cba2c6d68 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -301,6 +301,8 @@ static void emit_info(struct impl *this, bool full); static float get_soft_volume_boost(struct node *node) { + const struct a2dp_codec *codec = node->transport ? node->transport->a2dp_codec : NULL; + /* * For A2DP duplex, the duplex microphone channel sometimes does not appear * to have hardware gain, and input volume is very low. @@ -310,7 +312,8 @@ static float get_soft_volume_boost(struct node *node) * If this causes clipping, the user can just reduce the mic volume to * bring SW gain below 1. */ - if (node->a2dp_duplex && node->transport && + if (node->a2dp_duplex && node->transport && codec && codec->info && + spa_atob(spa_dict_lookup(codec->info, "duplex.boost")) && node->id == DEVICE_ID_SOURCE && !node->transport->volumes[SPA_BT_VOLUME_ID_RX].active) return 10.0f; /* 20 dB boost */ @@ -337,35 +340,57 @@ static void node_update_soft_volumes(struct node *node, float hw_volume) } } -static void volume_changed(void *userdata) +static bool node_update_volume_from_transport(struct node *node, bool reset) { - struct node *node = userdata; struct impl *impl = node->impl; struct spa_bt_transport_volume *t_volume; float prev_hw_volume; if (!node->transport || !spa_bt_transport_volume_enabled(node->transport)) - return; + return false; /* PW is the controller for remote device. */ if (impl->profile != DEVICE_PROFILE_A2DP && impl->profile != DEVICE_PROFILE_HSP_HFP) - return; + return false; t_volume = &node->transport->volumes[node->id]; if (!t_volume->active) - return; + return false; prev_hw_volume = node_get_hw_volume(node); - for (uint32_t i = 0; i < node->n_channels; ++i) { - node->volumes[i] = prev_hw_volume > 0.0f - ? node->volumes[i] * t_volume->volume / prev_hw_volume - : t_volume->volume; + + if (!reset) { + for (uint32_t i = 0; i < node->n_channels; ++i) { + node->volumes[i] = prev_hw_volume > 0.0f + ? node->volumes[i] * t_volume->volume / prev_hw_volume + : t_volume->volume; + } + } else { + for (uint32_t i = 0; i < node->n_channels; ++i) + node->volumes[i] = t_volume->volume; } node_update_soft_volumes(node, t_volume->volume); + /* + * Consider volume changes from the headset as requested + * by the user, and to be saved by the SM. + */ + node->save = true; + + return true; +} + +static void volume_changed(void *userdata) +{ + struct node *node = userdata; + struct impl *impl = node->impl; + + if (!node_update_volume_from_transport(node, false)) + return; + emit_volume(impl, node); impl->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; @@ -469,6 +494,8 @@ static void emit_node(struct impl *this, struct spa_bt_transport *t, this->nodes[id].volumes[i] = this->nodes[id].volumes[i % prev_channels]; } + node_update_volume_from_transport(&this->nodes[id], true); + boost = get_soft_volume_boost(&this->nodes[id]); if (boost != 1.0f) { size_t i; @@ -888,7 +915,7 @@ static void profiles_changed(void *userdata, uint32_t prev_profiles, uint32_t pr if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) { free(this->supported_codecs); this->supported_codecs = spa_bt_device_get_supported_a2dp_codecs( - this->bt_dev, &this->supported_codec_count); + this->bt_dev, &this->supported_codec_count, true); } switch (this->profile) { @@ -1103,7 +1130,7 @@ static void set_initial_profile(struct impl *this) if (this->supported_codecs) free(this->supported_codecs); this->supported_codecs = spa_bt_device_get_supported_a2dp_codecs( - this->bt_dev, &this->supported_codec_count); + this->bt_dev, &this->supported_codec_count, true); /* Prefer A2DP, then HFP, then null, but select AG if the device appears not to have A2DP_SINK or any HEAD_UNIT profile */ @@ -1301,16 +1328,17 @@ static bool validate_profile(struct impl *this, uint32_t profile, } static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, - uint32_t id, uint32_t port, uint32_t dev, uint32_t profile) + uint32_t id, uint32_t port, uint32_t profile) { struct spa_bt_device *device = this->bt_dev; struct spa_pod_frame f[2]; enum spa_direction direction; - const char *name_prefix, *description, *port_type; + const char *name_prefix, *description, *hfp_description, *port_type; enum spa_bt_form_factor ff; enum spa_bluetooth_audio_codec codec; char name[128]; uint32_t i, j, mask, next; + uint32_t dev = SPA_ID_INVALID, enum_dev; ff = spa_bt_form_factor_from_class(device->bluetooth_class); @@ -1318,52 +1346,62 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, case SPA_BT_FORM_FACTOR_HEADSET: name_prefix = "headset"; description = _("Headset"); + hfp_description = _("Handsfree"); port_type = "headset"; break; case SPA_BT_FORM_FACTOR_HANDSFREE: name_prefix = "handsfree"; description = _("Handsfree"); + hfp_description = _("Handsfree (HFP)"); port_type = "handsfree"; break; case SPA_BT_FORM_FACTOR_MICROPHONE: name_prefix = "microphone"; description = _("Microphone"); + hfp_description = _("Handsfree"); port_type = "mic"; break; case SPA_BT_FORM_FACTOR_SPEAKER: name_prefix = "speaker"; description = _("Speaker"); + hfp_description = _("Handsfree"); port_type = "speaker"; break; case SPA_BT_FORM_FACTOR_HEADPHONE: name_prefix = "headphone"; description = _("Headphone"); + hfp_description = _("Handsfree"); port_type = "headphones"; break; case SPA_BT_FORM_FACTOR_PORTABLE: name_prefix = "portable"; description = _("Portable"); + hfp_description = _("Handsfree"); port_type = "portable"; break; case SPA_BT_FORM_FACTOR_CAR: name_prefix = "car"; description = _("Car"); + hfp_description = _("Handsfree"); port_type = "car"; break; case SPA_BT_FORM_FACTOR_HIFI: name_prefix = "hifi"; description = _("HiFi"); + hfp_description = _("Handsfree"); port_type = "hifi"; break; case SPA_BT_FORM_FACTOR_PHONE: name_prefix = "phone"; description = _("Phone"); + hfp_description = _("Handsfree"); port_type = "phone"; break; case SPA_BT_FORM_FACTOR_UNKNOWN: default: name_prefix = "bluetooth"; description = _("Bluetooth"); + hfp_description = _("Bluetooth (HFP)"); port_type = "bluetooth"; break; } @@ -1372,16 +1410,51 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, case 0: direction = SPA_DIRECTION_INPUT; snprintf(name, sizeof(name), "%s-input", name_prefix); + enum_dev = DEVICE_ID_SOURCE; + if (profile == DEVICE_PROFILE_A2DP) + dev = enum_dev; + else if (profile != SPA_ID_INVALID) + enum_dev = SPA_ID_INVALID; break; case 1: direction = SPA_DIRECTION_OUTPUT; snprintf(name, sizeof(name), "%s-output", name_prefix); + enum_dev = DEVICE_ID_SINK; + if (profile == DEVICE_PROFILE_A2DP) + dev = enum_dev; + else if (profile != SPA_ID_INVALID) + enum_dev = SPA_ID_INVALID; + break; + case 2: + direction = SPA_DIRECTION_INPUT; + snprintf(name, sizeof(name), "%s-hf-input", name_prefix); + description = hfp_description; + enum_dev = DEVICE_ID_SOURCE; + if (profile == DEVICE_PROFILE_HSP_HFP) + dev = enum_dev; + else if (profile != SPA_ID_INVALID) + enum_dev = SPA_ID_INVALID; + break; + case 3: + direction = SPA_DIRECTION_OUTPUT; + snprintf(name, sizeof(name), "%s-hf-output", name_prefix); + description = hfp_description; + enum_dev = DEVICE_ID_SINK; + if (profile == DEVICE_PROFILE_HSP_HFP) + dev = enum_dev; + else if (profile != SPA_ID_INVALID) + enum_dev = SPA_ID_INVALID; break; default: errno = EINVAL; return NULL; } + if (enum_dev == SPA_ID_INVALID) { + errno = EINVAL; + return NULL; + } + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamRoute, id); spa_pod_builder_add(b, SPA_PARAM_ROUTE_index, SPA_POD_Int(port), @@ -1406,6 +1479,11 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, for (i = 1; (j = get_profile_from_index(this, i, &next, &codec)) != SPA_ID_INVALID; i = next) { uint32_t profile_mask; + if (j == DEVICE_PROFILE_A2DP && !(port == 0 || port == 1)) + continue; + if (j == DEVICE_PROFILE_HSP_HFP && !(port == 2 || port == 3)) + continue; + profile_mask = profile_direction_mask(this, j, codec); if (!(profile_mask & (1 << direction))) continue; @@ -1472,8 +1550,7 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, spa_pod_builder_prop(b, SPA_PARAM_ROUTE_devices, 0); spa_pod_builder_push_array(b, &f[1]); - /* port and device indexes are the same, 0=source, 1=sink */ - spa_pod_builder_int(b, port); + spa_pod_builder_int(b, enum_dev); spa_pod_builder_pop(b, &f[1]); if (profile != SPA_ID_INVALID) { @@ -1640,9 +1717,8 @@ static int impl_enum_params(void *object, int seq, case SPA_PARAM_EnumRoute: { switch (result.index) { - case 0: case 1: - param = build_route(this, &b, id, result.index, - SPA_ID_INVALID, SPA_ID_INVALID); + case 0: case 1: case 2: case 3: + param = build_route(this, &b, id, result.index, SPA_ID_INVALID); if (param == NULL) goto next; break; @@ -1654,9 +1730,8 @@ static int impl_enum_params(void *object, int seq, case SPA_PARAM_Route: { switch (result.index) { - case 0: case 1: - param = build_route(this, &b, id, result.index, - result.index, this->profile); + case 0: case 1: case 2: case 3: + param = build_route(this, &b, id, result.index, this->profile); if (param == NULL) goto next; break; diff --git a/spa/plugins/bluez5/codec-loader.c b/spa/plugins/bluez5/codec-loader.c index cce3c4bc65aac7736f782fd8b6625c40aa08b89b..172c42309455b473ad22924c1de7de227036d291 100644 --- a/spa/plugins/bluez5/codec-loader.c +++ b/spa/plugins/bluez5/codec-loader.c @@ -63,6 +63,11 @@ static int codec_order(const struct a2dp_codec *c) SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX, SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM, SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX, + SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05, + SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_51, + SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_71, + SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_DUPLEX, + SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO, }; size_t i; for (i = 0; i < SPA_N_ELEMENTS(order); ++i) @@ -138,6 +143,9 @@ static int load_a2dp_codecs_from(struct impl *impl, const char *factory_name, co spa_log_debug(impl->log, "loaded A2DP codec %s from %s", c->name, factory_name); + if (c->set_log) + c->set_log(impl->log); + impl->codecs[impl->n_codecs++] = c; ++n_codecs; @@ -171,7 +179,8 @@ const struct a2dp_codec * const *load_a2dp_codecs(struct spa_plugin_loader *load A2DP_CODEC_FACTORY_LIB("faststream"), A2DP_CODEC_FACTORY_LIB("ldac"), A2DP_CODEC_FACTORY_LIB("sbc"), - A2DP_CODEC_FACTORY_LIB("lc3plus") + A2DP_CODEC_FACTORY_LIB("lc3plus"), + A2DP_CODEC_FACTORY_LIB("opus") #undef A2DP_CODEC_FACTORY_LIB }; diff --git a/spa/plugins/bluez5/decode-buffer.h b/spa/plugins/bluez5/decode-buffer.h new file mode 100644 index 0000000000000000000000000000000000000000..434f735512c7fd94d4e719a1e946c5a3e2789ddc --- /dev/null +++ b/spa/plugins/bluez5/decode-buffer.h @@ -0,0 +1,486 @@ +/* Spa Bluez5 decode buffer + * + * Copyright © 2022 Pauli Virtanen + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/** + * \file decode-buffer.h Buffering for Bluetooth sources + * + * A linear buffer, which is compacted when it gets half full. + * + * Also contains buffering logic, which calculates a rate correction + * factor to maintain the buffer level at the target value. + * + * Consider typical packet intervals with nominal frame duration + * of 10ms: + * + * ... 5ms | 5ms | 20ms | 5ms | 5ms | 20ms ... + * + * ... 3ms | 3ms | 4ms | 30ms | 3ms | 3ms | 4ms | 30ms ... + * + * plus random jitter; 10ms nominal may occasionally have 20+ms interval. + * The regular timer cycle cannot be aligned with this, so process() + * may occur at any time. + * + * The buffer level is the difference between the number of samples in + * buffer immediately after receiving a packet, and the samples consumed + * before receiving the next packet. + * + * The buffer level indicates how much any packet can be delayed without + * underrun. If it is positive, there are no underruns. + * + * The rate correction aims to maintain the average level at a safety margin. + */ + +#ifndef SPA_BLUEZ5_DECODE_BUFFER_H +#define SPA_BLUEZ5_DECODE_BUFFER_H + +#include <stdlib.h> +#include <spa/utils/defs.h> +#include <spa/support/log.h> + +#define BUFFERING_LONG_MSEC (2*60000) +#define BUFFERING_SHORT_MSEC 1000 +#define BUFFERING_RATE_DIFF_MAX 0.005 + +/** + * Safety margin. + * + * The spike is the long-window maximum difference + * between minimum and average buffer level. + */ +#define BUFFERING_TARGET(spike,packet_size) \ + SPA_CLAMP((spike)*3/2, (packet_size), 6*(packet_size)) + +/** + * Rate controller. + * + * It's here in a form, where it operates on the running average + * so it's compatible with the level spike determination, and + * clamping the rate to a range is easy. The impulse response + * is similar to spa_dll, and step response does not have sign changes. + * + * The controller iterates as + * + * avg(j+1) = (1 - beta) avg(j) + beta level(j) + * corr(j+1) = corr(j) + a [avg(j+1) - avg(j)] / duration + * + b [avg(j) - target] / duration + * + * with beta = duration/avg_period < 0.5 is the moving average parameter, + * and a = beta/3 + ..., b = beta^2/27 + .... + * + * This choice results to c(j) being low-pass filtered, and buffer level(j) + * converging towards target with stable damped evolution with eigenvalues + * real and close to each other around (1 - beta)^(1/3). + * + * Derivation: + * + * The deviation from the buffer level target evolves as + * + * delta(j) = level(j) - target + * delta(j+1) = delta(j) + r(j) - c(j+1) + * + * where r is samples received in one duration, and c corrected rate + * (samples per duration). + * + * The rate correction is in general determined by linear filter f + * + * c(j+1) = c(j) + \sum_{k=0}^\infty delta(j - k) f(k) + * + * If \sum_k f(k) is not zero, the only fixed point is c=r, delta=0, + * so this structure (if the filter is stable) rate matches and + * drives buffer level to target. + * + * The z-transform then is + * + * delta(z) = G(z) r(z) + * c(z) = F(z) delta(z) + * G(z) = (z - 1) / [(z - 1)^2 + z f(z)] + * F(z) = f(z) / (z - 1) + * + * We now want: poles of G(z) must be in |z|<1 for stability, F(z) + * should damp high frequencies, and f(z) is causal. + * + * To satisfy the conditions, take + * + * (z - 1)^2 + z f(z) = p(z) / q(z) + * + * where p(z) is polynomial with leading term z^n with wanted root + * structure, and q(z) is any polynomial with leading term z^{n-2}. + * This guarantees f(z) is causal, and G(z) = (z-1) q(z) / p(z). + * We can choose p(z) and q(z) to improve low-pass properties of F(z). + * + * Simplest choice is p(z)=(z-x)^2 and q(z)=1, but that gives flat + * high frequency response in F(z). Better choice is p(z) = (z-u)*(z-v)*(z-w) + * and q(z) = z - r. To make F(z) better lowpass, one can cancel + * a resulting 1/z pole in F(z) by setting r=u*v*w. Then, + * + * G(z) = (z - u*v*w)*(z - 1) / [(z - u)*(z - v)*(z - w)] + * F(z) = (a z + b - a) / (z - 1) * H(z) + * H(z) = beta / (z - 1 + beta) + * beta = 1 - u*v*w + * a = [(1-u) + (1-v) + (1-w) - beta] / beta + * b = (1-u)*(1-v)*(1-w) / beta + * + * which corresponds to iteration for c(j): + * + * avg(j+1) = (1 - beta) avg(j) + beta delta(j) + * c(j+1) = c(j) + a [avg(j+1) - avg(j)] + b avg(j) + * + * So the controller operates on the running average, + * which gives the low-pass property for c(j). + * + * The simplest filter is obtained by putting the poles at + * u=v=w=(1-beta)**(1/3). Since beta << 1, computing the root + * can be avoided by expanding in series. + * + * Overshoot in impulse response could be reduced by moving one of the + * poles closer to z=1, but this increases the step response time. + */ +struct spa_bt_rate_control +{ + double avg; + double corr; +}; + +static void spa_bt_rate_control_init(struct spa_bt_rate_control *this, double level) +{ + this->avg = level; + this->corr = 1.0; +} + +static double spa_bt_rate_control_update(struct spa_bt_rate_control *this, double level, + double target, double duration, double period) +{ + /* + * u = (1 - beta)^(1/3) + * x = a / beta + * y = b / beta + * a = (2 + u) * (1 - u)^2 / beta + * b = (1 - u)^3 / beta + * beta -> 0 + */ + const double beta = SPA_CLAMP(duration / period, 0, 0.5); + const double x = 1.0/3; + const double y = beta/27; + double avg; + + avg = beta * level + (1 - beta) * this->avg; + this->corr += x * (avg - this->avg) / period + + y * (this->avg - target) / period; + this->avg = avg; + + this->corr = SPA_CLAMP(this->corr, + 1 - BUFFERING_RATE_DIFF_MAX, + 1 + BUFFERING_RATE_DIFF_MAX); + + return this->corr; +} + + +/** Windowed min/max */ +struct spa_bt_ptp +{ + union { + int32_t min; + int32_t mins[4]; + }; + union { + int32_t max; + int32_t maxs[4]; + }; + uint32_t pos; + uint32_t period; +}; + +struct spa_bt_decode_buffer +{ + struct spa_log *log; + + uint32_t frame_size; + uint32_t rate; + + uint8_t *buffer_decoded; + uint32_t buffer_size; + uint32_t buffer_reserve; + uint32_t write_index; + uint32_t read_index; + + struct spa_bt_ptp spike; /**< spikes (long window) */ + struct spa_bt_ptp packet_size; /**< packet size (short window) */ + + struct spa_bt_rate_control ctl; + double corr; + + uint32_t prev_consumed; + uint32_t prev_avail; + uint32_t prev_duration; + uint32_t underrun; + uint32_t pos; + + uint8_t received:1; + uint8_t buffering:1; +}; + +static void spa_bt_ptp_init(struct spa_bt_ptp *p, int32_t period) +{ + size_t i; + + spa_zero(*p); + for (i = 0; i < SPA_N_ELEMENTS(p->mins); ++i) { + p->mins[i] = INT32_MAX; + p->maxs[i] = INT32_MIN; + } + p->period = period; +} + +static void spa_bt_ptp_update(struct spa_bt_ptp *p, int32_t value, uint32_t duration) +{ + const size_t n = SPA_N_ELEMENTS(p->mins); + size_t i; + + for (i = 0; i < n; ++i) { + p->mins[i] = SPA_MIN(p->mins[i], value); + p->maxs[i] = SPA_MAX(p->maxs[i], value); + } + + p->pos += duration; + if (p->pos >= p->period / (n - 1)) { + p->pos = 0; + for (i = 1; i < SPA_N_ELEMENTS(p->mins); ++i) { + p->mins[i-1] = p->mins[i]; + p->maxs[i-1] = p->maxs[i]; + } + p->mins[n-1] = INT32_MAX; + p->maxs[n-1] = INT32_MIN; + } +} + +static int spa_bt_decode_buffer_init(struct spa_bt_decode_buffer *this, struct spa_log *log, + uint32_t frame_size, uint32_t rate, uint32_t quantum_limit, uint32_t reserve) +{ + spa_zero(*this); + this->frame_size = frame_size; + this->rate = rate; + this->log = log; + this->buffer_reserve = this->frame_size * reserve; + this->buffer_size = this->frame_size * quantum_limit * 2; + this->buffer_size += this->buffer_reserve; + this->corr = 1.0; + this->buffering = true; + + spa_bt_rate_control_init(&this->ctl, 0); + + spa_bt_ptp_init(&this->spike, (uint64_t)this->rate * BUFFERING_LONG_MSEC / 1000); + spa_bt_ptp_init(&this->packet_size, (uint64_t)this->rate * BUFFERING_SHORT_MSEC / 1000); + + if ((this->buffer_decoded = malloc(this->buffer_size)) == NULL) { + this->buffer_size = 0; + return -ENOMEM; + } + return 0; +} + +static void spa_bt_decode_buffer_clear(struct spa_bt_decode_buffer *this) +{ + free(this->buffer_decoded); + spa_zero(*this); +} + +static void spa_bt_decode_buffer_compact(struct spa_bt_decode_buffer *this) +{ + uint32_t avail; + + spa_assert(this->read_index <= this->write_index); + + if (this->read_index == this->write_index) { + this->read_index = 0; + this->write_index = 0; + goto done; + } + + if (this->write_index > this->read_index + this->buffer_size - this->buffer_reserve) { + /* Drop data to keep buffer reserve free */ + spa_log_info(this->log, "%p buffer overrun: dropping data", this); + this->read_index = this->write_index + this->buffer_reserve - this->buffer_size; + } + + if (this->write_index < (this->buffer_size - this->buffer_reserve) / 2 + || this->read_index == 0) + goto done; + + avail = this->write_index - this->read_index; + spa_memmove(this->buffer_decoded, + SPA_PTROFF(this->buffer_decoded, this->read_index, void), + avail); + this->read_index = 0; + this->write_index = avail; + +done: + spa_assert(this->buffer_size - this->write_index >= this->buffer_reserve); +} + +static void *spa_bt_decode_buffer_get_write(struct spa_bt_decode_buffer *this, uint32_t *avail) +{ + spa_bt_decode_buffer_compact(this); + spa_assert(this->buffer_size >= this->write_index); + *avail = this->buffer_size - this->write_index; + return SPA_PTROFF(this->buffer_decoded, this->write_index, void); +} + +static void spa_bt_decode_buffer_write_packet(struct spa_bt_decode_buffer *this, uint32_t size) +{ + spa_assert(size % this->frame_size == 0); + this->write_index += size; + this->received = true; + spa_bt_ptp_update(&this->packet_size, size / this->frame_size, size / this->frame_size); +} + +static void *spa_bt_decode_buffer_get_read(struct spa_bt_decode_buffer *this, uint32_t *avail) +{ + spa_assert(this->write_index >= this->read_index); + if (!this->buffering) + *avail = this->write_index - this->read_index; + else + *avail = 0; + return SPA_PTROFF(this->buffer_decoded, this->read_index, void); +} + +static void spa_bt_decode_buffer_read(struct spa_bt_decode_buffer *this, uint32_t size) +{ + spa_assert(size % this->frame_size == 0); + this->read_index += size; +} + +static void spa_bt_decode_buffer_recover(struct spa_bt_decode_buffer *this) +{ + int32_t size = (this->write_index - this->read_index) / this->frame_size; + int32_t level; + + this->prev_avail = size * this->frame_size; + this->prev_consumed = this->prev_duration; + + level = (int32_t)this->prev_avail/this->frame_size + - (int32_t)this->prev_duration; + this->corr = 1.0; + + spa_bt_rate_control_init(&this->ctl, level); +} + +static void spa_bt_decode_buffer_process(struct spa_bt_decode_buffer *this, uint32_t samples, uint32_t duration) +{ + const uint32_t data_size = samples * this->frame_size; + const int32_t packet_size = SPA_CLAMP(this->packet_size.max, 0, INT32_MAX/8); + const int32_t max_level = SPA_MAX(8 * packet_size, (int32_t)duration); + uint32_t avail; + + if (SPA_UNLIKELY(duration != this->prev_duration)) { + this->prev_duration = duration; + spa_bt_decode_buffer_recover(this); + } + + if (SPA_UNLIKELY(this->buffering)) { + int32_t size = (this->write_index - this->read_index) / this->frame_size; + + this->corr = 1.0; + + spa_log_trace(this->log, "%p buffering size:%d", this, (int)size); + + if (this->received && + packet_size > 0 && + size >= SPA_MAX(3*packet_size, (int32_t)duration)) + this->buffering = false; + else + return; + + spa_bt_decode_buffer_recover(this); + } + + spa_bt_decode_buffer_get_read(this, &avail); + + if (this->received) { + const uint32_t avg_period = (uint64_t)this->rate * BUFFERING_SHORT_MSEC / 1000; + int32_t level, target; + + /* Track buffer level */ + level = (int32_t)(this->prev_avail/this->frame_size) - (int32_t)this->prev_consumed; + level = SPA_MAX(level, -max_level); + this->prev_consumed = SPA_MIN(this->prev_consumed, avg_period); + + spa_bt_ptp_update(&this->spike, this->ctl.avg - level, this->prev_consumed); + + /* Update target level */ + target = BUFFERING_TARGET(this->spike.max, packet_size); + + if (level > SPA_MAX(4 * target, 2*(int32_t)duration) && + avail > data_size) { + /* Lagging too much: drop data */ + uint32_t size = SPA_MIN(avail - data_size, + (level - target*5/2) * this->frame_size); + + spa_bt_decode_buffer_read(this, size); + spa_log_trace(this->log, "%p overrun samples:%d level:%d target:%d", + this, (int)size/this->frame_size, + (int)level, (int)target); + + spa_bt_decode_buffer_recover(this); + } + + this->pos += this->prev_consumed; + if (this->pos > this->rate) { + spa_log_debug(this->log, + "%p avg:%d target:%d level:%d buffer:%d spike:%d corr:%f", + this, + (int)this->ctl.avg, + (int)target, + (int)level, + (int)(avail / this->frame_size), + (int)this->spike.max, + (double)this->corr); + this->pos = 0; + } + + this->corr = spa_bt_rate_control_update(&this->ctl, + level, target, this->prev_consumed, avg_period); + + spa_bt_decode_buffer_get_read(this, &avail); + + this->prev_consumed = 0; + this->prev_avail = avail; + this->underrun = 0; + this->received = false; + } + + if (avail < data_size) { + spa_log_trace(this->log, "%p underrun samples:%d", this, + (data_size - avail) / this->frame_size); + this->underrun += samples; + if (this->underrun >= SPA_MIN((uint32_t)max_level, this->buffer_size / this->frame_size)) { + this->buffering = true; + spa_log_debug(this->log, "%p underrun too much: start buffering", this); + } + } + + this->prev_consumed += samples; +} + +#endif diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index 2bdace5d3e9590edd554ca7eb4e0b5cbf7c4edc9..90348afa05f1805507adb52451241fce75105a46 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -134,13 +134,13 @@ extern "C" { #define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported" -#define SPA_BT_UUID_A2DP_SOURCE "0000110A-0000-1000-8000-00805F9B34FB" -#define SPA_BT_UUID_A2DP_SINK "0000110B-0000-1000-8000-00805F9B34FB" -#define SPA_BT_UUID_HSP_HS "00001108-0000-1000-8000-00805F9B34FB" -#define SPA_BT_UUID_HSP_HS_ALT "00001131-0000-1000-8000-00805F9B34FB" -#define SPA_BT_UUID_HSP_AG "00001112-0000-1000-8000-00805F9B34FB" -#define SPA_BT_UUID_HFP_HF "0000111E-0000-1000-8000-00805F9B34FB" -#define SPA_BT_UUID_HFP_AG "0000111F-0000-1000-8000-00805F9B34FB" +#define SPA_BT_UUID_A2DP_SOURCE "0000110a-0000-1000-8000-00805f9b34fb" +#define SPA_BT_UUID_A2DP_SINK "0000110b-0000-1000-8000-00805f9b34fb" +#define SPA_BT_UUID_HSP_HS "00001108-0000-1000-8000-00805f9b34fb" +#define SPA_BT_UUID_HSP_HS_ALT "00001131-0000-1000-8000-00805f9b34fb" +#define SPA_BT_UUID_HSP_AG "00001112-0000-1000-8000-00805f9b34fb" +#define SPA_BT_UUID_HFP_HF "0000111e-0000-1000-8000-00805f9b34fb" +#define SPA_BT_UUID_HFP_AG "0000111f-0000-1000-8000-00805f9b34fb" #define PROFILE_HSP_AG "/Profile/HSPAG" #define PROFILE_HSP_HS "/Profile/HSPHS" @@ -490,8 +490,8 @@ int spa_bt_device_add_profile(struct spa_bt_device *device, enum spa_bt_profile int spa_bt_device_connect_profile(struct spa_bt_device *device, enum spa_bt_profile profile); int spa_bt_device_check_profiles(struct spa_bt_device *device, bool force); int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a2dp_codec * const *codecs); -bool spa_bt_device_supports_a2dp_codec(struct spa_bt_device *device, const struct a2dp_codec *codec); -const struct a2dp_codec **spa_bt_device_get_supported_a2dp_codecs(struct spa_bt_device *device, size_t *count); +bool spa_bt_device_supports_a2dp_codec(struct spa_bt_device *device, const struct a2dp_codec *codec, bool sink); +const struct a2dp_codec **spa_bt_device_get_supported_a2dp_codecs(struct spa_bt_device *device, size_t *count, bool sink); int spa_bt_device_ensure_hfp_codec(struct spa_bt_device *device, unsigned int codec); int spa_bt_device_supports_hfp_codec(struct spa_bt_device *device, unsigned int codec); int spa_bt_device_release_transports(struct spa_bt_device *device); diff --git a/spa/plugins/bluez5/meson.build b/spa/plugins/bluez5/meson.build index 03f986b37249096b2ee37c67f3d5ea438301b914..2a17c78ee7cdd74b391d6f35d0622a604b755f8b 100644 --- a/spa/plugins/bluez5/meson.build +++ b/spa/plugins/bluez5/meson.build @@ -111,13 +111,23 @@ if ldac_dep.found() endif if get_option('bluez5-codec-lc3plus').allowed() and lc3plus_dep.found() - lc3plus_args = codec_args - lc3plus_dep = [ lc3plus_dep ] bluez_codec_lc3plus = shared_library('spa-codec-bluez5-lc3plus', [ 'a2dp-codec-lc3plus.c', 'a2dp-codecs.c' ], include_directories : [ configinc ], - c_args : ldac_args, + c_args : codec_args, dependencies : [ spa_dep, lc3plus_dep, mathlib ], install : true, install_dir : spa_plugindir / 'bluez5') endif + +if get_option('bluez5-codec-opus').allowed() and opus_dep.found() + opus_args = codec_args + opus_dep = [ opus_dep ] + bluez_codec_opus = shared_library('spa-codec-bluez5-opus', + [ 'a2dp-codec-opus.c', 'a2dp-codecs.c' ], + include_directories : [ configinc ], + c_args : opus_args, + dependencies : [ spa_dep, opus_dep, mathlib ], + install : true, + install_dir : spa_plugindir / 'bluez5') +endif diff --git a/spa/plugins/bluez5/sco-io.c b/spa/plugins/bluez5/sco-io.c index 0b399e910f22c64c51b607e50325643cbe2e6cfc..06577507677842838ea6ddaa718665e514608368 100644 --- a/spa/plugins/bluez5/sco-io.c +++ b/spa/plugins/bluez5/sco-io.c @@ -55,16 +55,12 @@ * since kernel might not report it as the socket MTU, see * https://lore.kernel.org/linux-bluetooth/20201210003528.3pmaxvubiwegxmhl@pali/T/ * - * Since 24 is the packet size for the smallest setting (ALT1), we'll stop - * reading when rx packet of at least this size is seen, and use its size as the - * heuristic maximum write MTU. Of course, if we have a source connected, we'll - * continue reading without stopping. + * We continue reading also when there's no source connected, to keep socket + * flushed. * * XXX: when the kernel/backends start giving the right values, the heuristic * XXX: can be removed */ -#define HEURISTIC_MIN_MTU 24 - #define MAX_MTU 1024 @@ -94,12 +90,6 @@ static void update_source(struct spa_bt_sco_io *io) int enabled; int changed = 0; - enabled = io->source_cb != NULL || io->read_size < HEURISTIC_MIN_MTU; - if (SPA_FLAG_IS_SET(io->source.mask, SPA_IO_IN) != enabled) { - SPA_FLAG_UPDATE(io->source.mask, SPA_IO_IN, enabled); - changed = 1; - } - enabled = io->sink_cb != NULL; if (SPA_FLAG_IS_SET(io->source.mask, SPA_IO_OUT) != enabled) { SPA_FLAG_UPDATE(io->source.mask, SPA_IO_OUT, enabled); @@ -118,11 +108,6 @@ static void sco_io_on_ready(struct spa_source *source) if (SPA_FLAG_IS_SET(source->rmask, SPA_IO_IN)) { int res; - /* - * Note that we will read from the socket for a few times even - * when there is no source callback, to autodetect packet size. - */ - read_again: res = read(io->fd, io->read_buffer, SPA_MIN(io->read_mtu, MAX_MTU)); if (res <= 0) { diff --git a/spa/plugins/bluez5/sco-sink.c b/spa/plugins/bluez5/sco-sink.c index 909a14928910a39f94a39aa4d5e65905296132c5..d92bada2af852540f132759f84a24c27cd1302f0 100644 --- a/spa/plugins/bluez5/sco-sink.c +++ b/spa/plugins/bluez5/sco-sink.c @@ -759,6 +759,7 @@ static void emit_node_info(struct impl *this, bool full) { SPA_KEY_MEDIA_CLASS, "Stream/Input/Audio" }, { "media.name", ((this->transport && this->transport->device->name) ? this->transport->device->name : "HSP/HFP") }, + { SPA_KEY_MEDIA_ROLE, "Communication" }, }; bool is_ag = this->transport && (this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY); @@ -1146,8 +1147,8 @@ static int impl_node_process(void *object) spa_return_val_if_fail(this != NULL, -EINVAL); port = &this->port; - io = port->io; - spa_return_val_if_fail(io != NULL, -EIO); + if ((io = port->io) == NULL) + return -EIO; if (io->status == SPA_STATUS_HAVE_DATA && io->buffer_id < port->n_buffers) { struct buffer *b = &port->buffers[io->buffer_id]; diff --git a/spa/plugins/bluez5/sco-source.c b/spa/plugins/bluez5/sco-source.c index 1819029b83250a15e38bbd54fb96fa55c16bb0a6..52a1d27cce5eac79b64dd67214a93b52b0424033 100644 --- a/spa/plugins/bluez5/sco-source.c +++ b/spa/plugins/bluez5/sco-source.c @@ -58,11 +58,11 @@ static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.source.sco" #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic +#include "decode-buffer.h" + #define DEFAULT_CLOCK_NAME "clock.system.monotonic" struct props { - uint32_t min_latency; - uint32_t max_latency; char clock_name[64]; }; @@ -101,8 +101,7 @@ struct port { struct spa_list free; struct spa_list ready; - struct buffer *current_buffer; - uint32_t ready_offset; + struct spa_bt_decode_buffer buffer; }; struct impl { @@ -116,6 +115,8 @@ struct impl { struct spa_hook_list hooks; struct spa_callbacks callbacks; + uint32_t quantum_limit; + uint64_t info_all; struct spa_node_info info; #define IDX_PropInfo 0 @@ -132,10 +133,18 @@ struct impl { unsigned int started:1; unsigned int following:1; + unsigned int matching:1; + unsigned int resampling:1; + + struct spa_source timer_source; + int timerfd; struct spa_io_clock *clock; struct spa_io_position *position; + uint64_t current_time; + uint64_t next_time; + /* mSBC */ sbc_t msbc; bool msbc_seq_initialized; @@ -150,13 +159,8 @@ struct impl { #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) == 0) -static const uint32_t default_min_latency = 128; -static const uint32_t default_max_latency = 512; - static void reset_props(struct props *props) { - props->min_latency = default_min_latency; - props->max_latency = default_max_latency; strncpy(props->clock_name, DEFAULT_CLOCK_NAME, sizeof(props->clock_name)); } @@ -184,23 +188,7 @@ static int impl_node_enum_params(void *object, int seq, switch (id) { case SPA_PARAM_PropInfo: { - struct props *p = &this->props; - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_minLatency), - SPA_PROP_INFO_description, SPA_POD_String("The minimum latency"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->min_latency, 1, INT32_MAX)); - break; - case 1: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_maxLatency), - SPA_PROP_INFO_description, SPA_POD_String("The maximum latency"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->max_latency, 1, INT32_MAX)); - break; default: return 0; } @@ -208,15 +196,7 @@ static int impl_node_enum_params(void *object, int seq, } case SPA_PARAM_Props: { - struct props *p = &this->props; - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_Props, id, - SPA_PROP_minLatency, SPA_POD_Int(p->min_latency), - SPA_PROP_maxLatency, SPA_POD_Int(p->max_latency)); - break; default: return 0; } @@ -237,6 +217,41 @@ static int impl_node_enum_params(void *object, int seq, return 0; } +static int set_timeout(struct impl *this, uint64_t time) +{ + struct itimerspec ts; + ts.it_value.tv_sec = time / SPA_NSEC_PER_SEC; + ts.it_value.tv_nsec = time % SPA_NSEC_PER_SEC; + ts.it_interval.tv_sec = 0; + ts.it_interval.tv_nsec = 0; + return spa_system_timerfd_settime(this->data_system, + this->timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); +} + +static int set_timers(struct impl *this) +{ + struct timespec now; + + spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); + this->next_time = SPA_TIMESPEC_TO_NSEC(&now); + + return set_timeout(this, this->following ? 0 : this->next_time); +} + +static int do_reassign_follower(struct spa_loop *loop, + bool async, + uint32_t seq, + const void *data, + size_t size, + void *user_data) +{ + struct impl *this = user_data; + struct port *port = &this->port; + + spa_bt_decode_buffer_recover(&port->buffer); + return 0; +} + static inline bool is_following(struct impl *this) { return this->position && this->clock && this->position->clock.id != this->clock->id; @@ -269,6 +284,7 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) if (this->started && following != this->following) { spa_log_debug(this->log, "%p: reassign follower %d->%d", this, this->following, following); this->following = following; + spa_loop_invoke(this->data_loop, do_reassign_follower, 0, NULL, 0, true, this); } return 0; @@ -284,10 +300,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param) if (param == NULL) { reset_props(&new_props); } else { - spa_pod_parse_object(param, - SPA_TYPE_OBJECT_Props, NULL, - SPA_PROP_minLatency, SPA_POD_OPT_Int(&new_props.min_latency), - SPA_PROP_maxLatency, SPA_POD_OPT_Int(&new_props.max_latency)); + /* noop */ } changed = (memcmp(&new_props, &this->props, sizeof(struct props)) != 0); @@ -326,8 +339,6 @@ static void reset_buffers(struct port *port) spa_list_init(&port->free); spa_list_init(&port->ready); - port->current_buffer = NULL; - for (i = 0; i < port->n_buffers; i++) { struct buffer *b = &port->buffers[i]; spa_list_append(&port->free, &b->link); @@ -422,116 +433,107 @@ static bool is_zero_packet(uint8_t *data, int size) return true; } -static void preprocess_and_decode_msbc_data(void *userdata, uint8_t *read_data, int size_read) +static uint32_t preprocess_and_decode_msbc_data(void *userdata, uint8_t *read_data, int size_read) { struct impl *this = userdata; struct port *port = &this->port; - struct spa_data *datas = port->current_buffer->buf->datas; + uint32_t decoded = 0; + int i; spa_log_trace(this->log, "handling mSBC data"); - /* check if the packet contains only zeros - if so ignore the packet. - This is necessary, because some kernels insert bogus "all-zero" packets - into the datastream. - See https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/549 */ - if (is_zero_packet(read_data, size_read)) { - return; - } + /* + * Check if the packet contains only zeros - if so ignore the packet. + * This is necessary, because some kernels insert bogus "all-zero" packets + * into the datastream. + * See https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/549 + */ + if (is_zero_packet(read_data, size_read)) + return 0; - int i; for (i = 0; i < size_read; ++i) { + void *buf; + uint32_t avail; + int seq, processed; + size_t written; + msbc_buffer_append_byte(this, read_data[i]); - /* Handle found mSBC packets. - * - * XXX: if there's no space for the decoded audio in - * XXX: the current buffer, we'll drop data. + if (this->msbc_buffer_pos != MSBC_ENCODED_SIZE) + continue; + + /* + * Handle found mSBC packet */ - if (this->msbc_buffer_pos == MSBC_ENCODED_SIZE) { - spa_log_trace(this->log, "Received full mSBC packet, start processing it"); - - if (port->ready_offset + MSBC_DECODED_SIZE <= datas[0].maxsize) { - int seq, processed; - size_t written; - spa_log_trace(this->log, - "Output buffer has space, processing mSBC packet"); - - /* Check sequence number */ - seq = ((this->msbc_buffer[1] >> 4) & 1) | - ((this->msbc_buffer[1] >> 6) & 2); - - spa_log_trace(this->log, "mSBC packet seq=%u", seq); - if (!this->msbc_seq_initialized) { - this->msbc_seq_initialized = true; - this->msbc_seq = seq; - } else if (seq != this->msbc_seq) { - spa_log_info(this->log, - "missing mSBC packet: %u != %u", seq, this->msbc_seq); - this->msbc_seq = seq; - /* TODO: Implement PLC. */ - } - this->msbc_seq = (this->msbc_seq + 1) % 4; - - /* decode frame */ - processed = sbc_decode( - &this->msbc, this->msbc_buffer + 2, MSBC_ENCODED_SIZE - 3, - (uint8_t *)datas[0].data + port->ready_offset, MSBC_DECODED_SIZE, - &written); - - if (processed < 0) { - spa_log_warn(this->log, "sbc_decode failed: %d", processed); - /* TODO: manage errors */ - continue; - } - - port->ready_offset += written; - - } else { - spa_log_warn(this->log, "Output buffer full, dropping mSBC packet"); - } + + buf = spa_bt_decode_buffer_get_write(&port->buffer, &avail); + + /* Check sequence number */ + seq = ((this->msbc_buffer[1] >> 4) & 1) | + ((this->msbc_buffer[1] >> 6) & 2); + + spa_log_trace(this->log, "mSBC packet seq=%u", seq); + if (!this->msbc_seq_initialized) { + this->msbc_seq_initialized = true; + this->msbc_seq = seq; + } else if (seq != this->msbc_seq) { + /* TODO: PLC (too late to insert data now) */ + spa_log_info(this->log, + "missing mSBC packet: %u != %u", seq, this->msbc_seq); + this->msbc_seq = seq; } + + this->msbc_seq = (this->msbc_seq + 1) % 4; + + if (avail < MSBC_DECODED_SIZE) + spa_log_warn(this->log, "Output buffer full, dropping msbc data"); + + /* decode frame */ + processed = sbc_decode( + &this->msbc, this->msbc_buffer + 2, MSBC_ENCODED_SIZE - 3, + buf, avail, &written); + + if (processed < 0) { + spa_log_warn(this->log, "sbc_decode failed: %d", processed); + /* TODO: manage errors */ + continue; + } + + spa_bt_decode_buffer_write_packet(&port->buffer, written); + decoded += written; } + + return decoded; } static int sco_source_cb(void *userdata, uint8_t *read_data, int size_read) { struct impl *this = userdata; struct port *port = &this->port; - struct spa_io_buffers *io = port->io; - struct spa_data *datas; - uint32_t min_data; + uint32_t decoded; + uint64_t dt; if (this->transport == NULL) { spa_log_debug(this->log, "no transport, stop reading"); goto stop; } - /* get buffer */ - if (!port->current_buffer) { - if (spa_list_is_empty(&port->free)) { - spa_log_warn(this->log, "buffer not available"); - return 0; - } - port->current_buffer = spa_list_first(&port->free, struct buffer, link); - spa_list_remove(&port->current_buffer->link); - port->ready_offset = 0; - } - datas = port->current_buffer->buf->datas; - /* update the current pts */ + dt = SPA_TIMESPEC_TO_NSEC(&this->now); spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &this->now); + dt = SPA_TIMESPEC_TO_NSEC(&this->now) - dt; /* handle data read from socket */ - spa_log_trace(this->log, "read socket data %d", size_read); #if 0 hexdump_to_log(this, read_data, size_read); #endif if (this->transport->codec == HFP_AUDIO_CODEC_MSBC) { - preprocess_and_decode_msbc_data(userdata, read_data, size_read); - + decoded = preprocess_and_decode_msbc_data(userdata, read_data, size_read); } else { + uint32_t avail; uint8_t *packet; + if (size_read != 48 && is_zero_packet(read_data, size_read)) { /* Adapter is returning non-standard CVSD stream. For example * Intel 8087:0029 at Firmware revision 0.0 build 191 week 21 2021 @@ -539,62 +541,101 @@ static int sco_source_cb(void *userdata, uint8_t *read_data, int size_read) */ return 0; } - packet = (uint8_t *)datas[0].data + port->ready_offset; - spa_memmove(packet, read_data, size_read); - port->ready_offset += size_read; + + if (size_read % port->frame_size != 0) { + /* Unaligned data: reception or adapter problem. + * Consider the whole packet lost and report. + */ + spa_log_debug(this->log, + "received bad Bluetooth SCO CVSD packet"); + return 0; + } + + packet = spa_bt_decode_buffer_get_write(&port->buffer, &avail); + avail = SPA_MIN(avail, (uint32_t)size_read); + spa_memmove(packet, read_data, avail); + spa_bt_decode_buffer_write_packet(&port->buffer, avail); + + decoded = avail; } - /* send buffer if full */ - min_data = SPA_MIN(this->props.min_latency * port->frame_size, datas[0].maxsize / 2); - if (port->ready_offset >= min_data) { - uint64_t sample_count; + spa_log_trace(this->log, "read socket data size:%d decoded frames:%d dt:%d dms", + size_read, decoded / port->frame_size, + (int)(dt / 100000)); - datas[0].chunk->offset = 0; - datas[0].chunk->size = port->ready_offset; - datas[0].chunk->stride = port->frame_size; + return 0; - sample_count = datas[0].chunk->size / port->frame_size; - spa_list_append(&port->ready, &port->current_buffer->link); - port->current_buffer = NULL; - - if (!this->following && this->clock) { - this->clock->nsec = SPA_TIMESPEC_TO_NSEC(&this->now); - this->clock->duration = sample_count * this->clock->rate.denom / port->current_format.info.raw.rate; - this->clock->position += this->clock->duration; - this->clock->delay = 0; - this->clock->rate_diff = 1.0f; - this->clock->next_nsec = this->clock->nsec; - } +stop: + return 1; +} + +static int setup_matching(struct impl *this) +{ + struct port *port = &this->port; + + if (this->position && port->rate_match) { + port->rate_match->rate = 1 / port->buffer.corr; + + this->matching = this->following; + this->resampling = this->matching || + (port->current_format.info.raw.rate != this->position->clock.rate.denom); + } else { + this->matching = false; + this->resampling = false; } - /* done if there are no buffers ready */ - if (spa_list_is_empty(&port->ready)) - return 0; + if (port->rate_match) + SPA_FLAG_UPDATE(port->rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE, this->matching); - if (this->following) - return 0; + return 0; +} + +static void sco_on_timeout(struct spa_source *source) +{ + struct impl *this = source->data; + struct port *port = &this->port; + uint64_t exp, duration; + uint32_t rate; + struct spa_io_buffers *io = port->io; + uint64_t prev_time, now_time; + + if (this->transport == NULL) + return; + + if (this->started && spa_system_timerfd_read(this->data_system, this->timerfd, &exp) < 0) + spa_log_warn(this->log, "error reading timerfd: %s", strerror(errno)); + + prev_time = this->current_time; + now_time = this->current_time = this->next_time; - /* process the buffer if IO does not have any */ - if (io->status != SPA_STATUS_HAVE_DATA) { - struct buffer *b; + spa_log_trace(this->log, "%p: timer %"PRIu64" %"PRIu64"", this, + now_time, now_time - prev_time); - if (io->buffer_id < port->n_buffers) - recycle_buffer(this, port, io->buffer_id); + if (SPA_LIKELY(this->position)) { + duration = this->position->clock.duration; + rate = this->position->clock.rate.denom; + } else { + duration = 1024; + rate = 48000; + } + + setup_matching(this); - b = spa_list_first(&port->ready, struct buffer, link); - spa_list_remove(&b->link); - b->outstanding = true; + this->next_time = now_time + duration * SPA_NSEC_PER_SEC / port->buffer.corr / rate; - io->buffer_id = b->id; - io->status = SPA_STATUS_HAVE_DATA; + if (SPA_LIKELY(this->clock)) { + this->clock->nsec = now_time; + this->clock->position += duration; + this->clock->duration = duration; + this->clock->rate_diff = port->buffer.corr; + this->clock->next_nsec = this->next_time; } - /* notify ready */ + spa_log_trace(this->log, "%p: %d", this, io->status); + io->status = SPA_STATUS_HAVE_DATA; spa_node_call_ready(&this->callbacks, SPA_STATUS_HAVE_DATA); - return 0; -stop: - return 1; + set_timeout(this, this->next_time); } static int do_add_source(struct spa_loop *loop, @@ -613,6 +654,7 @@ static int do_add_source(struct spa_loop *loop, static int do_start(struct impl *this) { + struct port *port = &this->port; bool do_accept; int res; @@ -636,7 +678,13 @@ static int do_start(struct impl *this) return res; /* Reset the buffers and sample count */ - reset_buffers(&this->port); + reset_buffers(port); + + spa_bt_decode_buffer_clear(&port->buffer); + if ((res = spa_bt_decode_buffer_init(&port->buffer, this->log, + port->frame_size, port->current_format.info.raw.rate, + this->quantum_limit, this->quantum_limit)) < 0) + return res; /* Init mSBC if needed */ if (this->transport->codec == HFP_AUDIO_CODEC_MSBC) { @@ -653,6 +701,17 @@ static int do_start(struct impl *this) goto fail; spa_loop_invoke(this->data_loop, do_add_source, 0, NULL, 0, true, this); + /* Start timer */ + this->timer_source.data = this; + this->timer_source.fd = this->timerfd; + this->timer_source.func = sco_on_timeout; + this->timer_source.mask = SPA_IO_IN; + this->timer_source.rmask = 0; + spa_loop_add_source(this->data_loop, &this->timer_source); + + setup_matching(this); + set_timers(this); + /* Set the started flag */ this->started = true; @@ -671,15 +730,25 @@ static int do_remove_source(struct spa_loop *loop, void *user_data) { struct impl *this = user_data; + struct itimerspec ts; if (this->transport && this->transport->sco_io) spa_bt_sco_io_set_source_cb(this->transport->sco_io, NULL, NULL); + if (this->timer_source.loop) + spa_loop_remove_source(this->data_loop, &this->timer_source); + ts.it_value.tv_sec = 0; + ts.it_value.tv_nsec = 0; + ts.it_interval.tv_sec = 0; + ts.it_interval.tv_nsec = 0; + spa_system_timerfd_settime(this->data_system, this->timerfd, 0, &ts, NULL); + return 0; } static int do_stop(struct impl *this) { + struct port *port = &this->port; int res = 0; if (!this->started) @@ -696,6 +765,8 @@ static int do_stop(struct impl *this) res = spa_bt_transport_release(this->transport); } + spa_bt_decode_buffer_clear(&port->buffer); + return res; } @@ -738,14 +809,12 @@ static void emit_node_info(struct impl *this, bool full) { SPA_KEY_MEDIA_CLASS, "Audio/Source" }, { SPA_KEY_NODE_DRIVER, "true" }, }; - - char latency[64] = "128/8000"; const struct spa_dict_item ag_node_info_items[] = { { SPA_KEY_DEVICE_API, "bluez5" }, { SPA_KEY_MEDIA_CLASS, "Stream/Output/Audio" }, - { SPA_KEY_NODE_LATENCY, latency }, { "media.name", ((this->transport && this->transport->device->name) ? this->transport->device->name : "HSP/HFP") }, + { SPA_KEY_MEDIA_ROLE, "Communication" }, }; bool is_ag = this->transport && (this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY); uint64_t old = full ? this->info.change_mask : 0; @@ -753,9 +822,6 @@ static void emit_node_info(struct impl *this, bool full) if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { - if (this->transport && this->port.have_format) - snprintf(latency, sizeof(latency), "%d/%d", (int)this->props.min_latency, - (int)this->port.current_format.info.raw.rate); this->info.props = is_ag ? &SPA_DICT_INIT_ARRAY(ag_node_info_items) : &SPA_DICT_INIT_ARRAY(hu_node_info_items); @@ -902,11 +968,11 @@ impl_node_port_enum_params(void *object, int seq, param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 8, MAX_BUFFERS), + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( - this->props.max_latency * port->frame_size, - this->props.min_latency * port->frame_size, + this->quantum_limit * port->frame_size, + 16 * port->frame_size, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->frame_size)); break; @@ -976,7 +1042,6 @@ static int clear_buffers(struct impl *this, struct port *port) spa_list_init(&port->ready); port->n_buffers = 0; } - port->current_buffer = NULL; return 0; } @@ -1147,6 +1212,79 @@ static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t return 0; } +static uint32_t get_samples(struct impl *this, uint32_t *duration) +{ + struct port *port = &this->port; + uint32_t samples; + + if (SPA_LIKELY(port->rate_match) && this->resampling) { + samples = port->rate_match->size; + } else { + if (SPA_LIKELY(this->position)) + samples = this->position->clock.duration * port->current_format.info.raw.rate + / this->position->clock.rate.denom; + else + samples = 1024; + } + + if (SPA_LIKELY(this->position)) + *duration = this->position->clock.duration * port->current_format.info.raw.rate + / this->position->clock.rate.denom; + else if (SPA_LIKELY(this->clock)) + *duration = this->clock->duration * port->current_format.info.raw.rate + / this->clock->rate.denom; + else + *duration = 1024 * port->current_format.info.raw.rate / 48000; + + return samples; +} + +static void process_buffering(struct impl *this) +{ + struct port *port = &this->port; + uint32_t duration; + const uint32_t samples = get_samples(this, &duration); + void *buf; + uint32_t avail; + + spa_bt_decode_buffer_process(&port->buffer, samples, duration); + + setup_matching(this); + + buf = spa_bt_decode_buffer_get_read(&port->buffer, &avail); + + /* copy data to buffers */ + if (!spa_list_is_empty(&port->free) && avail > 0) { + struct buffer *buffer; + struct spa_data *datas; + uint32_t data_size; + + data_size = samples * port->frame_size; + + avail = SPA_MIN(avail, data_size); + + spa_bt_decode_buffer_read(&port->buffer, avail); + + buffer = spa_list_first(&port->free, struct buffer, link); + spa_list_remove(&buffer->link); + + spa_log_trace(this->log, "dequeue %d", buffer->id); + + datas = buffer->buf->datas; + + spa_assert(datas[0].maxsize >= data_size); + + datas[0].chunk->offset = 0; + datas[0].chunk->size = avail; + datas[0].chunk->stride = port->frame_size; + memcpy(datas[0].data, buf, avail); + + /* ready buffer if full */ + spa_log_trace(this->log, "queue %d frames:%d", buffer->id, (int)avail / port->frame_size); + spa_list_append(&port->ready, &buffer->link); + } +} + static int impl_node_process(void *object) { struct impl *this = object; @@ -1157,8 +1295,8 @@ static int impl_node_process(void *object) spa_return_val_if_fail(this != NULL, -EINVAL); port = &this->port; - io = port->io; - spa_return_val_if_fail(io != NULL, -EIO); + if ((io = port->io) == NULL) + return -EIO; /* Return if we already have a buffer */ if (io->status == SPA_STATUS_HAVE_DATA) @@ -1170,6 +1308,9 @@ static int impl_node_process(void *object) io->buffer_id = SPA_ID_INVALID; } + /* Produce data */ + process_buffering(this); + /* Return if there are no buffers ready to be processed */ if (spa_list_is_empty(&port->ready)) return SPA_STATUS_OK; @@ -1252,6 +1393,8 @@ static int impl_clear(struct spa_handle *handle) struct impl *this = (struct impl *) handle; if (this->transport) spa_hook_remove(&this->transport_listener); + spa_system_close(this->data_system, this->timerfd); + spa_bt_decode_buffer_clear(&this->port.buffer); return 0; } @@ -1341,6 +1484,10 @@ impl_init(const struct spa_handle_factory *factory, spa_list_init(&port->ready); spa_list_init(&port->free); + this->quantum_limit = 8192; + if (info && (str = spa_dict_lookup(info, "clock.quantum-limit"))) + spa_atou32(str, &this->quantum_limit, 0); + if (info && (str = spa_dict_lookup(info, SPA_KEY_API_BLUEZ5_TRANSPORT))) sscanf(str, "pointer:%p", &this->transport); @@ -1351,6 +1498,9 @@ impl_init(const struct spa_handle_factory *factory, spa_bt_transport_add_listener(this->transport, &this->transport_listener, &transport_events, this); + this->timerfd = spa_system_timerfd_create(this->data_system, + CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); + return 0; } diff --git a/spa/plugins/control/mixer.c b/spa/plugins/control/mixer.c index 29c5376137ab0bdd66d7b48b93ebec30ac3c5c95..81a3bd5191cab114c76fe5b8fb0c8ddac84aabc9 100644 --- a/spa/plugins/control/mixer.c +++ b/spa/plugins/control/mixer.c @@ -587,8 +587,8 @@ static int impl_node_process(void *object) spa_return_val_if_fail(this != NULL, -EINVAL); outport = GET_OUT_PORT(this, 0); - outio = outport->io; - spa_return_val_if_fail(outio != NULL, -EIO); + if ((outio = outport->io) == NULL) + return -EIO; spa_log_trace_fp(this->log, NAME " %p: status %p %d %d", this, outio, outio->status, outio->buffer_id); diff --git a/spa/plugins/ffmpeg/ffmpeg-dec.c b/spa/plugins/ffmpeg/ffmpeg-dec.c index 245e3a8720234bd20d9b7efebaa411810e041277..e9c4c1e3013deb9123da8035808157dc7a33adad 100644 --- a/spa/plugins/ffmpeg/ffmpeg-dec.c +++ b/spa/plugins/ffmpeg/ffmpeg-dec.c @@ -38,6 +38,8 @@ #include <spa/param/video/format.h> #include <spa/pod/filter.h> +#include "ffmpeg.h" + #define IS_VALID_PORT(this,d,id) ((id) == 0) #define GET_IN_PORT(this,p) (&this->in_ports[p]) #define GET_OUT_PORT(this,p) (&this->out_ports[p]) @@ -456,6 +458,20 @@ impl_get_interface(struct spa_handle *handle, const char *type, void **interface return 0; } +static int +impl_clear(struct spa_handle *handle) +{ + spa_return_val_if_fail(handle != NULL, -EINVAL); + + return 0; +} + +size_t +spa_ffmpeg_dec_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) +{ + return sizeof(struct impl); +} + int spa_ffmpeg_dec_init(struct spa_handle *handle, const struct spa_dict *info, @@ -466,6 +482,7 @@ spa_ffmpeg_dec_init(struct spa_handle *handle, struct port *port; handle->get_interface = impl_get_interface; + handle->clear = impl_clear; this = (struct impl *) handle; diff --git a/spa/plugins/ffmpeg/ffmpeg-enc.c b/spa/plugins/ffmpeg/ffmpeg-enc.c index b9f2f277b4a9fecfed617dcbd4ded76ef04cf14e..621818386f4578f3597edc7a4fff447b745b6700 100644 --- a/spa/plugins/ffmpeg/ffmpeg-enc.c +++ b/spa/plugins/ffmpeg/ffmpeg-enc.c @@ -37,6 +37,8 @@ #include <spa/param/video/format-utils.h> #include <spa/pod/filter.h> +#include "ffmpeg.h" + #define IS_VALID_PORT(this,d,id) ((id) == 0) #define GET_IN_PORT(this,p) (&this->in_ports[p]) #define GET_OUT_PORT(this,p) (&this->out_ports[p]) @@ -435,6 +437,20 @@ impl_get_interface(struct spa_handle *handle, const char *type, void **interface return 0; } +static int +impl_clear(struct spa_handle *handle) +{ + spa_return_val_if_fail(handle != NULL, -EINVAL); + + return 0; +} + +size_t +spa_ffmpeg_enc_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) +{ + return sizeof(struct impl); +} + int spa_ffmpeg_enc_init(struct spa_handle *handle, const struct spa_dict *info, @@ -444,6 +460,7 @@ spa_ffmpeg_enc_init(struct spa_handle *handle, struct port *port; handle->get_interface = impl_get_interface; + handle->clear = impl_clear; this = (struct impl *) handle; diff --git a/spa/plugins/ffmpeg/ffmpeg.c b/spa/plugins/ffmpeg/ffmpeg.c index 39ec93e7113ef7cd0463086713da8dc94a8a9acb..9546dae2bf50e7621d96ee60d32029725aac8d6f 100644 --- a/spa/plugins/ffmpeg/ffmpeg.c +++ b/spa/plugins/ffmpeg/ffmpeg.c @@ -30,10 +30,7 @@ #include <libavcodec/avcodec.h> -int spa_ffmpeg_dec_init(struct spa_handle *handle, const struct spa_dict *info, - const struct spa_support *support, uint32_t n_support); -int spa_ffmpeg_enc_init(struct spa_handle *handle, const struct spa_dict *info, - const struct spa_support *support, uint32_t n_support); +#include "ffmpeg.h" static int ffmpeg_dec_init(const struct spa_handle_factory *factory, @@ -130,8 +127,12 @@ static const AVCodec *find_codec_by_index(uint32_t index) SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { - static struct spa_handle_factory f; static char name[128]; + static struct spa_handle_factory f = { + SPA_VERSION_HANDLE_FACTORY, + .name = name, + .enum_interface_info = ffmpeg_enum_interface_info, + }; #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100) avcodec_register_all(); @@ -144,16 +145,14 @@ int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t if (av_codec_is_encoder(c)) { snprintf(name, sizeof(name), "encoder.%s", c->name); + f.get_size = spa_ffmpeg_enc_get_size; f.init = ffmpeg_enc_init; } else { snprintf(name, sizeof(name), "decoder.%s", c->name); + f.get_size = spa_ffmpeg_dec_get_size; f.init = ffmpeg_dec_init; } - f.name = name; - f.info = NULL; - f.enum_interface_info = ffmpeg_enum_interface_info; - *factory = &f; (*index)++; diff --git a/spa/plugins/ffmpeg/ffmpeg.h b/spa/plugins/ffmpeg/ffmpeg.h new file mode 100644 index 0000000000000000000000000000000000000000..19078d813e0a44117bce7b6db35125095b5a5718 --- /dev/null +++ b/spa/plugins/ffmpeg/ffmpeg.h @@ -0,0 +1,20 @@ +#ifndef SPA_FFMPEG_H +#define SPA_FFMPEG_H + +#include <stdint.h> +#include <stddef.h> + +struct spa_dict; +struct spa_handle; +struct spa_support; +struct spa_handle_factory; + +int spa_ffmpeg_dec_init(struct spa_handle *handle, const struct spa_dict *info, + const struct spa_support *support, uint32_t n_support); +int spa_ffmpeg_enc_init(struct spa_handle *handle, const struct spa_dict *info, + const struct spa_support *support, uint32_t n_support); + +size_t spa_ffmpeg_dec_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params); +size_t spa_ffmpeg_enc_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params); + +#endif diff --git a/spa/plugins/libcamera/libcamera-device.cpp b/spa/plugins/libcamera/libcamera-device.cpp index 7f006a0cc04e86a050b6f2561d3b47fe6e32200a..f2f10c01785fb4b0267c2a4065392d218dae86a5 100644 --- a/spa/plugins/libcamera/libcamera-device.cpp +++ b/spa/plugins/libcamera/libcamera-device.cpp @@ -78,35 +78,32 @@ struct impl { std::shared_ptr<Camera> camera; }; -std::string cameraModel(const Camera *camera) +static std::string cameraModel(const Camera *camera) { const ControlList &props = camera->properties(); - std::string name; - if (props.contains(properties::Model)) - name = props.get(properties::Model); - else - name = camera->id(); - return name; + + if (auto model = props.get(properties::Model)) + return std::move(model.value()); + + return camera->id(); } -std::string cameraLoc(const Camera *camera) +static const char *cameraLoc(const Camera *camera) { const ControlList &props = camera->properties(); - std::string location; - if (props.contains(properties::Location)) { - switch (props.get(properties::Location)) { + + if (auto location = props.get(properties::Location)) { + switch (location.value()) { case properties::CameraLocationFront: - location = "front"; - break; + return "front"; case properties::CameraLocationBack: - location = "back"; - break; + return "back"; case properties::CameraLocationExternal: - location = "external"; - break; + return "external"; } } - return location; + + return nullptr; } static int emit_info(struct impl *impl, bool full) @@ -116,7 +113,7 @@ static int emit_info(struct impl *impl, bool full) uint32_t n_items = 0; struct spa_device_info info; struct spa_param_info params[2]; - char path[256], location[10], model[256], name[256]; + char path[256], model[256], name[256]; info = SPA_DEVICE_INFO_INIT(); @@ -127,9 +124,11 @@ static int emit_info(struct impl *impl, bool full) ADD_ITEM(SPA_KEY_OBJECT_PATH, path); ADD_ITEM(SPA_KEY_DEVICE_API, "libcamera"); ADD_ITEM(SPA_KEY_MEDIA_CLASS, "Video/Device"); - ADD_ITEM(SPA_KEY_API_LIBCAMERA_PATH, (char *)impl->props.device); - snprintf(location, sizeof(location), "%s", cameraLoc(impl->camera.get()).c_str()); - ADD_ITEM(SPA_KEY_API_LIBCAMERA_LOCATION, location); + ADD_ITEM(SPA_KEY_API_LIBCAMERA_PATH, impl->props.device); + + if (auto location = cameraLoc(impl->camera.get())) + ADD_ITEM(SPA_KEY_API_LIBCAMERA_LOCATION, location); + snprintf(model, sizeof(model), "%s", cameraModel(impl->camera.get()).c_str()); ADD_ITEM(SPA_KEY_DEVICE_PRODUCT_NAME, model); ADD_ITEM(SPA_KEY_DEVICE_DESCRIPTION, model); diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 948b4413654ce76a6c434e4aee2fe022298a4318..0d32b745b72c399c423169e8af553847d1cfcbd7 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -823,8 +823,8 @@ static int impl_node_process(void *object) spa_return_val_if_fail(impl != NULL, -EINVAL); port = GET_OUT_PORT(impl, 0); - io = port->io; - spa_return_val_if_fail(io != NULL, -EIO); + if ((io = port->io) == NULL) + return -EIO; if (port->control) process_control(impl, &port->control->sequence); diff --git a/spa/plugins/meson.build b/spa/plugins/meson.build index fcf007aa9efae4ec5d9193431676bf7fe755f541..6df9381701bacec30f1c895cf9d2d6192f9ac91f 100644 --- a/spa/plugins/meson.build +++ b/spa/plugins/meson.build @@ -1,6 +1,9 @@ if alsa_dep.found() subdir('alsa') endif +if get_option('avb').require(host_machine.system() == 'linux', error_message: 'AVB support is only available on Linux').allowed() + subdir('avb') +endif if get_option('audioconvert').allowed() subdir('audioconvert') endif @@ -52,4 +55,4 @@ if libcamera_dep.found() subdir('libcamera') endif -subdir('aec') \ No newline at end of file +subdir('aec') diff --git a/spa/plugins/support/cpu.c b/spa/plugins/support/cpu.c index 01cff4854d8a27776ac372d39345c5b009a4e6f7..67440af16f7d0868f7732bb950b22a25b3c6fa1e 100644 --- a/spa/plugins/support/cpu.c +++ b/spa/plugins/support/cpu.c @@ -30,7 +30,7 @@ #include <sched.h> #include <fcntl.h> -#ifdef __FreeBSD__ +#if defined(__FreeBSD__) || defined(__MidnightBSD__) #include <sys/sysctl.h> #endif diff --git a/spa/plugins/support/logger.c b/spa/plugins/support/logger.c index 4d308fa908bf93c32b8ef0c52aa6d1ba7f3c363b..3b854c65eff17f1da02401bf2ae78d8afedf3c2a 100644 --- a/spa/plugins/support/logger.c +++ b/spa/plugins/support/logger.c @@ -42,7 +42,7 @@ #include "log-patterns.h" -#ifdef __FreeBSD__ +#if defined(__FreeBSD__) || defined(__MidnightBSD__) #define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC #endif diff --git a/spa/plugins/support/loop.c b/spa/plugins/support/loop.c index e02cbed87958ad5b9638cd66783a0f3e1b243b04..758ce59db192332bf7d7a475f3f5fd38e265b166 100644 --- a/spa/plugins/support/loop.c +++ b/spa/plugins/support/loop.c @@ -350,6 +350,7 @@ static void loop_leave(void *object) if (--impl->enter_count == 0) { impl->thread = 0; + flush_items(impl); impl->polling = false; } } diff --git a/spa/plugins/support/null-audio-sink.c b/spa/plugins/support/null-audio-sink.c index 8a90d2ae540933732458e278a629429fbfae53f0..abee4bedd5f46c523e4e93f0523152acd031e0cc 100644 --- a/spa/plugins/support/null-audio-sink.c +++ b/spa/plugins/support/null-audio-sink.c @@ -42,6 +42,7 @@ #include <spa/node/keys.h> #include <spa/param/audio/format-utils.h> #include <spa/debug/types.h> +#include <spa/debug/mem.h> #include <spa/param/audio/type-info.h> #include <spa/param/param.h> #include <spa/pod/filter.h> @@ -57,6 +58,7 @@ struct props { uint32_t n_pos; uint32_t pos[SPA_AUDIO_MAX_CHANNELS]; char clock_name[64]; + unsigned int debug:1; }; static void reset_props(struct props *props) @@ -65,6 +67,7 @@ static void reset_props(struct props *props) props->rate = 0; props->n_pos = 0; strncpy(props->clock_name, DEFAULT_CLOCK_NAME, sizeof(props->clock_name)); + props->debug = false; } #define DEFAULT_CHANNELS 2 @@ -734,9 +737,8 @@ static int impl_node_process(void *object) spa_return_val_if_fail(this != NULL, -EINVAL); port = &this->port; - - io = port->io; - spa_return_val_if_fail(io != NULL, -EIO); + if ((io = port->io) == NULL) + return -EIO; if (io->status != SPA_STATUS_HAVE_DATA) return io->status; @@ -744,6 +746,20 @@ static int impl_node_process(void *object) io->status = -EINVAL; return io->status; } + if (this->props.debug) { + struct buffer *b; + uint32_t i; + + b = &port->buffers[io->buffer_id]; + for (i = 0; i < b->outbuf->n_datas; i++) { + uint32_t offs, size; + struct spa_data *d = b->outbuf->datas; + + offs = SPA_MIN(d->chunk->offset, d->maxsize); + size = SPA_MIN(d->maxsize - offs, d->chunk->size); + spa_debug_mem(i, SPA_PTROFF(d[i].data, offs, void), SPA_MIN(16u, size));; + } + } io->status = SPA_STATUS_OK; return SPA_STATUS_HAVE_DATA; } diff --git a/spa/plugins/test/fakesink.c b/spa/plugins/test/fakesink.c index fee62334b84ffa77ad49a1ad7271728260949bbb..104f27eb9da639e30c1d4f57ae7aaf6e277e4695 100644 --- a/spa/plugins/test/fakesink.c +++ b/spa/plugins/test/fakesink.c @@ -639,9 +639,8 @@ static int impl_node_process(void *object) spa_return_val_if_fail(this != NULL, -EINVAL); port = &this->port; - - io = port->io; - spa_return_val_if_fail(io != NULL, -EIO); + if ((io = port->io) == NULL) + return -EIO; if (io->status == SPA_STATUS_HAVE_DATA && io->buffer_id < port->n_buffers) { struct buffer *b = &port->buffers[io->buffer_id]; diff --git a/spa/plugins/test/fakesrc.c b/spa/plugins/test/fakesrc.c index fa7bee7e0a866f113fe485087313072998ec8cc4..6dde68455d1c56d68f7ed7e482a3c87f808f9b9f 100644 --- a/spa/plugins/test/fakesrc.c +++ b/spa/plugins/test/fakesrc.c @@ -680,8 +680,8 @@ static int impl_node_process(void *object) spa_return_val_if_fail(this != NULL, -EINVAL); port = &this->port; - io = port->io; - spa_return_val_if_fail(io != NULL, -EIO); + if ((io = port->io) == NULL) + return -EIO; if (io->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; diff --git a/spa/plugins/v4l2/v4l2-source.c b/spa/plugins/v4l2/v4l2-source.c index e49568b1bd314a67dd6f006f2fb8587d0370a56a..ac83032a8b515f1fa03fd59efcf8d781b49d578b 100644 --- a/spa/plugins/v4l2/v4l2-source.c +++ b/spa/plugins/v4l2/v4l2-source.c @@ -879,8 +879,8 @@ static int impl_node_process(void *object) spa_return_val_if_fail(this != NULL, -EINVAL); port = GET_OUT_PORT(this, 0); - io = port->io; - spa_return_val_if_fail(io != NULL, -EIO); + if ((io = port->io) == NULL) + return -EIO; if (port->control) process_control(this, &port->control->sequence); diff --git a/spa/plugins/v4l2/v4l2-udev.c b/spa/plugins/v4l2/v4l2-udev.c index ff5433e08c8409cf82ef7cc7afd2c55c5ebe4329..df1b1f8906b85a9a5a8b2ef976a7ff9e4016058a 100644 --- a/spa/plugins/v4l2/v4l2-udev.c +++ b/spa/plugins/v4l2/v4l2-udev.c @@ -279,11 +279,10 @@ static int emit_object_info(struct impl *this, struct device *device) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_SUBSYSTEM, str); } if ((str = udev_device_get_property_value(dev, "ID_VENDOR_ID")) && *str) { - char *dec = alloca(6); /* 65535 is max */ int32_t val; - if (spa_atoi32(str, &val, 16)) { - snprintf(dec, 6, "%d", val); + char *dec = alloca(12); /* 0xffff is max */ + snprintf(dec, 12, "0x%04x", val); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_ID, dec); } } @@ -302,11 +301,10 @@ static int emit_object_info(struct impl *this, struct device *device) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_NAME, str); } if ((str = udev_device_get_property_value(dev, "ID_MODEL_ID")) && *str) { - char *dec = alloca(6); /* 65535 is max */ int32_t val; - if (spa_atoi32(str, &val, 16)) { - snprintf(dec, 6, "%d", val); + char *dec = alloca(12); /* 0xffff is max */ + snprintf(dec, 12, "0x%04x", val); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PRODUCT_ID, dec); } } diff --git a/spa/plugins/v4l2/v4l2-utils.c b/spa/plugins/v4l2/v4l2-utils.c index 80fa2bd8769efdc0c179b6d9df5f7b25759d9358..14e967f731b63fd9de6341da0bceb3c4be8ed0e1 100644 --- a/spa/plugins/v4l2/v4l2-utils.c +++ b/spa/plugins/v4l2/v4l2-utils.c @@ -1107,6 +1107,8 @@ spa_v4l2_enum_controls(struct impl *this, int seq, spa_log_debug(this->log, "test control %08x", queryctrl.id); if (query_ext_ctrl_ioctl(port, &queryctrl) != 0) { + if (errno == ENOTTY) + goto enum_end; if (errno == EINVAL) { if (queryctrl.id != next_fl) goto enum_end; @@ -1123,6 +1125,7 @@ spa_v4l2_enum_controls(struct impl *this, int seq, } res = -errno; spa_log_error(this->log, "'%s' VIDIOC_QUERYCTRL: %m", this->props.device); + spa_v4l2_close(dev); return res; } if (result.next & next_fl) diff --git a/spa/plugins/videotestsrc/videotestsrc.c b/spa/plugins/videotestsrc/videotestsrc.c index 2c7e9e1aee6986b1eb0ef0d1ac5ec39253605f48..97742975963b66e7d1a24e074d9e06cf17396b76 100644 --- a/spa/plugins/videotestsrc/videotestsrc.c +++ b/spa/plugins/videotestsrc/videotestsrc.c @@ -787,8 +787,8 @@ static int impl_node_process(void *object) spa_return_val_if_fail(this != NULL, -EINVAL); port = &this->port; - io = port->io; - spa_return_val_if_fail(io != NULL, -EIO); + if ((io = port->io) == NULL) + return -EIO; if (io->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; diff --git a/spa/plugins/volume/volume.c b/spa/plugins/volume/volume.c index fb49ab74f370af9e8daa3377219cb1bbd6882b32..40556a33c6ffd1e4154cb6944d0501b38ba8a37a 100644 --- a/spa/plugins/volume/volume.c +++ b/spa/plugins/volume/volume.c @@ -679,8 +679,8 @@ static int impl_node_process(void *object) spa_return_val_if_fail(this != NULL, -EINVAL); out_port = GET_OUT_PORT(this, 0); - output = out_port->io; - spa_return_val_if_fail(output != NULL, -EIO); + if ((output = out_port->io) == NULL) + return -EIO; if (output->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; @@ -692,8 +692,8 @@ static int impl_node_process(void *object) } in_port = GET_IN_PORT(this, 0); - input = in_port->io; - spa_return_val_if_fail(input != NULL, -EIO); + if ((input = in_port->io) == NULL) + return -EIO; if (input->status != SPA_STATUS_HAVE_DATA) return SPA_STATUS_NEED_DATA; diff --git a/spa/plugins/vulkan/vulkan-compute-filter.c b/spa/plugins/vulkan/vulkan-compute-filter.c index fbe9e769e6ff41e23fb55f6df1414136cceabf2b..66157075a11789718a3dc7c628c2c3bfeae5fecf 100644 --- a/spa/plugins/vulkan/vulkan-compute-filter.c +++ b/spa/plugins/vulkan/vulkan-compute-filter.c @@ -578,8 +578,8 @@ static int impl_node_process(void *object) spa_return_val_if_fail(this != NULL, -EINVAL); inport = &this->port[SPA_DIRECTION_INPUT]; - inio = inport->io; - spa_return_val_if_fail(inio != NULL, -EIO); + if ((inio = inport->io) == NULL) + return -EIO; if (inio->status != SPA_STATUS_HAVE_DATA) return inio->status; @@ -590,8 +590,8 @@ static int impl_node_process(void *object) } outport = &this->port[SPA_DIRECTION_OUTPUT]; - outio = outport->io; - spa_return_val_if_fail(outio != NULL, -EIO); + if ((outio = outport->io) == NULL) + return -EIO; if (outio->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; diff --git a/spa/plugins/vulkan/vulkan-compute-source.c b/spa/plugins/vulkan/vulkan-compute-source.c index 3f26c9b24b3a3f272248cc591770317846eba2b5..8cce5489f6475570203f2497dfaa751866c8af16 100644 --- a/spa/plugins/vulkan/vulkan-compute-source.c +++ b/spa/plugins/vulkan/vulkan-compute-source.c @@ -802,8 +802,8 @@ static int impl_node_process(void *object) spa_return_val_if_fail(this != NULL, -EINVAL); port = &this->port; - io = port->io; - spa_return_val_if_fail(io != NULL, -EIO); + if ((io = port->io) == NULL) + return -EIO; if (io->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; diff --git a/spa/plugins/vulkan/vulkan-utils.c b/spa/plugins/vulkan/vulkan-utils.c index ff9a51eb36bcfb78a39caaf4ae9070890bf839f4..ae3337b776d3698c1943a31557651b5ab9788cad 100644 --- a/spa/plugins/vulkan/vulkan-utils.c +++ b/spa/plugins/vulkan/vulkan-utils.c @@ -6,7 +6,7 @@ #include <sys/mman.h> #include <fcntl.h> #include <string.h> -#ifndef __FreeBSD__ +#if !defined(__FreeBSD__) && !defined(__MidnightBSD__) #include <alloca.h> #endif #include <errno.h> diff --git a/spa/tests/stress-ringbuffer.c b/spa/tests/stress-ringbuffer.c index 2c3f38af2eb062b54c4411a0b4ebe159b7b04037..6a7e98fb53c40cafc871d1e846e97ce2bf4471ac 100644 --- a/spa/tests/stress-ringbuffer.c +++ b/spa/tests/stress-ringbuffer.c @@ -11,10 +11,10 @@ #define ARRAY_SIZE 63 #define MAX_VALUE 0x10000 -#ifdef __FreeBSD__ +#if defined(__FreeBSD__) || defined(__MidnightBSD__) #include <sys/param.h> #if (__FreeBSD_version >= 1400000 && __FreeBSD_version < 1400043) \ - || (__FreeBSD_version < 1300523) + || (__FreeBSD_version < 1300523) || defined(__MidnightBSD__) static int sched_getcpu(void) { return -1; }; #endif #endif diff --git a/spa/tools/spa-inspect.c b/spa/tools/spa-inspect.c index 87dd9ff969777e1a422168fdd2e0b35f1ae07540..4e7910401eb6987ebb31fd3d11b7688e30dba4f6 100644 --- a/spa/tools/spa-inspect.c +++ b/spa/tools/spa-inspect.c @@ -232,7 +232,7 @@ static void inspect_factory(struct data *data, const struct spa_handle_factory * if ((res = spa_handle_factory_init(factory, handle, NULL, data->support, data->n_support)) < 0) { printf("can't make factory instance: %d\n", res); - return; + goto out; } printf("factory instance:\n"); @@ -256,6 +256,12 @@ static void inspect_factory(struct data *data, const struct spa_handle_factory * else printf("skipping unknown interface\n"); } + + if ((res = spa_handle_clear(handle)) < 0) + printf("failed to clear handle: %s\n", spa_strerror(res)); + +out: + free(handle); } static const struct spa_loop_methods impl_loop = { diff --git a/src/daemon/client-rt.conf.in b/src/daemon/client-rt.conf.in index 30bd11513d9b0998a88bb3f94c35dae3a6d5a120..f62b1e2281054af0e073362ff8ab4789645130ac 100644 --- a/src/daemon/client-rt.conf.in +++ b/src/daemon/client-rt.conf.in @@ -82,7 +82,7 @@ stream.properties = { #node.autoconnect = true #resample.quality = 4 #channelmix.normalize = false - #channelmix.mix-lfe = true + #channelmix.mix-lfe = false #channelmix.upmix = true #channelmix.upmix-method = psd # none, simple #channelmix.lfe-cutoff = 150 @@ -90,4 +90,5 @@ stream.properties = { #channelmix.rear-delay = 12.0 #channelmix.stereo-widen = 0.0 #channelmix.hilbert-taps = 0 + #dither.noise = 0 } diff --git a/src/daemon/client.conf.in b/src/daemon/client.conf.in index ffe23c82768a1a9f44bfebaff6c20e887f4f5303..3931149f7e842bcaf6f7af8556eeafd1cadbee79 100644 --- a/src/daemon/client.conf.in +++ b/src/daemon/client.conf.in @@ -7,6 +7,7 @@ # @PIPEWIRE_CONFIG_DIR@/client.conf.d/ for system-wide changes or in # ~/.config/pipewire/client.conf.d/ for local changes. # + context.properties = { ## Configure properties in the system. #mem.warn-mlock = false @@ -80,4 +81,5 @@ stream.properties = { #channelmix.rear-delay = 12.0 #channelmix.stereo-widen = 0.0 #channelmix.hilbert-taps = 0 + #dither.noise = 0 } diff --git a/src/daemon/filter-chain.conf.in b/src/daemon/filter-chain.conf.in new file mode 100644 index 0000000000000000000000000000000000000000..28d2a5ad3f7f7c719db2f832faff03512c3fb331 --- /dev/null +++ b/src/daemon/filter-chain.conf.in @@ -0,0 +1,62 @@ +# Filter-chain config file for PipeWire version @VERSION@ # +# +# This is a base config file for running filters. +# +# Place filter fragments in @PIPEWIRE_CONFIG_DIR@/filter-chain.conf.d/ +# for system-wide changes or in ~/.config/pipewire/filter-chain.conf.d/ +# for local changes. +# +# Run the filters with pipewire -c filter-chain.conf +# + +context.properties = { + ## Configure properties in the system. + #mem.warn-mlock = false + #mem.allow-mlock = true + #mem.mlock-all = false + log.level = 0 +} + +context.spa-libs = { + #<factory-name regex> = <library-name> + # + # Used to find spa factory names. It maps an spa factory name + # regular expression to a library name that should contain + # that factory. + # + audio.convert.* = audioconvert/libspa-audioconvert + support.* = support/libspa-support +} + +context.modules = [ + #{ name = <module-name> + # [ args = { <key> = <value> ... } ] + # [ flags = [ [ ifexists ] [ nofail ] ] + #} + # + # Loads a module with the given parameters. + # If ifexists is given, the module is ignored when it is not found. + # If nofail is given, module initialization failures are ignored. + # + # Uses realtime scheduling to boost the audio thread priorities + { name = libpipewire-module-rt + args = { + #rt.prio = 88 + #rt.time.soft = -1 + #rt.time.hard = -1 + } + flags = [ ifexists nofail ] + } + + # The native communication protocol. + { name = libpipewire-module-protocol-native } + + # Allows creating nodes that run in the context of the + # client. Is used by all clients that want to provide + # data to PipeWire. + { name = libpipewire-module-client-node } + + # Makes a factory for wrapping nodes in an adapter with a + # converter and resampler. + { name = libpipewire-module-adapter } +] diff --git a/src/daemon/filter-chain/demonic.conf b/src/daemon/filter-chain/demonic.conf index b2953dc8174259aac214895a3cbaa8bcd3462fda..df52dbafb8ec4ee51f2eaeff2f603fff16ff5402 100644 --- a/src/daemon/filter-chain/demonic.conf +++ b/src/daemon/filter-chain/demonic.conf @@ -1,46 +1,9 @@ # filter-chain example config file for PipeWire version @VERSION@ # -context.properties = { - ## Configure properties in the system. - #mem.warn-mlock = false - #mem.allow-mlock = true - #mem.mlock-all = false - log.level = 0 -} - -context.spa-libs = { - #<factory-name regex> = <library-name> - # - # Used to find spa factory names. It maps an spa factory name - # regular expression to a library name that should contain - # that factory. - # - audio.convert.* = audioconvert/libspa-audioconvert - support.* = support/libspa-support -} - +# +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ +# context.modules = [ - # Uses realtime scheduling to boost the audio thread priorities - { name = libpipewire-module-rt - args = { - #rt.prio = 88 - #rt.time.soft = -1 - #rt.time.hard = -1 - } - flags = [ ifexists nofail ] - } - - # The native communication protocol. - { name = libpipewire-module-protocol-native } - - # Allows creating nodes that run in the context of the - # client. Is used by all clients that want to provide - # data to PipeWire. - { name = libpipewire-module-client-node } - - # Makes a factory for wrapping nodes in an adapter with a - # converter and resampler. - { name = libpipewire-module-adapter } - { name = libpipewire-module-filter-chain args = { #audio.format = F32 diff --git a/src/daemon/filter-chain/meson.build b/src/daemon/filter-chain/meson.build index c0540c1ff625b5b9a7265eb9affe9d193bbcc47d..91d4458687333ee1a118850a8c6f7ffa8b36ec0f 100644 --- a/src/daemon/filter-chain/meson.build +++ b/src/daemon/filter-chain/meson.build @@ -1,6 +1,8 @@ conf_files = [ [ 'demonic.conf', 'demonic.conf' ], - [ 'duplicate-FL.conf', 'duplicate-FL.conf' ], + [ 'source-duplicate-FL.conf', 'source-duplicate-FL.conf' ], + [ 'sink-mix-FL-FR.conf', 'sink-mix-FL-FR.conf' ], + [ 'sink-make-LFE.conf', 'sink-make-LFE.conf' ], [ 'sink-virtual-surround-5.1-kemar.conf', 'sink-virtual-surround-5.1-kemar.conf' ], [ 'sink-virtual-surround-7.1-hesuvi.conf', 'sink-virtual-surround-7.1-hesuvi.conf' ], [ 'sink-dolby-surround.conf', 'sink-dolby-surround.conf' ], diff --git a/src/daemon/filter-chain/sink-dolby-surround.conf b/src/daemon/filter-chain/sink-dolby-surround.conf index 78dd1572265de77f44bcb029513bc7de03e0f8d2..a53009f7cf56d17df9bcc6b6c285bb0e785be9ec 100644 --- a/src/daemon/filter-chain/sink-dolby-surround.conf +++ b/src/daemon/filter-chain/sink-dolby-surround.conf @@ -1,29 +1,9 @@ # Dolby Surround encoder sink # -# start with pipewire -c filter-chain/sink-dolby-surround.conf +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ # -context.properties = { - log.level = 0 -} - -context.spa-libs = { - audio.convert.* = audioconvert/libspa-audioconvert - support.* = support/libspa-support -} - context.modules = [ - { name = libpipewire-module-rt - args = { - #rt.prio = 88 - #rt.time.soft = -1 - #rt.time.hard = -1 - } - flags = [ ifexists nofail ] - } - { name = libpipewire-module-protocol-native } - { name = libpipewire-module-client-node } - { name = libpipewire-module-adapter } - { name = libpipewire-module-filter-chain args = { node.description = "Dolby Surround Sink" diff --git a/src/daemon/filter-chain/sink-eq6.conf b/src/daemon/filter-chain/sink-eq6.conf index cd671883a2c68131c81dc36a5ba667c77d811ac5..4cdf21b8964263870293ca446c624c9562cdd927 100644 --- a/src/daemon/filter-chain/sink-eq6.conf +++ b/src/daemon/filter-chain/sink-eq6.conf @@ -1,29 +1,9 @@ # 6 band sink equalizer # -# start with pipewire -c filter-chain/sink-eq6.conf +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ # -context.properties = { - log.level = 0 -} - -context.spa-libs = { - audio.convert.* = audioconvert/libspa-audioconvert - support.* = support/libspa-support -} - context.modules = [ - { name = libpipewire-module-rt - args = { - #rt.prio = 88 - #rt.time.soft = -1 - #rt.time.hard = -1 - } - flags = [ ifexists nofail ] - } - { name = libpipewire-module-protocol-native } - { name = libpipewire-module-client-node } - { name = libpipewire-module-adapter } - { name = libpipewire-module-filter-chain args = { node.description = "Equalizer Sink" diff --git a/src/daemon/filter-chain/sink-make-LFE.conf b/src/daemon/filter-chain/sink-make-LFE.conf new file mode 100644 index 0000000000000000000000000000000000000000..4ab770e38ecf8293a1aa1e79d2629f690e26e371 --- /dev/null +++ b/src/daemon/filter-chain/sink-make-LFE.conf @@ -0,0 +1,56 @@ +# An example filter chain that makes a stereo sink that mixes +# the FL and FR channels to FL, FR, LFE +# +# Copy this file into a conf.d/ directory +# +context.modules = [ + { name = libpipewire-module-filter-chain + args = { + node.description = "LFE example" + media.name = "LFE example" + filter.graph = { + nodes = [ + { name = copyIL type = builtin label = copy } + { name = copyOL type = builtin label = copy } + { name = copyIR type = builtin label = copy } + { name = copyOR type = builtin label = copy } + { + name = mix + type = builtin + label = mixer + control = { + "Gain 1" = 0.5 + "Gain 2" = 0.5 + } + } + { + type = builtin + name = lpLFE + label = bq_lowpass + control = { "Freq" = 150.0 } + } + ] + links = [ + { output = "copyIL:Out" input = "copyOL:In" } + { output = "copyIR:Out" input = "copyOR:In" } + { output = "copyIL:Out" input = "mix:In 1" } + { output = "copyIR:Out" input = "mix:In 2" } + { output = "mix:Out" input = "lpLFE:In" } + ] + inputs = [ "copyIL:In" "copyIR:In" ] + outputs = [ "copyOL:Out" "copyOR:Out" "lpLFE:Out"] + } + capture.props = { + node.name = "input_lfe" + audio.position = [ FL FR ] + media.class = "Audio/Sink" + } + playback.props = { + node.name = "output_lfe" + audio.position = [ FL FR LFE ] + stream.dont-remix = true + node.passive = true + } + } + } +] diff --git a/src/daemon/filter-chain/sink-matrix-spatialiser.conf b/src/daemon/filter-chain/sink-matrix-spatialiser.conf index 5519d3a672be2ddfba10e994adc1ea7f0f7b4076..b39890cb7dd53879214e5576b1f3fca979b974b2 100644 --- a/src/daemon/filter-chain/sink-matrix-spatialiser.conf +++ b/src/daemon/filter-chain/sink-matrix-spatialiser.conf @@ -1,30 +1,12 @@ # Matrix Spatialiser sink # -# start with pipewire -c filter-chain/sink-matrix-spatialiser.conf +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ +# # ( Jean-Philippe Guillemin <hyp3ri0n@sfr.fr> ) - -context.properties = { - log.level = 0 -} - -context.spa-libs = { - audio.convert.* = audioconvert/libspa-audioconvert - support.* = support/libspa-support -} +# context.modules = [ - { name = libpipewire-module-rt - args = { - #rt.prio = 88 - #rt.time.soft = -1 - #rt.time.hard = -1 - } - flags = [ ifexists nofail ] - } - { name = libpipewire-module-protocol-native } - { name = libpipewire-module-client-node } - { name = libpipewire-module-adapter } - { name = libpipewire-module-filter-chain args = { node.description = "Matrix Spatialiser" diff --git a/src/daemon/filter-chain/sink-mix-FL-FR.conf b/src/daemon/filter-chain/sink-mix-FL-FR.conf new file mode 100644 index 0000000000000000000000000000000000000000..8288fad51858c93ca9e86289983f40183fe4f074 --- /dev/null +++ b/src/daemon/filter-chain/sink-mix-FL-FR.conf @@ -0,0 +1,40 @@ +# An example filter chain that makes a stereo sink that mixes +# the FL and FR channels to a single FL channel +# +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ +# +context.modules = [ + { name = libpipewire-module-filter-chain + args = { + node.description = "Mix example" + media.name = "Mix example" + filter.graph = { + nodes = [ + { + name = mix + type = builtin + label = mixer + control = { + "Gain 1" = 0.5 + "Gain 2" = 0.5 + } + } + ] + inputs = [ "mix:In 1" "mix:In 2" ] + outputs = [ "mix:Out" ] + } + capture.props = { + node.name = "mix_input.mix-FL-FR-to-FL" + audio.position = [ FL FR ] + media.class = "Audio/Sink" + } + playback.props = { + node.name = "mix_output.mix-FL-FR-to-FL" + audio.position = [ FL ] + stream.dont-remix = true + node.passive = true + } + } + } +] diff --git a/src/daemon/filter-chain/sink-virtual-surround-5.1-kemar.conf b/src/daemon/filter-chain/sink-virtual-surround-5.1-kemar.conf index fa12ca4a542e98188c0bed5fb5ab66c8ee0163bb..ee8929b061292ac08841ff989cd907eea89fb802 100644 --- a/src/daemon/filter-chain/sink-virtual-surround-5.1-kemar.conf +++ b/src/daemon/filter-chain/sink-virtual-surround-5.1-kemar.conf @@ -1,29 +1,9 @@ # Convolver sink # -# start with pipewire -c filter-chain/sink-virtual-surround-5.1-kemar.conf +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ # -context.properties = { - log.level = 0 -} - -context.spa-libs = { - audio.convert.* = audioconvert/libspa-audioconvert - support.* = support/libspa-support -} - context.modules = [ - { name = libpipewire-module-rt - args = { - #rt.prio = 88 - #rt.time.soft = -1 - #rt.time.hard = -1 - } - flags = [ ifexists nofail ] - } - { name = libpipewire-module-protocol-native } - { name = libpipewire-module-client-node } - { name = libpipewire-module-adapter } - { name = libpipewire-module-filter-chain args = { node.description = "Virtual Surround Sink" diff --git a/src/daemon/filter-chain/sink-virtual-surround-7.1-hesuvi.conf b/src/daemon/filter-chain/sink-virtual-surround-7.1-hesuvi.conf index ee596a530da69261088fa1fc8e844453debebd5e..4aad3102cb16e630c3a6262cf7e1639f03a5b4d9 100644 --- a/src/daemon/filter-chain/sink-virtual-surround-7.1-hesuvi.conf +++ b/src/daemon/filter-chain/sink-virtual-surround-7.1-hesuvi.conf @@ -1,29 +1,9 @@ # Convolver sink # -# start with pipewire -c filter-chain/sink-virtual-surround-7.1-hesuvi.conf +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ # -context.properties = { - log.level = 0 -} - -context.spa-libs = { - audio.convert.* = audioconvert/libspa-audioconvert - support.* = support/libspa-support -} - context.modules = [ - { name = libpipewire-module-rt - args = { - #rt.prio = 88 - #rt.time.soft = -1 - #rt.time.hard = -1 - } - flags = [ ifexists nofail ] - } - { name = libpipewire-module-protocol-native } - { name = libpipewire-module-client-node } - { name = libpipewire-module-adapter } - { name = libpipewire-module-filter-chain args = { node.description = "Virtual Surround Sink" diff --git a/src/daemon/filter-chain/duplicate-FL.conf b/src/daemon/filter-chain/source-duplicate-FL.conf similarity index 72% rename from src/daemon/filter-chain/duplicate-FL.conf rename to src/daemon/filter-chain/source-duplicate-FL.conf index 077f3bfacdea96a4c8e168e02d55f034e25a2c5f..7e0158f1834af757fae8efa9d1a5d392796ed50d 100644 --- a/src/daemon/filter-chain/duplicate-FL.conf +++ b/src/daemon/filter-chain/source-duplicate-FL.conf @@ -1,7 +1,8 @@ -# An example filter chain for duplicating the FL channel +# An example filter chain that makes a source that duplicates the FL channel # to FL and FR. # -# Copy this file into a conf.d/ directory +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ # context.modules = [ { name = libpipewire-module-filter-chain @@ -36,13 +37,15 @@ context.modules = [ outputs = [ "copyOL:Out" "copyOR:Out" ] } capture.props = { - node.name = "remap_input.remap-FL-to-FL-FR" - audio.position = [ FL ] + node.name = "remap_input.remap-FL-to-FL-FR" + audio.position = [ FL ] stream.dont-remix = true + node.passive = true } playback.props = { - node.name = "remap_output.remap-FL-to-FL-FR" - audio.position = [ FL FR ] + node.name = "remap_output.remap-FL-to-FL-FR" + audio.position = [ FL FR ] + media.class = "Audio/Source" } } } diff --git a/src/daemon/filter-chain/source-rnnoise.conf b/src/daemon/filter-chain/source-rnnoise.conf index fba5e29645c46322597dd50cb170371d1f89147a..5babd8111aca690f1945ebcb837985ab8f8bbf49 100644 --- a/src/daemon/filter-chain/source-rnnoise.conf +++ b/src/daemon/filter-chain/source-rnnoise.conf @@ -1,29 +1,9 @@ # Noise canceling source # -# start with pipewire -c filter-chain/source-rnnoise.conf +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ # -context.properties = { - log.level = 0 -} - -context.spa-libs = { - audio.convert.* = audioconvert/libspa-audioconvert - support.* = support/libspa-support -} - context.modules = [ - { name = libpipewire-module-rt - args = { - #rt.prio = 88 - #rt.time.soft = -1 - #rt.time.hard = -1 - } - flags = [ ifexists nofail ] - } - { name = libpipewire-module-protocol-native } - { name = libpipewire-module-client-node } - { name = libpipewire-module-adapter } - { name = libpipewire-module-filter-chain args = { node.description = "Noise Canceling source" diff --git a/src/daemon/jack.conf.in b/src/daemon/jack.conf.in index 48b637c7297b19ec674c1f30c1660e461fd87529..e3e2dc790ed60171610ca82528022e75d1457aeb 100644 --- a/src/daemon/jack.conf.in +++ b/src/daemon/jack.conf.in @@ -81,7 +81,8 @@ jack.properties = { # ignore-all: Ignore all self connect requests #jack.self-connect-mode = allow #jack.locked-process = true - #jack.default-as-system = false + #jack.default-as-system = false + #jack.fix-midi-events = true } # client specific properties diff --git a/src/daemon/meson.build b/src/daemon/meson.build index a2e4c55e3768aa15b847608bc258eddcf304688d..5d5914e4f44f15f35308464a9bdb6f4a9883334a 100644 --- a/src/daemon/meson.build +++ b/src/daemon/meson.build @@ -67,9 +67,11 @@ conf_files = [ 'pipewire.conf', 'client.conf', 'client-rt.conf', + 'filter-chain.conf', 'jack.conf', 'minimal.conf', 'pipewire-pulse.conf', + 'pipewire-avb.conf', ] foreach c : conf_files @@ -99,6 +101,14 @@ executable('pipewire-pulse', dependencies : [ spa_dep, pipewire_dep, ], ) +executable('pipewire-avb', + pipewire_daemon_sources, + install: true, + c_args : pipewire_c_args, + include_directories : [ configinc ], + dependencies : [ spa_dep, pipewire_dep, ], +) + ln = find_program('ln') custom_target('pipewire-uninstalled', diff --git a/src/daemon/minimal.conf.in b/src/daemon/minimal.conf.in index 49227dddf9555b5963e9fd9c2882fee458cac77f..8de1f21e1f76677a1acd2ce078997470d2fb65c2 100644 --- a/src/daemon/minimal.conf.in +++ b/src/daemon/minimal.conf.in @@ -27,7 +27,7 @@ context.properties = { ## Properties for the DSP configuration. #default.clock.rate = 48000 - #default.clock.allowed-rates = [ 44100 48000 ] + #default.clock.allowed-rates = [ 48000 ] #default.clock.quantum = 1024 #default.clock.min-quantum = 32 #default.clock.max-quantum = 2048 @@ -212,6 +212,7 @@ context.objects = [ #channelmix.stereo-widen = 0.0 #channelmix.hilbert-taps = 0 channelmix.disable = true + #dither.noise = 0 #node.param.Props = { # params = [ # audio.channels 6 @@ -220,6 +221,7 @@ context.objects = [ adapter.auto-port-config = { mode = dsp monitor = false + control = false position = unknown # aux, preserve } #node.param.PortConfig = { @@ -272,6 +274,7 @@ context.objects = [ #channelmix.stereo-widen = 0.0 #channelmix.hilbert-taps = 0 channelmix.disable = true + #dither.noise = 0 #node.param.Props = { # params = [ # audio.format S16 @@ -280,6 +283,7 @@ context.objects = [ adapter.auto-port-config = { mode = dsp monitor = false + control = false position = unknown # aux, preserve } #node.param.PortConfig = { diff --git a/src/daemon/pipewire-avb.conf.in b/src/daemon/pipewire-avb.conf.in new file mode 100644 index 0000000000000000000000000000000000000000..b4e465f07082948d767784c8ae3670567d4f177f --- /dev/null +++ b/src/daemon/pipewire-avb.conf.in @@ -0,0 +1,73 @@ +# PulseAudio config file for PipeWire version @VERSION@ # +# +# Copy and edit this file in @PIPEWIRE_CONFIG_DIR@ for system-wide changes +# or in ~/.config/pipewire for local changes. +# +# It is also possible to place a file with an updated section in +# @PIPEWIRE_CONFIG_DIR@/pipewire-pulse.conf.d/ for system-wide changes or in +# ~/.config/pipewire/pipewire-pulse.conf.d/ for local changes. +# + +context.properties = { + ## Configure properties in the system. + #mem.warn-mlock = false + #mem.allow-mlock = true + #mem.mlock-all = false + #log.level = 2 + + #default.clock.quantum-limit = 8192 +} + +context.spa-libs = { + audio.convert.* = audioconvert/libspa-audioconvert + support.* = support/libspa-support +} + +context.modules = [ + { name = libpipewire-module-rt + args = { + nice.level = -11 + #rt.prio = 88 + #rt.time.soft = -1 + #rt.time.hard = -1 + } + flags = [ ifexists nofail ] + } + { name = libpipewire-module-protocol-native } + { name = libpipewire-module-client-node } + { name = libpipewire-module-adapter } + { name = libpipewire-module-avb + args = { + # contents of avb.properties can also be placed here + # to have config per server. + } + } +] + +# Extra modules can be loaded here. Setup in default.pa can be moved here +context.exec = [ + #{ path = "pactl" args = "load-module module-always-sink" } +] + +stream.properties = { + #node.latency = 1024/48000 + #node.autoconnect = true + #resample.quality = 4 + #channelmix.normalize = false + #channelmix.mix-lfe = false + #channelmix.upmix = true + #channelmix.lfe-cutoff = 120 + #channelmix.fc-cutoff = 6000 + #channelmix.rear-delay = 12.0 + #channelmix.stereo-widen = 0.1 + #channelmix.hilbert-taps = 0 +} + +avb.properties = { + # the addresses this server listens on + #ifname = "eth0.2" + ifname = "enp3s0" + # These overrides are only applied when running in a vm. + vm.overrides = { + } +} diff --git a/src/daemon/pipewire-pulse.conf.in b/src/daemon/pipewire-pulse.conf.in index 3ab1528177942ce395d7509984bf83da519471f4..1ee3494a084b9f5f792330043c240c5e0f038ffb 100644 --- a/src/daemon/pipewire-pulse.conf.in +++ b/src/daemon/pipewire-pulse.conf.in @@ -66,6 +66,7 @@ stream.properties = { #channelmix.rear-delay = 12.0 #channelmix.stereo-widen = 0.0 #channelmix.hilbert-taps = 0 + #dither.noise = 0 } pulse.properties = { diff --git a/src/daemon/pipewire.conf.in b/src/daemon/pipewire.conf.in index 787e45f42004432be8f07473b9e005b95141e4d4..748c6173f036f2b4a1eaec4644ffd494b46b8bd5 100644 --- a/src/daemon/pipewire.conf.in +++ b/src/daemon/pipewire.conf.in @@ -27,7 +27,7 @@ context.properties = { ## Properties for the DSP configuration. #default.clock.rate = 48000 - #default.clock.allowed-rates = [ 44100 48000 ] + #default.clock.allowed-rates = [ 48000 ] #default.clock.quantum = 1024 default.clock.min-quantum = 16 #default.clock.max-quantum = 2048 @@ -54,6 +54,7 @@ context.spa-libs = { # that factory. # audio.convert.* = audioconvert/libspa-audioconvert + avb.* = avb/libspa-avb api.alsa.* = alsa/libspa-alsa api.v4l2.* = v4l2/libspa-v4l2 api.libcamera.* = libcamera/libspa-libcamera diff --git a/src/daemon/systemd/user/filter-chain.service.in b/src/daemon/systemd/user/filter-chain.service.in new file mode 100644 index 0000000000000000000000000000000000000000..542cbd7f38218804b397a0b53107c5cbb114fae9 --- /dev/null +++ b/src/daemon/systemd/user/filter-chain.service.in @@ -0,0 +1,21 @@ +[Unit] +Description=PipeWire filter chain daemon + +After=pipewire.service pipewire-session-manager.service +BindsTo=pipewire.service + +[Service] +LockPersonality=yes +MemoryDenyWriteExecute=yes +NoNewPrivileges=yes +RestrictNamespaces=yes +SystemCallArchitectures=native +SystemCallFilter=@system-service +Type=simple +ExecStart=@PW_BINARY@ -c filter-chain.conf +Restart=on-failure +Slice=session.slice + +[Install] +Also=pipewire.socket +WantedBy=default.target diff --git a/src/daemon/systemd/user/meson.build b/src/daemon/systemd/user/meson.build index d17f3794f63b32e091c4f56e7c2b4be6228c0f36..10227629df3f5a3b8c013bfd59aeb11ee7e4cc51 100644 --- a/src/daemon/systemd/user/meson.build +++ b/src/daemon/systemd/user/meson.build @@ -20,3 +20,8 @@ configure_file(input : 'pipewire-pulse.service.in', output : 'pipewire-pulse.service', configuration : systemd_config, install_dir : systemd_user_services_dir) + +configure_file(input : 'filter-chain.service.in', + output : 'filter-chain.service', + configuration : systemd_config, + install_dir : systemd_user_services_dir) diff --git a/src/examples/audio-src.c b/src/examples/audio-src.c index f65f6148ae0e866377ef91e186e6e57e66ff05ba..121773219d910007ab84b5919eb2e53258311de8 100644 --- a/src/examples/audio-src.c +++ b/src/examples/audio-src.c @@ -120,6 +120,7 @@ int main(int argc, char *argv[]) struct data data = { 0, }; const struct spa_pod *params[1]; uint8_t buffer[1024]; + struct pw_properties *props; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); pw_init(&argc, &argv); @@ -142,14 +143,17 @@ int main(int argc, char *argv[]) * you need to listen to is the process event where you need to produce * the data. */ + props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", + PW_KEY_MEDIA_CATEGORY, "Playback", + PW_KEY_MEDIA_ROLE, "Music", + NULL); + if (argc > 1) + /* Set stream target if given on command line */ + pw_properties_set(props, PW_KEY_TARGET_OBJECT, argv[1]); data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "audio-src", - pw_properties_new( - PW_KEY_MEDIA_TYPE, "Audio", - PW_KEY_MEDIA_CATEGORY, "Playback", - PW_KEY_MEDIA_ROLE, "Music", - NULL), + props, &stream_events, &data); @@ -165,7 +169,7 @@ int main(int argc, char *argv[]) * called in a realtime thread. */ pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, - argc > 1 ? (uint32_t)atoi(argv[1]) : PW_ID_ANY, + PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, diff --git a/src/examples/export-sink.c b/src/examples/export-sink.c index 05d6380dfe1ce7368d7d559f7685cc63d062d3fb..9e0985475ec1591514135b7193be3c285c48c66f 100644 --- a/src/examples/export-sink.c +++ b/src/examples/export-sink.c @@ -472,7 +472,7 @@ static void make_node(struct data *data) props = pw_properties_new(PW_KEY_NODE_AUTOCONNECT, "true", NULL); if (data->path) - pw_properties_set(props, PW_KEY_NODE_TARGET, data->path); + pw_properties_set(props, PW_KEY_TARGET_OBJECT, data->path); pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Stream/Input/Video"); pw_properties_set(props, PW_KEY_MEDIA_TYPE, "Video"); pw_properties_set(props, PW_KEY_MEDIA_CATEGORY, "Capture"); diff --git a/src/examples/export-source.c b/src/examples/export-source.c index 0310efb5b4fe9b8e601c33f792edb902e475b710..f84d01d9262d6e004ed984955707acf74fc0390d 100644 --- a/src/examples/export-source.c +++ b/src/examples/export-source.c @@ -483,7 +483,7 @@ static void make_node(struct data *data) PW_KEY_MEDIA_ROLE, "Music", NULL); if (data->path) - pw_properties_set(props, PW_KEY_NODE_TARGET, data->path); + pw_properties_set(props, PW_KEY_TARGET_OBJECT, data->path); data->impl_node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, diff --git a/src/examples/export-spa.c b/src/examples/export-spa.c index 864555d1f583b3cd3b5f88d739a76f64223edd5a..e2f27cf12c58a5f500b55882f319e07f2db9a219 100644 --- a/src/examples/export-spa.c +++ b/src/examples/export-spa.c @@ -93,7 +93,7 @@ static int make_node(struct data *data) if (data->path) { pw_properties_set(props, PW_KEY_NODE_AUTOCONNECT, "true"); - pw_properties_set(props, PW_KEY_NODE_TARGET, data->path); + pw_properties_set(props, PW_KEY_TARGET_OBJECT, data->path); } data->proxy = pw_core_export(data->core, diff --git a/src/examples/video-dsp-play.c b/src/examples/video-dsp-play.c index 05828b5918220a952008791d5ddcb1d752ed1525..71f9b01998030de63cc47534c9bd8a0dca51ce3c 100644 --- a/src/examples/video-dsp-play.c +++ b/src/examples/video-dsp-play.c @@ -263,7 +263,7 @@ int main(int argc, char *argv[]) PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "DSP", PW_KEY_NODE_AUTOCONNECT, data.target ? "true" : "false", - PW_KEY_NODE_TARGET, data.target, + PW_KEY_TARGET_OBJECT, data.target, PW_KEY_MEDIA_CLASS, "Stream/Input/Video", NULL), &filter_events, diff --git a/src/examples/video-play-fixate.c b/src/examples/video-play-fixate.c index 3131c144521ed29f2a34800b45ad522d00aeefe3..1badc3c39a8d60877b642a535419222122c3217a 100644 --- a/src/examples/video-play-fixate.c +++ b/src/examples/video-play-fixate.c @@ -419,6 +419,7 @@ int main(int argc, char *argv[]) const struct spa_pod *params[2]; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + struct pw_properties *props; int res, n_params; pw_init(&argc, &argv); @@ -440,19 +441,22 @@ int main(int argc, char *argv[]) * you need to listen to is the process event where you need to consume * the data provided to you. */ + props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Camera", + NULL), + data.path = argc > 1 ? argv[1] : NULL; + if (data.path) + /* Set stream target if given on command line */ + pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path); + data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), - "video-play-reneg", - pw_properties_new( - PW_KEY_MEDIA_TYPE, "Video", - PW_KEY_MEDIA_CATEGORY, "Capture", - PW_KEY_MEDIA_ROLE, "Camera", - NULL), + "video-play-fixate", + props, &stream_events, &data); - data.path = argc > 1 ? argv[1] : NULL; - if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError()); return -1; @@ -477,7 +481,7 @@ int main(int argc, char *argv[]) */ if ((res = pw_stream_connect(data.stream, PW_DIRECTION_INPUT, - data.path ? (uint32_t)atoi(data.path) : PW_ID_ANY, + PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */ params, n_params)) /* extra parameters, see above */ < 0) { diff --git a/src/examples/video-play-pull.c b/src/examples/video-play-pull.c index 37f7e731413d545501bcfbbc25aa6e14cf3b085f..2b2c34660c1275ef98c1698b9aa4bf8b2be78897 100644 --- a/src/examples/video-play-pull.c +++ b/src/examples/video-play-pull.c @@ -493,6 +493,7 @@ int main(int argc, char *argv[]) const struct spa_pod *params[2]; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + struct pw_properties *props; int res, n_params; pw_init(&argc, &argv); @@ -517,20 +518,23 @@ int main(int argc, char *argv[]) * you need to listen to is the process event where you need to consume * the data provided to you. */ + props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Camera", + PW_KEY_PRIORITY_DRIVER, "10000", + NULL), + data.path = argc > 1 ? argv[1] : NULL; + if (data.path) + /* Set stream target if given on command line */ + pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path); + data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "video-play", - pw_properties_new( - PW_KEY_MEDIA_TYPE, "Video", - PW_KEY_MEDIA_CATEGORY, "Capture", - PW_KEY_MEDIA_ROLE, "Camera", - PW_KEY_PRIORITY_DRIVER, "10000", - NULL), + props, &stream_events, &data); - data.path = argc > 1 ? argv[1] : NULL; - if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError()); return -1; @@ -552,7 +556,7 @@ int main(int argc, char *argv[]) */ if ((res = pw_stream_connect(data.stream, PW_DIRECTION_INPUT, - data.path ? (uint32_t)atoi(data.path) : PW_ID_ANY, + PW_ID_ANY, PW_STREAM_FLAG_DRIVER | /* we're driver, we pull */ PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */ diff --git a/src/examples/video-play-reneg.c b/src/examples/video-play-reneg.c index 3c86bd0969252181133333fcf8ce15fb1b96f479..aa071850afad34a68860867ba8161a9a891ddd67 100644 --- a/src/examples/video-play-reneg.c +++ b/src/examples/video-play-reneg.c @@ -346,6 +346,7 @@ int main(int argc, char *argv[]) const struct spa_pod *params[2]; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + struct pw_properties *props; int res, n_params; pw_init(&argc, &argv); @@ -367,19 +368,22 @@ int main(int argc, char *argv[]) * you need to listen to is the process event where you need to consume * the data provided to you. */ + props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Camera", + NULL), + data.path = argc > 1 ? argv[1] : NULL; + if (data.path) + /* Set stream target if given on command line */ + pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path); + data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "video-play-reneg", - pw_properties_new( - PW_KEY_MEDIA_TYPE, "Video", - PW_KEY_MEDIA_CATEGORY, "Capture", - PW_KEY_MEDIA_ROLE, "Camera", - NULL), + props, &stream_events, &data); - data.path = argc > 1 ? argv[1] : NULL; - if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError()); return -1; @@ -401,7 +405,7 @@ int main(int argc, char *argv[]) */ if ((res = pw_stream_connect(data.stream, PW_DIRECTION_INPUT, - data.path ? (uint32_t)atoi(data.path) : PW_ID_ANY, + PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */ params, n_params)) /* extra parameters, see above */ < 0) { diff --git a/src/examples/video-play.c b/src/examples/video-play.c index 20362b892828ea767435510e53e09e5b306a3705..68589c90218db9c5a1600893f60fb69f9df95194 100644 --- a/src/examples/video-play.c +++ b/src/examples/video-play.c @@ -439,6 +439,7 @@ int main(int argc, char *argv[]) const struct spa_pod *params[2]; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + struct pw_properties *props; int res, n_params; pw_init(&argc, &argv); @@ -460,19 +461,21 @@ int main(int argc, char *argv[]) * you need to listen to is the process event where you need to consume * the data provided to you. */ + props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Camera", + NULL), + data.path = argc > 1 ? argv[1] : NULL; + if (data.path) + pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path); + data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "video-play", - pw_properties_new( - PW_KEY_MEDIA_TYPE, "Video", - PW_KEY_MEDIA_CATEGORY, "Capture", - PW_KEY_MEDIA_ROLE, "Camera", - NULL), + props, &stream_events, &data); - data.path = argc > 1 ? argv[1] : NULL; - if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError()); return -1; @@ -494,7 +497,7 @@ int main(int argc, char *argv[]) */ if ((res = pw_stream_connect(data.stream, PW_DIRECTION_INPUT, - data.path ? (uint32_t)atoi(data.path) : PW_ID_ANY, + PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ PW_STREAM_FLAG_INACTIVE | /* we will activate ourselves */ PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */ diff --git a/src/examples/video-src-alloc.c b/src/examples/video-src-alloc.c index a35ecc8a7775faca7a2cfc721f63287e03443175..ef364fd1d7d23a53abb40b5cf922c0ceda0dbde6 100644 --- a/src/examples/video-src-alloc.c +++ b/src/examples/video-src-alloc.c @@ -442,7 +442,7 @@ int main(int argc, char *argv[]) * the server. */ pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, - PW_ID_ANY, /* link to any node */ + PW_ID_ANY, PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS, params, 1); diff --git a/src/examples/video-src-fixate.c b/src/examples/video-src-fixate.c index bdfbb33a286ddd5ab25164596244de2d328cb608..6d621d16367279b0297d7c3c9bc900467f1f7dd0 100644 --- a/src/examples/video-src-fixate.c +++ b/src/examples/video-src-fixate.c @@ -578,7 +578,7 @@ int main(int argc, char *argv[]) * the server. */ pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, - PW_ID_ANY, /* link to any node */ + PW_ID_ANY, PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS, params, 2); diff --git a/src/examples/video-src-reneg.c b/src/examples/video-src-reneg.c index d0431210a8638a5e46d50877931b5d2ace0bb249..172e7dc915637bd8a17fa6ac32977bd3c9c388df 100644 --- a/src/examples/video-src-reneg.c +++ b/src/examples/video-src-reneg.c @@ -487,7 +487,7 @@ int main(int argc, char *argv[]) * the server. */ pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, - PW_ID_ANY, /* link to any node */ + PW_ID_ANY, PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS, params, 1); diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 32bcf4988b4f545c6e956c44da24b216e089ccab..4e8e8bd4e7f10f846027443bbb7aadecc6108907 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -44,6 +44,7 @@ #include <unistd.h> #include <spa/pod/builder.h> +#include <spa/utils/result.h> #include <gst/net/gstnetclientclock.h> #include <gst/allocators/gstfdmemory.h> @@ -443,6 +444,7 @@ buffer_recycle (GstMiniObject *obj) { GstPipeWireSrc *src; GstPipeWirePoolData *data; + int res; data = gst_pipewire_pool_get_data (GST_BUFFER_CAST(obj)); @@ -466,8 +468,11 @@ buffer_recycle (GstMiniObject *obj) data->queued = TRUE; - GST_LOG_OBJECT (src, "recycle buffer %p", obj); - pw_stream_queue_buffer (src->stream, data->b); + if ((res = pw_stream_queue_buffer (src->stream, data->b)) < 0) + GST_WARNING_OBJECT (src, "can't queue recycled buffer %p, %s", obj, spa_strerror(res)); + else + GST_LOG_OBJECT (src, "recycle buffer %p", obj); + pw_thread_loop_unlock (src->core->loop); GST_OBJECT_UNLOCK (data->pool); @@ -481,9 +486,9 @@ on_add_buffer (void *_data, struct pw_buffer *b) GstPipeWireSrc *pwsrc = _data; GstPipeWirePoolData *data; - GST_DEBUG_OBJECT (pwsrc, "add buffer"); gst_pipewire_pool_wrap_buffer (pwsrc->pool, b); data = b->user_data; + GST_DEBUG_OBJECT (pwsrc, "add buffer %p", data->buf); data->owner = pwsrc; data->queued = TRUE; GST_MINI_OBJECT_CAST (data->buf)->dispose = buffer_recycle; @@ -495,15 +500,18 @@ on_remove_buffer (void *_data, struct pw_buffer *b) GstPipeWireSrc *pwsrc = _data; GstPipeWirePoolData *data = b->user_data; GstBuffer *buf = data->buf; + int res; GST_DEBUG_OBJECT (pwsrc, "remove buffer %p", buf); GST_MINI_OBJECT_CAST (buf)->dispose = NULL; - if (data->queued) + if (data->queued) { gst_buffer_unref (buf); - else - pw_stream_queue_buffer (pwsrc->stream, b); + } else { + if ((res = pw_stream_queue_buffer (pwsrc->stream, b)) < 0) + GST_WARNING_OBJECT (pwsrc, "can't queue removed buffer %p, %s", buf, spa_strerror(res)); + } } static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc) @@ -560,19 +568,23 @@ static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc) meta->height = crop->region.size.height; } } - gst_buffer_add_parent_buffer_meta (buf, data->buf); - gst_buffer_unref (data->buf); for (i = 0; i < b->buffer->n_datas; i++) { struct spa_data *d = &b->buffer->datas[i]; GstMemory *pmem = gst_buffer_peek_memory (data->buf, i); if (pmem) { - GstMemory *mem = gst_memory_share (pmem, d->chunk->offset, d->chunk->size); + GstMemory *mem; + if (!pwsrc->always_copy) + mem = gst_memory_share (pmem, d->chunk->offset, d->chunk->size); + else + mem = gst_memory_copy (pmem, d->chunk->offset, d->chunk->size); gst_buffer_insert_memory (buf, i, mem); - spa_assert_se(mem->size <= mem->maxsize); } if (d->chunk->flags & SPA_CHUNK_FLAG_CORRUPTED) GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_CORRUPTED); } + if (!pwsrc->always_copy) + gst_buffer_add_parent_buffer_meta (buf, data->buf); + gst_buffer_unref (data->buf); return buf; } @@ -1026,10 +1038,10 @@ gst_pipewire_src_create (GstPushSrc * psrc, GstBuffer ** buffer) pwsrc = GST_PIPEWIRE_SRC (psrc); + pw_thread_loop_lock (pwsrc->core->loop); if (!pwsrc->negotiated) goto not_negotiated; - pw_thread_loop_lock (pwsrc->core->loop); while (TRUE) { enum pw_stream_state state; @@ -1083,12 +1095,7 @@ gst_pipewire_src_create (GstPushSrc * psrc, GstBuffer ** buffer) } pw_thread_loop_unlock (pwsrc->core->loop); - if (pwsrc->always_copy) { - *buffer = gst_buffer_copy_deep (buf); - gst_buffer_unref (buf); - } - else - *buffer = buf; + *buffer = buf; if (pwsrc->is_live) base_time = GST_ELEMENT_CAST (psrc)->base_time; @@ -1126,6 +1133,7 @@ gst_pipewire_src_create (GstPushSrc * psrc, GstBuffer ** buffer) not_negotiated: { + pw_thread_loop_unlock (pwsrc->core->loop); return GST_FLOW_NOT_NEGOTIATED; } streaming_eos: @@ -1348,7 +1356,9 @@ gst_pipewire_src_change_state (GstElement * element, GstStateChange transition) case GST_STATE_CHANGE_PLAYING_TO_PAUSED: break; case GST_STATE_CHANGE_PAUSED_TO_READY: + pw_thread_loop_lock (this->core->loop); this->negotiated = FALSE; + pw_thread_loop_unlock (this->core->loop); break; case GST_STATE_CHANGE_READY_TO_NULL: gst_pipewire_src_close (this); diff --git a/src/modules/flatpak-utils.h b/src/modules/flatpak-utils.h new file mode 100644 index 0000000000000000000000000000000000000000..8952ac4e32887396cac80ac7314b3e85cb0a4b08 --- /dev/null +++ b/src/modules/flatpak-utils.h @@ -0,0 +1,156 @@ +/* PipeWire + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef FLATPAK_UTILS_H +#define FLATPAK_UTILS_H + +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#ifdef HAVE_SYS_VFS_H +#include <sys/vfs.h> +#endif + +#include "config.h" + +#ifdef HAVE_GLIB2 +#include <glib.h> +#endif + +#include <spa/utils/result.h> +#include <pipewire/log.h> + +static int pw_check_flatpak_parse_metadata(const char *buf, size_t size, char **app_id, char **devices) +{ +#ifdef HAVE_GLIB2 + /* + * See flatpak-metadata(5) + * + * The .flatpak-info file is in GLib key_file .ini format. + */ + g_autoptr(GKeyFile) metadata = NULL; + char *s; + + metadata = g_key_file_new(); + if (!g_key_file_load_from_data(metadata, buf, size, G_KEY_FILE_NONE, NULL)) + return -EINVAL; + + if (app_id) { + s = g_key_file_get_value(metadata, "Application", "name", NULL); + *app_id = s ? strdup(s) : NULL; + g_free(s); + } + + if (devices) { + s = g_key_file_get_value(metadata, "Context", "devices", NULL); + *devices = s ? strdup(s) : NULL; + g_free(s); + } + + return 0; +#else + return -ENOTSUP; +#endif +} + +static int pw_check_flatpak(pid_t pid, char **app_id, char **devices) +{ +#if defined(__linux__) + char root_path[2048]; + int root_fd, info_fd, res; + struct stat stat_buf; + + if (app_id) + *app_id = NULL; + if (devices) + *devices = NULL; + + snprintf(root_path, sizeof(root_path), "/proc/%d/root", (int)pid); + root_fd = openat (AT_FDCWD, root_path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); + if (root_fd == -1) { + res = -errno; + if (res == -EACCES) { + struct statfs buf; + /* Access to the root dir isn't allowed. This can happen if the root is on a fuse + * filesystem, such as in a toolbox container. We will never have a fuse rootfs + * in the flatpak case, so in that case its safe to ignore this and + * continue to detect other types of apps. */ + if (statfs(root_path, &buf) == 0 && + buf.f_type == 0x65735546) /* FUSE_SUPER_MAGIC */ + return 0; + } + /* Not able to open the root dir shouldn't happen. Probably the app died and + * we're failing due to /proc/$pid not existing. In that case fail instead + * of treating this as privileged. */ + pw_log_info("failed to open \"%s\": %s", root_path, spa_strerror(res)); + return res; + } + info_fd = openat (root_fd, ".flatpak-info", O_RDONLY | O_CLOEXEC | O_NOCTTY); + close (root_fd); + if (info_fd == -1) { + if (errno == ENOENT) { + pw_log_debug("no .flatpak-info, client on the host"); + /* No file => on the host */ + return 0; + } + res = -errno; + pw_log_error("error opening .flatpak-info: %m"); + return res; + } + if (fstat (info_fd, &stat_buf) != 0 || !S_ISREG (stat_buf.st_mode)) { + /* Some weird fd => failure, assume sandboxed */ + pw_log_error("error fstat .flatpak-info: %m"); + } else if (app_id || devices) { + /* Parse the application ID if needed */ + const size_t size = stat_buf.st_size; + + if (size > 0) { + void *buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, info_fd, 0); + if (buf != MAP_FAILED) { + res = pw_check_flatpak_parse_metadata(buf, size, app_id, devices); + munmap(buf, size); + } else { + res = -errno; + } + } else { + res = -EINVAL; + } + + if (res == -EINVAL) + pw_log_error("PID %d .flatpak-info file is malformed", + (int)pid); + else if (res < 0) + pw_log_error("PID %d .flatpak-info parsing failed: %s", + (int)pid, spa_strerror(res)); + } + close(info_fd); + return 1; +#else + return 0; +#endif +} + +#endif /* FLATPAK_UTILS_H */ diff --git a/src/modules/meson.build b/src/modules/meson.build index 3c8d6db5ba34878ca052190f644df16dea9ea47c..3e0e3a3fdccd890fcdfc83da5c7db5e33cf3efba 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -5,6 +5,7 @@ subdir('spa') module_sources = [ 'module-access.c', 'module-adapter.c', + 'module-avb.c', 'module-client-device.c', 'module-client-node.c', 'module-echo-cancel.c', @@ -32,12 +33,17 @@ module_sources = [ 'module-x11-bell.c', ] +pipewire_module_access_deps = [spa_dep, mathlib, dl_lib, pipewire_dep] +if flatpak_support + pipewire_module_access_deps += glib2_dep +endif + pipewire_module_access = shared_library('pipewire-module-access', [ 'module-access.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, - dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], + dependencies : pipewire_module_access_deps ) pipewire_module_loopback = shared_library('pipewire-module-loopback', @@ -276,6 +282,10 @@ if avahi_dep.found() cdata.set('HAVE_AVAHI', true) endif +if flatpak_support + pipewire_module_protocol_pulse_deps += glib2_dep +endif + pipewire_module_protocol_pulse = shared_library('pipewire-module-protocol-pulse', pipewire_module_protocol_pulse_sources, include_directories : [configinc], @@ -469,7 +479,7 @@ pipewire_module_raop_sink = shared_library('pipewire-module-raop-sink', endif summary({'raop-sink (requires OpenSSL)': build_module_raop}, bool_yn: true, section: 'Optional Modules') -roc_lib = cc.find_library('roc', required: get_option('roc')) +roc_lib = cc.find_library('roc', has_headers: ['roc/config.h' ], required: get_option('roc')) summary({'ROC': roc_lib.found()}, bool_yn: true, section: 'Streaming between daemons') build_module_roc = roc_lib.found() @@ -516,3 +526,30 @@ pipewire_module_fallback_sink = shared_library('pipewire-module-fallback-sink', install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep], ) + +build_module_avb = get_option('avb').require(host_machine.system() == 'linux', error_message: 'AVB support is only available on Linux').allowed() +if build_module_avb +pipewire_module_avb = shared_library('pipewire-module-avb', + [ 'module-avb.c', + 'module-avb/avb.c', + 'module-avb/adp.c', + 'module-avb/acmp.c', + 'module-avb/aecp.c', + 'module-avb/aecp-aem.c', + 'module-avb/avdecc.c', + 'module-avb/maap.c', + 'module-avb/mmrp.c', + 'module-avb/mrp.c', + 'module-avb/msrp.c', + 'module-avb/mvrp.c', + 'module-avb/srp.c', + 'module-avb/stream.c' + ], + include_directories : [configinc], + install : true, + install_dir : modules_install_dir, + install_rpath: modules_install_dir, + dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep], +) +endif +summary({'avb': build_module_avb}, bool_yn: true, section: 'Optional Modules') diff --git a/src/modules/module-access.c b/src/modules/module-access.c index a5df8aa5e0cf25e02129114046bdf798056953a6..9b6d02861001a05ca7a7f5eb17feea8810f3bea2 100644 --- a/src/modules/module-access.c +++ b/src/modules/module-access.c @@ -46,6 +46,8 @@ #include <pipewire/impl.h> #include <pipewire/private.h> +#include "flatpak-utils.h" + /** \page page_module_access PipeWire Module: Access * * @@ -75,6 +77,9 @@ * on an external actor to update that property once permission is * granted or rejected. * + * For connections from applications running inside Flatpak not mediated + * by a portal, the `access` module itself sets the `pipewire.access.portal.app_id` + * property to the Flatpak application ID. * * ## Module Options * @@ -184,54 +189,6 @@ exit: return res; } -#if defined(__linux__) -static int check_flatpak(struct pw_impl_client *client, int pid) -{ - char root_path[2048]; - int root_fd, info_fd, res; - struct stat stat_buf; - - sprintf(root_path, "/proc/%u/root", pid); - root_fd = openat (AT_FDCWD, root_path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); - if (root_fd == -1) { - res = -errno; - if (res == -EACCES) { - struct statfs buf; - /* Access to the root dir isn't allowed. This can happen if the root is on a fuse - * filesystem, such as in a toolbox container. We will never have a fuse rootfs - * in the flatpak case, so in that case its safe to ignore this and - * continue to detect other types of apps. */ - if (statfs(root_path, &buf) == 0 && - buf.f_type == 0x65735546) /* FUSE_SUPER_MAGIC */ - return 0; - } - /* Not able to open the root dir shouldn't happen. Probably the app died and - * we're failing due to /proc/$pid not existing. In that case fail instead - * of treating this as privileged. */ - pw_log_info("failed to open \"%s\": %s", root_path, spa_strerror(res)); - return res; - } - info_fd = openat (root_fd, ".flatpak-info", O_RDONLY | O_CLOEXEC | O_NOCTTY); - close (root_fd); - if (info_fd == -1) { - if (errno == ENOENT) { - pw_log_debug("no .flatpak-info, client on the host"); - /* No file => on the host */ - return 0; - } - res = -errno; - pw_log_error("error opening .flatpak-info: %m"); - return res; - } - if (fstat (info_fd, &stat_buf) != 0 || !S_ISREG (stat_buf.st_mode)) { - /* Some weird fd => failure, assume sandboxed */ - pw_log_error("error fstat .flatpak-info: %m"); - } - close(info_fd); - return 1; -} -#endif - static void context_check_access(void *data, struct pw_impl_client *client) { @@ -240,6 +197,8 @@ context_check_access(void *data, struct pw_impl_client *client) struct spa_dict_item items[2]; const struct pw_properties *props; const char *str, *access; + char *flatpak_app_id = NULL; + int nitems = 0; int pid, res; pid = -EINVAL; @@ -298,8 +257,7 @@ context_check_access(void *data, struct pw_impl_client *client) (access = pw_properties_get(impl->properties, "access.force")) != NULL) goto wait_permissions; -#if defined(__linux__) - res = check_flatpak(client, pid); + res = pw_check_flatpak(pid, &flatpak_app_id, NULL); if (res != 0) { if (res < 0) { if (res == -EACCES) { @@ -313,9 +271,11 @@ context_check_access(void *data, struct pw_impl_client *client) pw_log_debug(" %p: flatpak client %p added", impl, client); } access = "flatpak"; + items[nitems++] = SPA_DICT_ITEM_INIT("pipewire.access.portal.app_id", + flatpak_app_id); goto wait_permissions; } -#endif + if ((access = pw_properties_get(props, PW_KEY_CLIENT_ACCESS)) == NULL) access = "unrestricted"; @@ -326,24 +286,28 @@ context_check_access(void *data, struct pw_impl_client *client) granted: pw_log_info("%p: client %p '%s' access granted", impl, client, access); - items[0] = SPA_DICT_ITEM_INIT(PW_KEY_ACCESS, access); - pw_impl_client_update_properties(client, &SPA_DICT_INIT(items, 1)); + items[nitems++] = SPA_DICT_ITEM_INIT(PW_KEY_ACCESS, access); + pw_impl_client_update_properties(client, &SPA_DICT_INIT(items, nitems)); permissions[0] = PW_PERMISSION_INIT(PW_ID_ANY, PW_PERM_ALL); pw_impl_client_update_permissions(client, 1, permissions); - return; + goto done; wait_permissions: pw_log_info("%p: client %p wait for '%s' permissions", impl, client, access); - items[0] = SPA_DICT_ITEM_INIT(PW_KEY_ACCESS, access); - pw_impl_client_update_properties(client, &SPA_DICT_INIT(items, 1)); - return; + items[nitems++] = SPA_DICT_ITEM_INIT(PW_KEY_ACCESS, access); + pw_impl_client_update_properties(client, &SPA_DICT_INIT(items, nitems)); + goto done; rejected: pw_resource_error(pw_impl_client_get_core_resource(client), res, access); - items[0] = SPA_DICT_ITEM_INIT(PW_KEY_ACCESS, access); - pw_impl_client_update_properties(client, &SPA_DICT_INIT(items, 1)); + items[nitems++] = SPA_DICT_ITEM_INIT(PW_KEY_ACCESS, access); + pw_impl_client_update_properties(client, &SPA_DICT_INIT(items, nitems)); + goto done; + +done: + free(flatpak_app_id); return; } diff --git a/src/modules/module-adapter/adapter.c b/src/modules/module-adapter/adapter.c index aab785527c613819a153399355529a40535f5111..3c2cd56b9c9b4ac7cba220d58cce7abccbbc5015 100644 --- a/src/modules/module-adapter/adapter.c +++ b/src/modules/module-adapter/adapter.c @@ -95,7 +95,7 @@ static void node_port_init(void *data, struct pw_impl_port *port) struct pw_properties *new; const char *str, *path, *desc, *nick, *name, *node_name, *media_class; char position[8], *prefix; - bool is_monitor, is_device, is_duplex, is_virtual; + bool is_monitor, is_device, is_duplex, is_virtual, is_control = false; direction = pw_impl_port_get_direction(port); @@ -105,6 +105,9 @@ static void node_port_init(void *data, struct pw_impl_port *port) if (!is_monitor && direction != n->direction) return; + if ((str = pw_properties_get(old, PW_KEY_FORMAT_DSP)) != NULL) + is_control = spa_streq(str, "8 bit raw midi"); + path = pw_properties_get(n->props, PW_KEY_OBJECT_PATH); media_class = pw_properties_get(n->props, PW_KEY_MEDIA_CLASS); @@ -120,7 +123,10 @@ static void node_port_init(void *data, struct pw_impl_port *port) new = pw_properties_new(NULL, NULL); - if (is_duplex) + if (is_control) + prefix = direction == PW_DIRECTION_INPUT ? + "control" : "notify"; + else if (is_duplex) prefix = direction == PW_DIRECTION_INPUT ? "playback" : "capture"; else if (is_virtual) @@ -156,13 +162,20 @@ static void node_port_init(void *data, struct pw_impl_port *port) pw_properties_setf(new, PW_KEY_OBJECT_PATH, "%s:%s_%d", path ? path : node_name, prefix, pw_impl_port_get_id(port)); - pw_properties_setf(new, PW_KEY_PORT_NAME, "%s_%s", prefix, str); + if (is_control) + pw_properties_setf(new, PW_KEY_PORT_NAME, "%s", prefix); + else + pw_properties_setf(new, PW_KEY_PORT_NAME, "%s_%s", prefix, str); if ((node_name = nick) == NULL && (node_name = desc) == NULL && (node_name = name) == NULL) node_name = "node"; - pw_properties_setf(new, PW_KEY_PORT_ALIAS, "%s:%s_%s", + if (is_control) + pw_properties_setf(new, PW_KEY_PORT_ALIAS, "%s:%s", + node_name, prefix); + else + pw_properties_setf(new, PW_KEY_PORT_ALIAS, "%s:%s_%s", node_name, prefix, str); pw_impl_port_update_properties(port, &new->dict); @@ -240,7 +253,7 @@ static int do_auto_port_config(struct node *n, const char *str) int res, position = POSITION_PRESERVE; struct spa_pod *param; uint32_t media_type, media_subtype; - bool have_format = false, monitor = false; + bool have_format = false, monitor = false, control = false; struct spa_audio_info format = { 0, }; enum spa_param_port_config_mode mode = SPA_PARAM_PORT_CONFIG_MODE_none; struct spa_json it[2]; @@ -260,6 +273,8 @@ static int do_auto_port_config(struct node *n, const char *str) mode = SPA_PARAM_PORT_CONFIG_MODE_none; } else if (spa_streq(key, "monitor")) { monitor = spa_atob(val); + } else if (spa_streq(key, "control")) { + control = spa_atob(val); } else if (spa_streq(key, "position")) { if (spa_streq(val, "unknown")) position = POSITION_UNKNOWN; @@ -331,6 +346,7 @@ static int do_auto_port_config(struct node *n, const char *str) SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(n->direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(mode), SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(monitor), + SPA_PARAM_PORT_CONFIG_control, SPA_POD_Bool(control), SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); pw_impl_node_set_param(n->node, SPA_PARAM_PortConfig, 0, param); diff --git a/src/modules/module-avb.c b/src/modules/module-avb.c new file mode 100644 index 0000000000000000000000000000000000000000..e70c6503183f1ade1b9a3fb6300db75b25628b18 --- /dev/null +++ b/src/modules/module-avb.c @@ -0,0 +1,130 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include "config.h" + +#include <spa/utils/result.h> +#include <spa/utils/string.h> +#include <spa/utils/json.h> + +#include <pipewire/impl.h> +#include <pipewire/private.h> +#include <pipewire/i18n.h> + +#include "module-avb/avb.h" + +/** \page page_module_avb PipeWire Module: AVB + */ + +#define NAME "avb" + +PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); +#define PW_LOG_TOPIC_DEFAULT mod_topic + +#define MODULE_USAGE " " + +static const struct spa_dict_item module_props[] = { + { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { PW_KEY_MODULE_DESCRIPTION, "Manage an AVB network" }, + { PW_KEY_MODULE_USAGE, MODULE_USAGE }, + { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, +}; + + +struct impl { + struct pw_context *context; + + struct pw_impl_module *module; + struct spa_hook module_listener; + + struct pw_avb *avb; +}; + +static void impl_free(struct impl *impl) +{ + free(impl); +} + +static void module_destroy(void *data) +{ + struct impl *impl = data; + spa_hook_remove(&impl->module_listener); + impl_free(impl); +} + +static const struct pw_impl_module_events module_events = { + PW_VERSION_IMPL_MODULE_EVENTS, + .destroy = module_destroy, +}; + +SPA_EXPORT +int pipewire__module_init(struct pw_impl_module *module, const char *args) +{ + struct pw_context *context = pw_impl_module_get_context(module); + struct pw_properties *props; + struct impl *impl; + int res; + + PW_LOG_TOPIC_INIT(mod_topic); + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + goto error_errno; + + pw_log_debug("module %p: new %s", impl, args); + + if (args == NULL) + args = ""; + + props = pw_properties_new_string(args); + if (props == NULL) + goto error_errno; + + impl->module = module; + impl->context = context; + + impl->avb = pw_avb_new(context, props, 0); + if (impl->avb == NULL) + goto error_errno; + + pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); + + pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); + + return 0; + +error_errno: + res = -errno; + if (impl) + impl_free(impl); + return res; +} diff --git a/src/modules/module-avb/aaf.h b/src/modules/module-avb/aaf.h new file mode 100644 index 0000000000000000000000000000000000000000..b444ce25101d0bf385a1fe5fd52e3eafbee1ef6d --- /dev/null +++ b/src/modules/module-avb/aaf.h @@ -0,0 +1,102 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_AAF_H +#define AVB_AAF_H + +struct avb_packet_aaf { + uint8_t subtype; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned sv:1; + unsigned version:3; + unsigned mr:1; + unsigned _r1:1; + unsigned gv:1; + unsigned tv:1; + + uint8_t seq_num; + + unsigned _r2:7; + unsigned tu:1; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned tv:1; + unsigned gv:1; + unsigned _r1:1; + unsigned mr:1; + unsigned version:3; + unsigned sv:1; + + uint8_t seq_num; + + unsigned tu:1; + unsigned _r2:7; +#endif + uint64_t stream_id; + uint32_t timestamp; +#define AVB_AAF_FORMAT_USER 0x00 +#define AVB_AAF_FORMAT_FLOAT_32BIT 0x01 +#define AVB_AAF_FORMAT_INT_32BIT 0x02 +#define AVB_AAF_FORMAT_INT_24BIT 0x03 +#define AVB_AAF_FORMAT_INT_16BIT 0x04 +#define AVB_AAF_FORMAT_AES3_32BIT 0x05 + uint8_t format; + +#define AVB_AAF_PCM_NSR_USER 0x00 +#define AVB_AAF_PCM_NSR_8KHZ 0x01 +#define AVB_AAF_PCM_NSR_16KHZ 0x02 +#define AVB_AAF_PCM_NSR_32KHZ 0x03 +#define AVB_AAF_PCM_NSR_44_1KHZ 0x04 +#define AVB_AAF_PCM_NSR_48KHZ 0x05 +#define AVB_AAF_PCM_NSR_88_2KHZ 0x06 +#define AVB_AAF_PCM_NSR_96KHZ 0x07 +#define AVB_AAF_PCM_NSR_176_4KHZ 0x08 +#define AVB_AAF_PCM_NSR_192KHZ 0x09 +#define AVB_AAF_PCM_NSR_24KHZ 0x0A +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned nsr:4; + unsigned _r3:4; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned _r3:4; + unsigned nsr:4; +#endif + uint8_t chan_per_frame; + uint8_t bit_depth; + uint16_t data_len; + +#define AVB_AAF_PCM_SP_NORMAL 0x00 +#define AVB_AAF_PCM_SP_SPARSE 0x01 +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned _r4:3; + unsigned sp:1; + unsigned event:4; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned event:4; + unsigned sp:1; + unsigned _r4:3; +#endif + uint8_t _r5; + uint8_t payload[0]; +} __attribute__ ((__packed__)); + +#endif /* AVB_AAF_H */ diff --git a/src/modules/module-avb/acmp.c b/src/modules/module-avb/acmp.c new file mode 100644 index 0000000000000000000000000000000000000000..18ea1ba92c3948a09579f6e75a3a146c56be1d42 --- /dev/null +++ b/src/modules/module-avb/acmp.c @@ -0,0 +1,477 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <spa/utils/json.h> +#include <spa/debug/mem.h> + +#include <pipewire/pipewire.h> + +#include "acmp.h" +#include "msrp.h" +#include "internal.h" +#include "stream.h" + +static const uint8_t mac[6] = AVB_BROADCAST_MAC; + +struct pending { + struct spa_list link; + uint64_t last_time; + uint64_t timeout; + uint16_t old_sequence_id; + uint16_t sequence_id; + uint16_t retry; + size_t size; + void *ptr; +}; + +struct acmp { + struct server *server; + struct spa_hook server_listener; + +#define PENDING_TALKER 0 +#define PENDING_LISTENER 1 +#define PENDING_CONTROLLER 2 + struct spa_list pending[3]; + uint16_t sequence_id[3]; +}; + +static void *pending_new(struct acmp *acmp, uint32_t type, uint64_t now, uint32_t timeout_ms, + const void *m, size_t size) +{ + struct pending *p; + struct avb_ethernet_header *h; + struct avb_packet_acmp *pm; + + p = calloc(1, sizeof(*p) + size); + if (p == NULL) + return NULL; + p->last_time = now; + p->timeout = timeout_ms * SPA_NSEC_PER_MSEC; + p->sequence_id = acmp->sequence_id[type]++; + p->size = size; + p->ptr = SPA_PTROFF(p, sizeof(*p), void); + memcpy(p->ptr, m, size); + + h = p->ptr; + pm = SPA_PTROFF(h, sizeof(*h), void); + p->old_sequence_id = ntohs(pm->sequence_id); + pm->sequence_id = htons(p->sequence_id); + spa_list_append(&acmp->pending[type], &p->link); + + return p->ptr; +} + +static struct pending *pending_find(struct acmp *acmp, uint32_t type, uint16_t sequence_id) +{ + struct pending *p; + spa_list_for_each(p, &acmp->pending[type], link) + if (p->sequence_id == sequence_id) + return p; + return NULL; +} + +static void pending_free(struct acmp *acmp, struct pending *p) +{ + spa_list_remove(&p->link); + free(p); +} + +struct msg_info { + uint16_t type; + const char *name; + int (*handle) (struct acmp *acmp, uint64_t now, const void *m, int len); +}; + +static int reply_not_supported(struct acmp *acmp, uint8_t type, const void *m, int len) +{ + struct server *server = acmp->server; + uint8_t buf[len]; + struct avb_ethernet_header *h = (void*)buf; + struct avb_packet_acmp *reply = SPA_PTROFF(h, sizeof(*h), void); + + memcpy(h, m, len); + AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, type); + AVB_PACKET_ACMP_SET_STATUS(reply, AVB_ACMP_STATUS_NOT_SUPPORTED); + + return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, len); +} + +static int retry_pending(struct acmp *acmp, uint64_t now, struct pending *p) +{ + struct server *server = acmp->server; + struct avb_ethernet_header *h = p->ptr; + p->retry++; + p->last_time = now; + return avb_server_send_packet(server, h->dest, AVB_TSN_ETH, p->ptr, p->size); +} + +static int handle_connect_tx_command(struct acmp *acmp, uint64_t now, const void *m, int len) +{ + struct server *server = acmp->server; + uint8_t buf[len]; + struct avb_ethernet_header *h = (void*)buf; + struct avb_packet_acmp *reply = SPA_PTROFF(h, sizeof(*h), void); + const struct avb_packet_acmp *p = SPA_PTROFF(m, sizeof(*h), void); + int status = AVB_ACMP_STATUS_SUCCESS; + struct stream *stream; + + if (be64toh(p->talker_guid) != server->entity_id) + return 0; + + memcpy(buf, m, len); + stream = server_find_stream(server, SPA_DIRECTION_OUTPUT, + reply->talker_unique_id); + if (stream == NULL) { + status = AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX; + goto done; + } + + AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_RESPONSE); + reply->stream_id = htobe64(stream->id); + + stream_activate(stream, now); + + memcpy(reply->stream_dest_mac, stream->addr, 6); + reply->connection_count = htons(1); + reply->stream_vlan_id = htons(stream->vlan_id); + +done: + AVB_PACKET_ACMP_SET_STATUS(reply, status); + return avb_server_send_packet(server, h->dest, AVB_TSN_ETH, buf, len); +} + +static int handle_connect_tx_response(struct acmp *acmp, uint64_t now, const void *m, int len) +{ + struct server *server = acmp->server; + struct avb_ethernet_header *h; + const struct avb_packet_acmp *resp = SPA_PTROFF(m, sizeof(*h), void); + struct avb_packet_acmp *reply; + struct pending *pending; + uint16_t sequence_id; + struct stream *stream; + int res; + + if (be64toh(resp->listener_guid) != server->entity_id) + return 0; + + sequence_id = ntohs(resp->sequence_id); + + pending = pending_find(acmp, PENDING_TALKER, sequence_id); + if (pending == NULL) + return 0; + + h = pending->ptr; + pending->size = SPA_MIN((int)pending->size, len); + memcpy(h, m, pending->size); + + reply = SPA_PTROFF(h, sizeof(*h), void); + reply->sequence_id = htons(pending->old_sequence_id); + AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_RESPONSE); + + stream = server_find_stream(server, SPA_DIRECTION_INPUT, + ntohs(reply->listener_unique_id)); + if (stream == NULL) + return 0; + + stream->peer_id = be64toh(reply->stream_id); + memcpy(stream->addr, reply->stream_dest_mac, 6); + stream_activate(stream, now); + + res = avb_server_send_packet(server, h->dest, AVB_TSN_ETH, h, pending->size); + + pending_free(acmp, pending); + + return res; +} + +static int handle_disconnect_tx_command(struct acmp *acmp, uint64_t now, const void *m, int len) +{ + struct server *server = acmp->server; + uint8_t buf[len]; + struct avb_ethernet_header *h = (void*)buf; + struct avb_packet_acmp *reply = SPA_PTROFF(h, sizeof(*h), void); + const struct avb_packet_acmp *p = SPA_PTROFF(m, sizeof(*h), void); + int status = AVB_ACMP_STATUS_SUCCESS; + struct stream *stream; + + if (be64toh(p->talker_guid) != server->entity_id) + return 0; + + memcpy(buf, m, len); + stream = server_find_stream(server, SPA_DIRECTION_OUTPUT, + reply->talker_unique_id); + if (stream == NULL) { + status = AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX; + goto done; + } + + AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_RESPONSE); + + stream_deactivate(stream, now); + +done: + AVB_PACKET_ACMP_SET_STATUS(reply, status); + return avb_server_send_packet(server, h->dest, AVB_TSN_ETH, buf, len); +} + +static int handle_disconnect_tx_response(struct acmp *acmp, uint64_t now, const void *m, int len) +{ + struct server *server = acmp->server; + struct avb_ethernet_header *h; + struct avb_packet_acmp *reply; + const struct avb_packet_acmp *resp = SPA_PTROFF(m, sizeof(*h), void); + struct pending *pending; + uint16_t sequence_id; + struct stream *stream; + int res; + + if (be64toh(resp->listener_guid) != server->entity_id) + return 0; + + sequence_id = ntohs(resp->sequence_id); + + pending = pending_find(acmp, PENDING_TALKER, sequence_id); + if (pending == NULL) + return 0; + + h = pending->ptr; + pending->size = SPA_MIN((int)pending->size, len); + memcpy(h, m, pending->size); + + reply = SPA_PTROFF(h, sizeof(*h), void); + reply->sequence_id = htons(pending->old_sequence_id); + AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_RESPONSE); + + stream = server_find_stream(server, SPA_DIRECTION_INPUT, + reply->listener_unique_id); + if (stream == NULL) + return 0; + + stream_deactivate(stream, now); + + res = avb_server_send_packet(server, h->dest, AVB_TSN_ETH, h, pending->size); + + pending_free(acmp, pending); + + return res; +} + +static int handle_connect_rx_command(struct acmp *acmp, uint64_t now, const void *m, int len) +{ + struct server *server = acmp->server; + struct avb_ethernet_header *h; + const struct avb_packet_acmp *p = SPA_PTROFF(m, sizeof(*h), void); + struct avb_packet_acmp *cmd; + + if (be64toh(p->listener_guid) != server->entity_id) + return 0; + + h = pending_new(acmp, PENDING_TALKER, now, + AVB_ACMP_TIMEOUT_CONNECT_TX_COMMAND_MS, m, len); + if (h == NULL) + return -errno; + + cmd = SPA_PTROFF(h, sizeof(*h), void); + AVB_PACKET_ACMP_SET_MESSAGE_TYPE(cmd, AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_COMMAND); + AVB_PACKET_ACMP_SET_STATUS(cmd, AVB_ACMP_STATUS_SUCCESS); + + return avb_server_send_packet(server, h->dest, AVB_TSN_ETH, h, len); +} + +static int handle_ignore(struct acmp *acmp, uint64_t now, const void *m, int len) +{ + return 0; +} + +static int handle_disconnect_rx_command(struct acmp *acmp, uint64_t now, const void *m, int len) +{ + struct server *server = acmp->server; + struct avb_ethernet_header *h; + const struct avb_packet_acmp *p = SPA_PTROFF(m, sizeof(*h), void); + struct avb_packet_acmp *cmd; + + if (be64toh(p->listener_guid) != server->entity_id) + return 0; + + h = pending_new(acmp, PENDING_TALKER, now, + AVB_ACMP_TIMEOUT_DISCONNECT_TX_COMMAND_MS, m, len); + if (h == NULL) + return -errno; + + cmd = SPA_PTROFF(h, sizeof(*h), void); + AVB_PACKET_ACMP_SET_MESSAGE_TYPE(cmd, AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_COMMAND); + AVB_PACKET_ACMP_SET_STATUS(cmd, AVB_ACMP_STATUS_SUCCESS); + + return avb_server_send_packet(server, h->dest, AVB_TSN_ETH, h, len); +} + +static const struct msg_info msg_info[] = { + { AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_COMMAND, "connect-tx-command", handle_connect_tx_command, }, + { AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_RESPONSE, "connect-tx-response", handle_connect_tx_response, }, + { AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_COMMAND, "disconnect-tx-command", handle_disconnect_tx_command, }, + { AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_RESPONSE, "disconnect-tx-response", handle_disconnect_tx_response, }, + { AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_COMMAND, "get-tx-state-command", NULL, }, + { AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_RESPONSE, "get-tx-state-response", handle_ignore, }, + { AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_COMMAND, "connect-rx-command", handle_connect_rx_command, }, + { AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_RESPONSE, "connect-rx-response", handle_ignore, }, + { AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_COMMAND, "disconnect-rx-command", handle_disconnect_rx_command, }, + { AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_RESPONSE, "disconnect-rx-response", handle_ignore, }, + { AVB_ACMP_MESSAGE_TYPE_GET_RX_STATE_COMMAND, "get-rx-state-command", NULL, }, + { AVB_ACMP_MESSAGE_TYPE_GET_RX_STATE_RESPONSE, "get-rx-state-response", handle_ignore, }, + { AVB_ACMP_MESSAGE_TYPE_GET_TX_CONNECTION_COMMAND, "get-tx-connection-command", NULL, }, + { AVB_ACMP_MESSAGE_TYPE_GET_TX_CONNECTION_RESPONSE, "get-tx-connection-response", handle_ignore, }, +}; + +static inline const struct msg_info *find_msg_info(uint16_t type, const char *name) +{ + uint32_t i; + for (i = 0; i < SPA_N_ELEMENTS(msg_info); i++) { + if ((name == NULL && type == msg_info[i].type) || + (name != NULL && spa_streq(name, msg_info[i].name))) + return &msg_info[i]; + } + return NULL; +} + +static int acmp_message(void *data, uint64_t now, const void *message, int len) +{ + struct acmp *acmp = data; + struct server *server = acmp->server; + const struct avb_ethernet_header *h = message; + const struct avb_packet_acmp *p = SPA_PTROFF(h, sizeof(*h), void); + const struct msg_info *info; + int message_type; + + if (ntohs(h->type) != AVB_TSN_ETH) + return 0; + if (memcmp(h->dest, mac, 6) != 0 && + memcmp(h->dest, server->mac_addr, 6) != 0) + return 0; + + if (AVB_PACKET_GET_SUBTYPE(&p->hdr) != AVB_SUBTYPE_ACMP) + return 0; + + message_type = AVB_PACKET_ACMP_GET_MESSAGE_TYPE(p); + + info = find_msg_info(message_type, NULL); + if (info == NULL) + return 0; + + pw_log_info("got ACMP message %s", info->name); + + if (info->handle == NULL) + return reply_not_supported(acmp, message_type | 1, message, len); + + return info->handle(acmp, now, message, len); +} + +static void acmp_destroy(void *data) +{ + struct acmp *acmp = data; + spa_hook_remove(&acmp->server_listener); + free(acmp); +} + +static void check_timeout(struct acmp *acmp, uint64_t now, uint16_t type) +{ + struct pending *p, *t; + + spa_list_for_each_safe(p, t, &acmp->pending[type], link) { + if (p->last_time + p->timeout > now) + continue; + + if (p->retry == 0) { + pw_log_info("%p: pending timeout, retry", p); + retry_pending(acmp, now, p); + } else { + pw_log_info("%p: pending timeout, fail", p); + pending_free(acmp, p); + } + } +} +static void acmp_periodic(void *data, uint64_t now) +{ + struct acmp *acmp = data; + check_timeout(acmp, now, PENDING_TALKER); + check_timeout(acmp, now, PENDING_LISTENER); + check_timeout(acmp, now, PENDING_CONTROLLER); +} + +static int do_help(struct acmp *acmp, const char *args, FILE *out) +{ + fprintf(out, "{ \"type\": \"help\"," + "\"text\": \"" + "/adp/help: this help \\n" + "\" }"); + return 0; +} + +static int acmp_command(void *data, uint64_t now, const char *command, const char *args, FILE *out) +{ + struct acmp *acmp = data; + int res; + + if (!spa_strstartswith(command, "/acmp/")) + return 0; + + command += strlen("/acmp/"); + + if (spa_streq(command, "help")) + res = do_help(acmp, args, out); + else + res = -ENOTSUP; + + return res; +} + +static const struct server_events server_events = { + AVB_VERSION_SERVER_EVENTS, + .destroy = acmp_destroy, + .message = acmp_message, + .periodic = acmp_periodic, + .command = acmp_command +}; + +struct avb_acmp *avb_acmp_register(struct server *server) +{ + struct acmp *acmp; + + acmp = calloc(1, sizeof(*acmp)); + if (acmp == NULL) + return NULL; + + acmp->server = server; + spa_list_init(&acmp->pending[PENDING_TALKER]); + spa_list_init(&acmp->pending[PENDING_LISTENER]); + spa_list_init(&acmp->pending[PENDING_CONTROLLER]); + + avdecc_server_add_listener(server, &acmp->server_listener, &server_events, acmp); + + return (struct avb_acmp*)acmp; +} + +void avb_acmp_unregister(struct avb_acmp *acmp) +{ + acmp_destroy(acmp); +} diff --git a/src/modules/module-avb/acmp.h b/src/modules/module-avb/acmp.h new file mode 100644 index 0000000000000000000000000000000000000000..5a41c661f2ce8b1939eb797eeaf1433f6c71ea4c --- /dev/null +++ b/src/modules/module-avb/acmp.h @@ -0,0 +1,99 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_ACMP_H +#define AVB_ACMP_H + +#include "packets.h" +#include "internal.h" + +#define AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_COMMAND 0 +#define AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_RESPONSE 1 +#define AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_COMMAND 2 +#define AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_RESPONSE 3 +#define AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_COMMAND 4 +#define AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_RESPONSE 5 +#define AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_COMMAND 6 +#define AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_RESPONSE 7 +#define AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_COMMAND 8 +#define AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_RESPONSE 9 +#define AVB_ACMP_MESSAGE_TYPE_GET_RX_STATE_COMMAND 10 +#define AVB_ACMP_MESSAGE_TYPE_GET_RX_STATE_RESPONSE 11 +#define AVB_ACMP_MESSAGE_TYPE_GET_TX_CONNECTION_COMMAND 12 +#define AVB_ACMP_MESSAGE_TYPE_GET_TX_CONNECTION_RESPONSE 13 + +#define AVB_ACMP_STATUS_SUCCESS 0 +#define AVB_ACMP_STATUS_LISTENER_UNKNOWN_ID 1 +#define AVB_ACMP_STATUS_TALKER_UNKNOWN_ID 2 +#define AVB_ACMP_STATUS_TALKER_DEST_MAC_FAIL 3 +#define AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX 4 +#define AVB_ACMP_STATUS_TALKER_NO_BANDWIDTH 5 +#define AVB_ACMP_STATUS_TALKER_EXCLUSIVE 6 +#define AVB_ACMP_STATUS_LISTENER_TALKER_TIMEOUT 7 +#define AVB_ACMP_STATUS_LISTENER_EXCLUSIVE 8 +#define AVB_ACMP_STATUS_STATE_UNAVAILABLE 9 +#define AVB_ACMP_STATUS_NOT_CONNECTED 10 +#define AVB_ACMP_STATUS_NO_SUCH_CONNECTION 11 +#define AVB_ACMP_STATUS_COULD_NOT_SEND_MESSAGE 12 +#define AVB_ACMP_STATUS_TALKER_MISBEHAVING 13 +#define AVB_ACMP_STATUS_LISTENER_MISBEHAVING 14 +#define AVB_ACMP_STATUS_RESERVED 15 +#define AVB_ACMP_STATUS_CONTROLLER_NOT_AUTHORIZED 16 +#define AVB_ACMP_STATUS_INCOMPATIBLE_REQUEST 17 +#define AVB_ACMP_STATUS_LISTENER_INVALID_CONNECTION 18 +#define AVB_ACMP_STATUS_NOT_SUPPORTED 31 + +#define AVB_ACMP_TIMEOUT_CONNECT_TX_COMMAND_MS 2000 +#define AVB_ACMP_TIMEOUT_DISCONNECT_TX_COMMAND_MS 200 +#define AVB_ACMP_TIMEOUT_GET_TX_STATE_COMMAND 200 +#define AVB_ACMP_TIMEOUT_CONNECT_RX_COMMAND_MS 4500 +#define AVB_ACMP_TIMEOUT_DISCONNECT_RX_COMMAND_MS 500 +#define AVB_ACMP_TIMEOUT_GET_RX_STATE_COMMAND_MS 200 +#define AVB_ACMP_TIMEOUT_GET_TX_CONNECTION_COMMAND 200 + +struct avb_packet_acmp { + struct avb_packet_header hdr; + uint64_t stream_id; + uint64_t controller_guid; + uint64_t talker_guid; + uint64_t listener_guid; + uint16_t talker_unique_id; + uint16_t listener_unique_id; + char stream_dest_mac[6]; + uint16_t connection_count; + uint16_t sequence_id; + uint16_t flags; + uint16_t stream_vlan_id; + uint16_t reserved; +} __attribute__ ((__packed__)); + +#define AVB_PACKET_ACMP_SET_MESSAGE_TYPE(p,v) AVB_PACKET_SET_SUB1(&(p)->hdr, v) +#define AVB_PACKET_ACMP_SET_STATUS(p,v) AVB_PACKET_SET_SUB2(&(p)->hdr, v) + +#define AVB_PACKET_ACMP_GET_MESSAGE_TYPE(p) AVB_PACKET_GET_SUB1(&(p)->hdr) +#define AVB_PACKET_ACMP_GET_STATUS(p) AVB_PACKET_GET_SUB2(&(p)->hdr) + +struct avb_acmp *avb_acmp_register(struct server *server); + +#endif /* AVB_ACMP_H */ diff --git a/src/modules/module-avb/adp.c b/src/modules/module-avb/adp.c new file mode 100644 index 0000000000000000000000000000000000000000..6b13c41b7739ff11445cec48a16df62aa9fa7b61 --- /dev/null +++ b/src/modules/module-avb/adp.c @@ -0,0 +1,381 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <spa/utils/json.h> + +#include <pipewire/pipewire.h> + +#include "adp.h" +#include "aecp-aem-descriptors.h" +#include "internal.h" +#include "utils.h" + +static const uint8_t mac[6] = AVB_BROADCAST_MAC; + +struct entity { + struct spa_list link; + uint64_t entity_id; + uint64_t last_time; + int valid_time; + unsigned advertise:1; + size_t len; + uint8_t buf[128]; +}; + +struct adp { + struct server *server; + struct spa_hook server_listener; + + struct spa_list entities; + uint32_t available_index; +}; + +static struct entity *find_entity_by_id(struct adp *adp, uint64_t id) +{ + struct entity *e; + spa_list_for_each(e, &adp->entities, link) + if (e->entity_id == id) + return e; + return NULL; +} +static void entity_free(struct entity *e) +{ + spa_list_remove(&e->link); + free(e); +} + +static int send_departing(struct adp *adp, uint64_t now, struct entity *e) +{ + struct avb_ethernet_header *h = (void*)e->buf; + struct avb_packet_adp *p = SPA_PTROFF(h, sizeof(*h), void); + + AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_DEPARTING); + p->available_index = htonl(adp->available_index++); + avb_server_send_packet(adp->server, mac, AVB_TSN_ETH, e->buf, e->len); + e->last_time = now; + return 0; +} + +static int send_advertise(struct adp *adp, uint64_t now, struct entity *e) +{ + struct avb_ethernet_header *h = (void*)e->buf; + struct avb_packet_adp *p = SPA_PTROFF(h, sizeof(*h), void); + + AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE); + p->available_index = htonl(adp->available_index++); + avb_server_send_packet(adp->server, mac, AVB_TSN_ETH, e->buf, e->len); + e->last_time = now; + return 0; +} + +static int send_discover(struct adp *adp, uint64_t entity_id) +{ + uint8_t buf[128]; + struct avb_ethernet_header *h = (void*)buf; + struct avb_packet_adp *p = SPA_PTROFF(h, sizeof(*h), void); + size_t len = sizeof(*h) + sizeof(*p); + + spa_memzero(buf, sizeof(buf)); + AVB_PACKET_SET_SUBTYPE(&p->hdr, AVB_SUBTYPE_ADP); + AVB_PACKET_SET_LENGTH(&p->hdr, AVB_ADP_CONTROL_DATA_LENGTH); + AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_DISCOVER); + p->entity_id = htonl(entity_id); + avb_server_send_packet(adp->server, mac, AVB_TSN_ETH, buf, len); + return 0; +} + +static int adp_message(void *data, uint64_t now, const void *message, int len) +{ + struct adp *adp = data; + struct server *server = adp->server; + const struct avb_ethernet_header *h = message; + const struct avb_packet_adp *p = SPA_PTROFF(h, sizeof(*h), void); + struct entity *e; + int message_type; + char buf[128]; + uint64_t entity_id; + + if (ntohs(h->type) != AVB_TSN_ETH) + return 0; + if (memcmp(h->dest, mac, 6) != 0 && + memcmp(h->dest, server->mac_addr, 6) != 0) + return 0; + + if (AVB_PACKET_GET_SUBTYPE(&p->hdr) != AVB_SUBTYPE_ADP || + AVB_PACKET_GET_LENGTH(&p->hdr) < AVB_ADP_CONTROL_DATA_LENGTH) + return 0; + + message_type = AVB_PACKET_ADP_GET_MESSAGE_TYPE(p); + entity_id = be64toh(p->entity_id); + + e = find_entity_by_id(adp, entity_id); + + switch (message_type) { + case AVB_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE: + if (e == NULL) { + e = calloc(1, sizeof(*e)); + if (e == NULL) + return -errno; + + memcpy(e->buf, message, len); + e->len = len; + e->valid_time = AVB_PACKET_ADP_GET_VALID_TIME(p); + e->entity_id = entity_id; + spa_list_append(&adp->entities, &e->link); + pw_log_info("entity %s available", + avb_utils_format_id(buf, sizeof(buf), entity_id)); + } + e->last_time = now; + break; + case AVB_ADP_MESSAGE_TYPE_ENTITY_DEPARTING: + if (e != NULL) { + pw_log_info("entity %s departing", + avb_utils_format_id(buf, sizeof(buf), entity_id)); + entity_free(e); + } + break; + case AVB_ADP_MESSAGE_TYPE_ENTITY_DISCOVER: + pw_log_info("entity %s advertise", + avb_utils_format_id(buf, sizeof(buf), entity_id)); + if (entity_id == 0UL) { + spa_list_for_each(e, &adp->entities, link) + if (e->advertise) + send_advertise(adp, now, e); + } else if (e != NULL && + e->advertise && e->entity_id == entity_id) { + send_advertise(adp, now, e); + } + break; + default: + return -EINVAL; + } + return 0; +} + +static void adp_destroy(void *data) +{ + struct adp *adp = data; + spa_hook_remove(&adp->server_listener); + free(adp); +} + +static void check_timeout(struct adp *adp, uint64_t now) +{ + struct entity *e, *t; + char buf[128]; + + spa_list_for_each_safe(e, t, &adp->entities, link) { + if (e->last_time + (e->valid_time + 2) * SPA_NSEC_PER_SEC > now) + continue; + + pw_log_info("entity %s timeout", + avb_utils_format_id(buf, sizeof(buf), e->entity_id)); + + if (e->advertise) + send_departing(adp, now, e); + + entity_free(e); + } +} +static void check_readvertize(struct adp *adp, uint64_t now, struct entity *e) +{ + char buf[128]; + + if (!e->advertise) + return; + + if (e->last_time + (e->valid_time / 2) * SPA_NSEC_PER_SEC > now) + return; + + pw_log_debug("entity %s readvertise", + avb_utils_format_id(buf, sizeof(buf), e->entity_id)); + + send_advertise(adp, now, e); +} + +static int check_advertise(struct adp *adp, uint64_t now) +{ + struct server *server = adp->server; + const struct descriptor *d; + struct avb_aem_desc_entity *entity; + struct avb_aem_desc_avb_interface *avb_interface; + struct entity *e; + uint64_t entity_id; + struct avb_ethernet_header *h; + struct avb_packet_adp *p; + char buf[128]; + + d = server_find_descriptor(server, AVB_AEM_DESC_ENTITY, 0); + if (d == NULL) + return 0; + + entity = d->ptr; + entity_id = be64toh(entity->entity_id); + + if ((e = find_entity_by_id(adp, entity_id)) != NULL) { + if (e->advertise) + check_readvertize(adp, now, e); + return 0; + } + + d = server_find_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, 0); + avb_interface = d ? d->ptr : NULL; + + pw_log_info("entity %s advertise", + avb_utils_format_id(buf, sizeof(buf), entity_id)); + + e = calloc(1, sizeof(*e)); + if (e == NULL) + return -errno; + + e->advertise = true; + e->valid_time = 10; + e->last_time = now; + e->entity_id = entity_id; + e->len = sizeof(*h) + sizeof(*p); + + h = (void*)e->buf; + p = SPA_PTROFF(h, sizeof(*h), void); + AVB_PACKET_SET_LENGTH(&p->hdr, AVB_ADP_CONTROL_DATA_LENGTH); + AVB_PACKET_SET_SUBTYPE(&p->hdr, AVB_SUBTYPE_ADP); + AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE); + AVB_PACKET_ADP_SET_VALID_TIME(p, e->valid_time); + + p->entity_id = entity->entity_id; + p->entity_model_id = entity->entity_model_id; + p->entity_capabilities = entity->entity_capabilities; + p->talker_stream_sources = entity->talker_stream_sources; + p->talker_capabilities = entity->talker_capabilities; + p->listener_stream_sinks = entity->listener_stream_sinks; + p->listener_capabilities = entity->listener_capabilities; + p->controller_capabilities = entity->controller_capabilities; + p->available_index = entity->available_index; + if (avb_interface) { + p->gptp_grandmaster_id = avb_interface->clock_identity; + p->gptp_domain_number = avb_interface->domain_number; + } + p->identify_control_index = 0; + p->interface_index = 0; + p->association_id = entity->association_id; + + spa_list_append(&adp->entities, &e->link); + + return 0; +} + +static void adp_periodic(void *data, uint64_t now) +{ + struct adp *adp = data; + check_timeout(adp, now); + check_advertise(adp, now); +} + +static int do_help(struct adp *adp, const char *args, FILE *out) +{ + fprintf(out, "{ \"type\": \"help\"," + "\"text\": \"" + "/adp/help: this help \\n" + "/adp/discover [{ \"entity-id\": <id> }] : trigger discover\\n" + "\" }"); + return 0; +} + +static int do_discover(struct adp *adp, const char *args, FILE *out) +{ + struct spa_json it[2]; + char key[128]; + uint64_t entity_id = 0ULL; + + spa_json_init(&it[0], args, strlen(args)); + if (spa_json_enter_object(&it[0], &it[1]) <= 0) + return -EINVAL; + + while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) { + int len; + const char *value; + uint64_t id_val; + + if ((len = spa_json_next(&it[1], &value)) <= 0) + break; + + if (spa_json_is_null(value, len)) + continue; + + if (spa_streq(key, "entity-id")) { + if (avb_utils_parse_id(value, len, &id_val) >= 0) + entity_id = id_val; + } + } + send_discover(adp, entity_id); + return 0; +} + +static int adp_command(void *data, uint64_t now, const char *command, const char *args, FILE *out) +{ + struct adp *adp = data; + int res; + + if (!spa_strstartswith(command, "/adp/")) + return 0; + + command += strlen("/adp/"); + + if (spa_streq(command, "help")) + res = do_help(adp, args, out); + else if (spa_streq(command, "discover")) + res = do_discover(adp, args, out); + else + res = -ENOTSUP; + + return res; +} + +static const struct server_events server_events = { + AVB_VERSION_SERVER_EVENTS, + .destroy = adp_destroy, + .message = adp_message, + .periodic = adp_periodic, + .command = adp_command +}; + +struct avb_adp *avb_adp_register(struct server *server) +{ + struct adp *adp; + + adp = calloc(1, sizeof(*adp)); + if (adp == NULL) + return NULL; + + adp->server = server; + spa_list_init(&adp->entities); + + avdecc_server_add_listener(server, &adp->server_listener, &server_events, adp); + + return (struct avb_adp*)adp; +} + +void avb_adp_unregister(struct avb_adp *adp) +{ + adp_destroy(adp); +} diff --git a/src/modules/module-avb/adp.h b/src/modules/module-avb/adp.h new file mode 100644 index 0000000000000000000000000000000000000000..c546088d8ac130b7dfb1f5939ea6057efd2d78df --- /dev/null +++ b/src/modules/module-avb/adp.h @@ -0,0 +1,105 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_ADP_H +#define AVB_ADP_H + +#include "packets.h" +#include "internal.h" + +#define AVB_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE 0 +#define AVB_ADP_MESSAGE_TYPE_ENTITY_DEPARTING 1 +#define AVB_ADP_MESSAGE_TYPE_ENTITY_DISCOVER 2 + +#define AVB_ADP_ENTITY_CAPABILITY_EFU_MODE (1u<<0) +#define AVB_ADP_ENTITY_CAPABILITY_ADDRESS_ACCESS_SUPPORTED (1u<<1) +#define AVB_ADP_ENTITY_CAPABILITY_GATEWAY_ENTITY (1u<<2) +#define AVB_ADP_ENTITY_CAPABILITY_AEM_SUPPORTED (1u<<3) +#define AVB_ADP_ENTITY_CAPABILITY_LEGACY_AVC (1u<<4) +#define AVB_ADP_ENTITY_CAPABILITY_ASSOCIATION_ID_SUPPORTED (1u<<5) +#define AVB_ADP_ENTITY_CAPABILITY_ASSOCIATION_ID_VALID (1u<<6) +#define AVB_ADP_ENTITY_CAPABILITY_VENDOR_UNIQUE_SUPPORTED (1u<<7) +#define AVB_ADP_ENTITY_CAPABILITY_CLASS_A_SUPPORTED (1u<<8) +#define AVB_ADP_ENTITY_CAPABILITY_CLASS_B_SUPPORTED (1u<<9) +#define AVB_ADP_ENTITY_CAPABILITY_GPTP_SUPPORTED (1u<<10) +#define AVB_ADP_ENTITY_CAPABILITY_AEM_AUTHENTICATION_SUPPORTED (1u<<11) +#define AVB_ADP_ENTITY_CAPABILITY_AEM_AUTHENTICATION_REQUIRED (1u<<12) +#define AVB_ADP_ENTITY_CAPABILITY_AEM_PERSISTENT_ACQUIRE_SUPPORTED (1u<<13) +#define AVB_ADP_ENTITY_CAPABILITY_AEM_IDENTIFY_CONTROL_INDEX_VALID (1u<<14) +#define AVB_ADP_ENTITY_CAPABILITY_AEM_INTERFACE_INDEX_VALID (1u<<15) +#define AVB_ADP_ENTITY_CAPABILITY_GENERAL_CONTROLLER_IGNORE (1u<<16) +#define AVB_ADP_ENTITY_CAPABILITY_ENTITY_NOT_READY (1u<<17) + +#define AVB_ADP_TALKER_CAPABILITY_IMPLEMENTED (1u<<0) +#define AVB_ADP_TALKER_CAPABILITY_OTHER_SOURCE (1u<<9) +#define AVB_ADP_TALKER_CAPABILITY_CONTROL_SOURCE (1u<<10) +#define AVB_ADP_TALKER_CAPABILITY_MEDIA_CLOCK_SOURCE (1u<<11) +#define AVB_ADP_TALKER_CAPABILITY_SMPTE_SOURCE (1u<<12) +#define AVB_ADP_TALKER_CAPABILITY_MIDI_SOURCE (1u<<13) +#define AVB_ADP_TALKER_CAPABILITY_AUDIO_SOURCE (1u<<14) +#define AVB_ADP_TALKER_CAPABILITY_VIDEO_SOURCE (1u<<15) + +#define AVB_ADP_LISTENER_CAPABILITY_IMPLEMENTED (1u<<0) +#define AVB_ADP_LISTENER_CAPABILITY_OTHER_SINK (1u<<9) +#define AVB_ADP_LISTENER_CAPABILITY_CONTROL_SINK (1u<<10) +#define AVB_ADP_LISTENER_CAPABILITY_MEDIA_CLOCK_SINK (1u<<11) +#define AVB_ADP_LISTENER_CAPABILITY_SMPTE_SINK (1u<<12) +#define AVB_ADP_LISTENER_CAPABILITY_MIDI_SINK (1u<<13) +#define AVB_ADP_LISTENER_CAPABILITY_AUDIO_SINK (1u<<14) +#define AVB_ADP_LISTENER_CAPABILITY_VIDEO_SINK (1u<<15) + +#define AVB_ADP_CONTROLLER_CAPABILITY_IMPLEMENTED (1u<<0) +#define AVB_ADP_CONTROLLER_CAPABILITY_LAYER3_PROXY (1u<<1) + +#define AVB_ADP_CONTROL_DATA_LENGTH 56 + +struct avb_packet_adp { + struct avb_packet_header hdr; + uint64_t entity_id; + uint64_t entity_model_id; + uint32_t entity_capabilities; + uint16_t talker_stream_sources; + uint16_t talker_capabilities; + uint16_t listener_stream_sinks; + uint16_t listener_capabilities; + uint32_t controller_capabilities; + uint32_t available_index; + uint64_t gptp_grandmaster_id; + uint8_t gptp_domain_number; + uint8_t reserved0[3]; + uint16_t identify_control_index; + uint16_t interface_index; + uint64_t association_id; + uint32_t reserved1; +} __attribute__ ((__packed__)); + +#define AVB_PACKET_ADP_SET_MESSAGE_TYPE(p,v) AVB_PACKET_SET_SUB1(&(p)->hdr, v) +#define AVB_PACKET_ADP_SET_VALID_TIME(p,v) AVB_PACKET_SET_SUB2(&(p)->hdr, v) + +#define AVB_PACKET_ADP_GET_MESSAGE_TYPE(p) AVB_PACKET_GET_SUB1(&(p)->hdr) +#define AVB_PACKET_ADP_GET_VALID_TIME(p) AVB_PACKET_GET_SUB2(&(p)->hdr) + +struct avb_adp *avb_adp_register(struct server *server); + +#endif /* AVB_ADP_H */ diff --git a/src/modules/module-avb/aecp-aem-descriptors.h b/src/modules/module-avb/aecp-aem-descriptors.h new file mode 100644 index 0000000000000000000000000000000000000000..101c33168f23ef276bb4acebb2d99f6511fa22ce --- /dev/null +++ b/src/modules/module-avb/aecp-aem-descriptors.h @@ -0,0 +1,247 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_AECP_AEM_DESCRIPTORS_H +#define AVB_AECP_AEM_DESCRIPTORS_H + +#include "internal.h" + +#define AVB_AEM_DESC_ENTITY 0x0000 +#define AVB_AEM_DESC_CONFIGURATION 0x0001 +#define AVB_AEM_DESC_AUDIO_UNIT 0x0002 +#define AVB_AEM_DESC_VIDEO_UNIT 0x0003 +#define AVB_AEM_DESC_SENSOR_UNIT 0x0004 +#define AVB_AEM_DESC_STREAM_INPUT 0x0005 +#define AVB_AEM_DESC_STREAM_OUTPUT 0x0006 +#define AVB_AEM_DESC_JACK_INPUT 0x0007 +#define AVB_AEM_DESC_JACK_OUTPUT 0x0008 +#define AVB_AEM_DESC_AVB_INTERFACE 0x0009 +#define AVB_AEM_DESC_CLOCK_SOURCE 0x000a +#define AVB_AEM_DESC_MEMORY_OBJECT 0x000b +#define AVB_AEM_DESC_LOCALE 0x000c +#define AVB_AEM_DESC_STRINGS 0x000d +#define AVB_AEM_DESC_STREAM_PORT_INPUT 0x000e +#define AVB_AEM_DESC_STREAM_PORT_OUTPUT 0x000f +#define AVB_AEM_DESC_EXTERNAL_PORT_INPUT 0x0010 +#define AVB_AEM_DESC_EXTERNAL_PORT_OUTPUT 0x0011 +#define AVB_AEM_DESC_INTERNAL_PORT_INPUT 0x0012 +#define AVB_AEM_DESC_INTERNAL_PORT_OUTPUT 0x0013 +#define AVB_AEM_DESC_AUDIO_CLUSTER 0x0014 +#define AVB_AEM_DESC_VIDEO_CLUSTER 0x0015 +#define AVB_AEM_DESC_SENSOR_CLUSTER 0x0016 +#define AVB_AEM_DESC_AUDIO_MAP 0x0017 +#define AVB_AEM_DESC_VIDEO_MAP 0x0018 +#define AVB_AEM_DESC_SENSOR_MAP 0x0019 +#define AVB_AEM_DESC_CONTROL 0x001a +#define AVB_AEM_DESC_SIGNAL_SELECTOR 0x001b +#define AVB_AEM_DESC_MIXER 0x001c +#define AVB_AEM_DESC_MATRIX 0x001d +#define AVB_AEM_DESC_MATRIX_SIGNAL 0x001e +#define AVB_AEM_DESC_SIGNAL_SPLITTER 0x001f +#define AVB_AEM_DESC_SIGNAL_COMBINER 0x0020 +#define AVB_AEM_DESC_SIGNAL_DEMULTIPLEXER 0x0021 +#define AVB_AEM_DESC_SIGNAL_MULTIPLEXER 0x0022 +#define AVB_AEM_DESC_SIGNAL_TRANSCODER 0x0023 +#define AVB_AEM_DESC_CLOCK_DOMAIN 0x0024 +#define AVB_AEM_DESC_CONTROL_BLOCK 0x0025 +#define AVB_AEM_DESC_INVALID 0xffff + +struct avb_aem_desc_entity { + uint64_t entity_id; + uint64_t entity_model_id; + uint32_t entity_capabilities; + uint16_t talker_stream_sources; + uint16_t talker_capabilities; + uint16_t listener_stream_sinks; + uint16_t listener_capabilities; + uint32_t controller_capabilities; + uint32_t available_index; + uint64_t association_id; + char entity_name[64]; + uint16_t vendor_name_string; + uint16_t model_name_string; + char firmware_version[64]; + char group_name[64]; + char serial_number[64]; + uint16_t configurations_count; + uint16_t current_configuration; +} __attribute__ ((__packed__)); + +struct avb_aem_desc_descriptor_count { + uint16_t descriptor_type; + uint16_t descriptor_count; +} __attribute__ ((__packed__)); + +struct avb_aem_desc_configuration { + char object_name[64]; + uint16_t localized_description; + uint16_t descriptor_counts_count; + uint16_t descriptor_counts_offset; + struct avb_aem_desc_descriptor_count descriptor_counts[0]; +} __attribute__ ((__packed__)); + +struct avb_aem_desc_sampling_rate { + uint32_t pull_frequency; +} __attribute__ ((__packed__)); + +struct avb_aem_desc_audio_unit { + char object_name[64]; + uint16_t localized_description; + uint16_t clock_domain_index; + uint16_t number_of_stream_input_ports; + uint16_t base_stream_input_port; + uint16_t number_of_stream_output_ports; + uint16_t base_stream_output_port; + uint16_t number_of_external_input_ports; + uint16_t base_external_input_port; + uint16_t number_of_external_output_ports; + uint16_t base_external_output_port; + uint16_t number_of_internal_input_ports; + uint16_t base_internal_input_port; + uint16_t number_of_internal_output_ports; + uint16_t base_internal_output_port; + uint16_t number_of_controls; + uint16_t base_control; + uint16_t number_of_signal_selectors; + uint16_t base_signal_selector; + uint16_t number_of_mixers; + uint16_t base_mixer; + uint16_t number_of_matrices; + uint16_t base_matrix; + uint16_t number_of_splitters; + uint16_t base_splitter; + uint16_t number_of_combiners; + uint16_t base_combiner; + uint16_t number_of_demultiplexers; + uint16_t base_demultiplexer; + uint16_t number_of_multiplexers; + uint16_t base_multiplexer; + uint16_t number_of_transcoders; + uint16_t base_transcoder; + uint16_t number_of_control_blocks; + uint16_t base_control_block; + uint32_t current_sampling_rate; + uint16_t sampling_rates_offset; + uint16_t sampling_rates_count; + struct avb_aem_desc_sampling_rate sampling_rates[0]; +} __attribute__ ((__packed__)); + +#define AVB_AEM_DESC_STREAM_FLAG_SYNC_SOURCE (1u<<0) +#define AVB_AEM_DESC_STREAM_FLAG_CLASS_A (1u<<1) +#define AVB_AEM_DESC_STREAM_FLAG_CLASS_B (1u<<2) +#define AVB_AEM_DESC_STREAM_FLAG_SUPPORTS_ENCRYPTED (1u<<3) +#define AVB_AEM_DESC_STREAM_FLAG_PRIMARY_BACKUP_SUPPORTED (1u<<4) +#define AVB_AEM_DESC_STREAM_FLAG_PRIMARY_BACKUP_VALID (1u<<5) +#define AVB_AEM_DESC_STREAM_FLAG_SECONDARY_BACKUP_SUPPORTED (1u<<6) +#define AVB_AEM_DESC_STREAM_FLAG_SECONDARY_BACKUP_VALID (1u<<7) +#define AVB_AEM_DESC_STREAM_FLAG_TERTIARY_BACKUP_SUPPORTED (1u<<8) +#define AVB_AEM_DESC_STREAM_FLAG_TERTIARY_BACKUP_VALID (1u<<9) + +struct avb_aem_desc_stream { + char object_name[64]; + uint16_t localized_description; + uint16_t clock_domain_index; + uint16_t stream_flags; + uint64_t current_format; + uint16_t formats_offset; + uint16_t number_of_formats; + uint64_t backup_talker_entity_id_0; + uint16_t backup_talker_unique_id_0; + uint64_t backup_talker_entity_id_1; + uint16_t backup_talker_unique_id_1; + uint64_t backup_talker_entity_id_2; + uint16_t backup_talker_unique_id_2; + uint64_t backedup_talker_entity_id; + uint16_t backedup_talker_unique; + uint16_t avb_interface_index; + uint32_t buffer_length; + uint64_t stream_formats[0]; +} __attribute__ ((__packed__)); + +#define AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_GRANDMASTER_SUPPORTED (1<<0) +#define AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_SUPPORTED (1<<1) +#define AVB_AEM_DESC_AVB_INTERFACE_FLAG_SRP_SUPPORTED (1<<2) + +struct avb_aem_desc_avb_interface { + char object_name[64]; + uint16_t localized_description; + uint8_t mac_address[6]; + uint16_t interface_flags; + uint64_t clock_identity; + uint8_t priority1; + uint8_t clock_class; + uint16_t offset_scaled_log_variance; + uint8_t clock_accuracy; + uint8_t priority2; + uint8_t domain_number; + int8_t log_sync_interval; + int8_t log_announce_interval; + int8_t log_pdelay_interval; + uint16_t port_number; +} __attribute__ ((__packed__)); + +#define AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INTERNAL 0x0000 +#define AVB_AEM_DESC_CLOCK_SOURCE_TYPE_EXTERNAL 0x0001 +#define AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INPUT_STREAM 0x0002 +#define AVB_AEM_DESC_CLOCK_SOURCE_TYPE_MEDIA_CLOCK_STREAM 0x0003 +#define AVB_AEM_DESC_CLOCK_SOURCE_TYPE_EXPANSION 0xffff + +struct avb_aem_desc_clock_source { + char object_name[64]; + uint16_t localized_description; + uint16_t clock_source_flags; + uint16_t clock_source_type; + uint64_t clock_source_identifier; + uint16_t clock_source_location_type; + uint16_t clock_source_location_index; +} __attribute__ ((__packed__)); + +struct avb_aem_desc_locale { + char locale_identifier[64]; + uint16_t number_of_strings; + uint16_t base_strings; +} __attribute__ ((__packed__)); + +struct avb_aem_desc_strings { + char string_0[64]; + char string_1[64]; + char string_2[64]; + char string_3[64]; + char string_4[64]; + char string_5[64]; + char string_6[64]; +} __attribute__ ((__packed__)); + +struct avb_aem_desc_stream_port { + uint16_t clock_domain_index; + uint16_t port_flags; + uint16_t number_of_controls; + uint16_t base_control; + uint16_t number_of_clusters; + uint16_t base_cluster; + uint16_t number_of_maps; + uint16_t base_map; +} __attribute__ ((__packed__)); + +#endif /* AVB_AECP_AEM_DESCRIPTORS_H */ diff --git a/src/modules/module-avb/aecp-aem.c b/src/modules/module-avb/aecp-aem.c new file mode 100644 index 0000000000000000000000000000000000000000..22a2ac2dd7ec4761626034bd563115eddf4ffe0c --- /dev/null +++ b/src/modules/module-avb/aecp-aem.c @@ -0,0 +1,286 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "aecp-aem.h" +#include "aecp-aem-descriptors.h" + +static int reply_status(struct aecp *aecp, int status, const void *m, int len) +{ + struct server *server = aecp->server; + uint8_t buf[len]; + struct avb_ethernet_header *h = (void*)buf; + struct avb_packet_aecp_header *reply = SPA_PTROFF(h, sizeof(*h), void); + + memcpy(buf, m, len); + AVB_PACKET_AECP_SET_MESSAGE_TYPE(reply, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); + AVB_PACKET_AECP_SET_STATUS(reply, status); + + return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, len); +} + +static int reply_not_implemented(struct aecp *aecp, const void *m, int len) +{ + return reply_status(aecp, AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED, m, len); +} + +static int reply_success(struct aecp *aecp, const void *m, int len) +{ + return reply_status(aecp, AVB_AECP_AEM_STATUS_SUCCESS, m, len); +} + +/* ACQUIRE_ENTITY */ +static int handle_acquire_entity(struct aecp *aecp, const void *m, int len) +{ + struct server *server = aecp->server; + const struct avb_packet_aecp_aem *p = m; + const struct avb_packet_aecp_aem_acquire *ae; + const struct descriptor *desc; + uint16_t desc_type, desc_id; + + ae = (const struct avb_packet_aecp_aem_acquire*)p->payload; + + desc_type = ntohs(ae->descriptor_type); + desc_id = ntohs(ae->descriptor_id); + + desc = server_find_descriptor(server, desc_type, desc_id); + if (desc == NULL) + return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len); + + if (desc_type != AVB_AEM_DESC_ENTITY || desc_id != 0) + return reply_not_implemented(aecp, m, len); + + return reply_success(aecp, m, len); +} + +/* LOCK_ENTITY */ +static int handle_lock_entity(struct aecp *aecp, const void *m, int len) +{ + struct server *server = aecp->server; + const struct avb_packet_aecp_aem *p = m; + const struct avb_packet_aecp_aem_acquire *ae; + const struct descriptor *desc; + uint16_t desc_type, desc_id; + + ae = (const struct avb_packet_aecp_aem_acquire*)p->payload; + + desc_type = ntohs(ae->descriptor_type); + desc_id = ntohs(ae->descriptor_id); + + desc = server_find_descriptor(server, desc_type, desc_id); + if (desc == NULL) + return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len); + + if (desc_type != AVB_AEM_DESC_ENTITY || desc_id != 0) + return reply_not_implemented(aecp, m, len); + + return reply_success(aecp, m, len); +} + +/* READ_DESCRIPTOR */ +static int handle_read_descriptor(struct aecp *aecp, const void *m, int len) +{ + struct server *server = aecp->server; + const struct avb_ethernet_header *h = m; + const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void); + struct avb_packet_aecp_aem *reply; + const struct avb_packet_aecp_aem_read_descriptor *rd; + uint16_t desc_type, desc_id; + const struct descriptor *desc; + uint8_t buf[2048]; + size_t size, psize; + + rd = (struct avb_packet_aecp_aem_read_descriptor*)p->payload; + + desc_type = ntohs(rd->descriptor_type); + desc_id = ntohs(rd->descriptor_id); + + pw_log_info("descriptor type:%04x index:%d", desc_type, desc_id); + + desc = server_find_descriptor(server, desc_type, desc_id); + if (desc == NULL) + return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len); + + memcpy(buf, m, len); + + psize = sizeof(*rd); + size = sizeof(*h) + sizeof(*reply) + psize; + + memcpy(buf + size, desc->ptr, desc->size); + size += desc->size; + psize += desc->size; + + h = (void*)buf; + reply = SPA_PTROFF(h, sizeof(*h), void); + AVB_PACKET_AECP_SET_MESSAGE_TYPE(&reply->aecp, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); + AVB_PACKET_AECP_SET_STATUS(&reply->aecp, AVB_AECP_AEM_STATUS_SUCCESS); + AVB_PACKET_SET_LENGTH(&reply->aecp.hdr, psize + 12); + + return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, size); +} + +/* GET_AVB_INFO */ +static int handle_get_avb_info(struct aecp *aecp, const void *m, int len) +{ + struct server *server = aecp->server; + const struct avb_ethernet_header *h = m; + const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void); + struct avb_packet_aecp_aem *reply; + struct avb_packet_aecp_aem_get_avb_info *i; + struct avb_aem_desc_avb_interface *avb_interface; + uint16_t desc_type, desc_id; + const struct descriptor *desc; + uint8_t buf[2048]; + size_t size, psize; + + i = (struct avb_packet_aecp_aem_get_avb_info*)p->payload; + + desc_type = ntohs(i->descriptor_type); + desc_id = ntohs(i->descriptor_id); + + desc = server_find_descriptor(server, desc_type, desc_id); + if (desc == NULL) + return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len); + + if (desc_type != AVB_AEM_DESC_AVB_INTERFACE || desc_id != 0) + return reply_not_implemented(aecp, m, len); + + avb_interface = desc->ptr; + + memcpy(buf, m, len); + + psize = sizeof(*i); + size = sizeof(*h) + sizeof(*reply) + psize; + + h = (void*)buf; + reply = SPA_PTROFF(h, sizeof(*h), void); + AVB_PACKET_AECP_SET_MESSAGE_TYPE(&reply->aecp, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); + AVB_PACKET_AECP_SET_STATUS(&reply->aecp, AVB_AECP_AEM_STATUS_SUCCESS); + AVB_PACKET_SET_LENGTH(&reply->aecp.hdr, psize + 12); + + i = (struct avb_packet_aecp_aem_get_avb_info*)reply->payload; + i->gptp_grandmaster_id = avb_interface->clock_identity; + i->propagation_delay = htonl(0); + i->gptp_domain_number = avb_interface->domain_number; + i->flags = 0; + i->msrp_mappings_count = htons(0); + + return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, size); +} + +/* AEM_COMMAND */ +struct cmd_info { + uint16_t type; + const char *name; + int (*handle) (struct aecp *aecp, const void *p, int len); +}; + +static const struct cmd_info cmd_info[] = { + { AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, "acquire-entity", handle_acquire_entity, }, + { AVB_AECP_AEM_CMD_LOCK_ENTITY, "lock-entity", handle_lock_entity, }, + { AVB_AECP_AEM_CMD_ENTITY_AVAILABLE, "entity-available", NULL, }, + { AVB_AECP_AEM_CMD_CONTROLLER_AVAILABLE, "controller-available", NULL, }, + { AVB_AECP_AEM_CMD_READ_DESCRIPTOR, "read-descriptor", handle_read_descriptor, }, + { AVB_AECP_AEM_CMD_WRITE_DESCRIPTOR, "write-descriptor", NULL, }, + { AVB_AECP_AEM_CMD_SET_CONFIGURATION, "set-configuration", NULL, }, + { AVB_AECP_AEM_CMD_GET_CONFIGURATION, "get-configuration", NULL, }, + { AVB_AECP_AEM_CMD_SET_STREAM_FORMAT, "set-stream-format", NULL, }, + { AVB_AECP_AEM_CMD_GET_STREAM_FORMAT, "get-stream-format", NULL, }, + { AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT, "set-video-format", NULL, }, + { AVB_AECP_AEM_CMD_GET_VIDEO_FORMAT, "get-video-format", NULL, }, + { AVB_AECP_AEM_CMD_SET_SENSOR_FORMAT, "set-sensor-format", NULL, }, + { AVB_AECP_AEM_CMD_GET_SENSOR_FORMAT, "get-sensor-format", NULL, }, + { AVB_AECP_AEM_CMD_SET_STREAM_INFO, "set-stream-info", NULL, }, + { AVB_AECP_AEM_CMD_GET_STREAM_INFO, "get-stream-info", NULL, }, + { AVB_AECP_AEM_CMD_SET_NAME, "set-name", NULL, }, + { AVB_AECP_AEM_CMD_GET_NAME, "get-name", NULL, }, + { AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID, "set-association-id", NULL, }, + { AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID, "get-association-id", NULL, }, + { AVB_AECP_AEM_CMD_SET_SAMPLING_RATE, "set-sampling-rate", NULL, }, + { AVB_AECP_AEM_CMD_GET_SAMPLING_RATE, "get-sampling-rate", NULL, }, + { AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE, "set-clock-source", NULL, }, + { AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE, "get-clock-source", NULL, }, + { AVB_AECP_AEM_CMD_SET_CONTROL, "set-control", NULL, }, + { AVB_AECP_AEM_CMD_GET_CONTROL, "get-control", NULL, }, + { AVB_AECP_AEM_CMD_INCREMENT_CONTROL, "increment-control", NULL, }, + { AVB_AECP_AEM_CMD_DECREMENT_CONTROL, "decrement-control", NULL, }, + { AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR, "set-signal-selector", NULL, }, + { AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR, "get-signal-selector", NULL, }, + { AVB_AECP_AEM_CMD_SET_MIXER, "set-mixer", NULL, }, + { AVB_AECP_AEM_CMD_GET_MIXER, "get-mixer", NULL, }, + { AVB_AECP_AEM_CMD_SET_MATRIX, "set-matrix", NULL, }, + { AVB_AECP_AEM_CMD_GET_MATRIX, "get-matrix", NULL, }, + { AVB_AECP_AEM_CMD_START_STREAMING, "start-streaming", NULL, }, + { AVB_AECP_AEM_CMD_STOP_STREAMING, "stop-streaming", NULL, }, + { AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION, "register-unsolicited-notification", NULL, }, + { AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION, "deregister-unsolicited-notification", NULL, }, + { AVB_AECP_AEM_CMD_IDENTIFY_NOTIFICATION, "identify-notification", NULL, }, + { AVB_AECP_AEM_CMD_GET_AVB_INFO, "get-avb-info", handle_get_avb_info, }, + { AVB_AECP_AEM_CMD_GET_AS_PATH, "get-as-path", NULL, }, + { AVB_AECP_AEM_CMD_GET_COUNTERS, "get-counters", NULL, }, + { AVB_AECP_AEM_CMD_REBOOT, "reboot", NULL, }, + { AVB_AECP_AEM_CMD_GET_AUDIO_MAP, "get-audio-map", NULL, }, + { AVB_AECP_AEM_CMD_ADD_AUDIO_MAPPINGS, "add-audio-mappings", NULL, }, + { AVB_AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS, "remove-audio-mappings", NULL, }, + { AVB_AECP_AEM_CMD_GET_VIDEO_MAP, "get-video-map", NULL, }, + { AVB_AECP_AEM_CMD_ADD_VIDEO_MAPPINGS, "add-video-mappings", NULL, }, + { AVB_AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS, "remove-video-mappings", NULL, }, + { AVB_AECP_AEM_CMD_GET_SENSOR_MAP, "get-sensor-map", NULL, } +}; + +static inline const struct cmd_info *find_cmd_info(uint16_t type, const char *name) +{ + uint32_t i; + for (i = 0; i < SPA_N_ELEMENTS(cmd_info); i++) { + if ((name == NULL && type == cmd_info[i].type) || + (name != NULL && spa_streq(name, cmd_info[i].name))) + return &cmd_info[i]; + } + return NULL; +} + +int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len) +{ + const struct avb_ethernet_header *h = m; + const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void); + uint16_t cmd_type; + const struct cmd_info *info; + + cmd_type = AVB_PACKET_AEM_GET_COMMAND_TYPE(p); + + info = find_cmd_info(cmd_type, NULL); + if (info == NULL) + return reply_not_implemented(aecp, m, len); + + pw_log_info("aem command %s", info->name); + + if (info->handle == NULL) + return reply_not_implemented(aecp, m, len); + + return info->handle(aecp, m, len); +} + +int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len) +{ + return 0; +} diff --git a/src/modules/module-avb/aecp-aem.h b/src/modules/module-avb/aecp-aem.h new file mode 100644 index 0000000000000000000000000000000000000000..dcf26b5b7441fa293d3f00cab1c36ad6c26c9b19 --- /dev/null +++ b/src/modules/module-avb/aecp-aem.h @@ -0,0 +1,345 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_AEM_H +#define AVB_AEM_H + +#include "aecp.h" + +#define AVB_AECP_AEM_STATUS_SUCCESS 0 +#define AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED 1 +#define AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR 2 +#define AVB_AECP_AEM_STATUS_ENTITY_LOCKED 3 +#define AVB_AECP_AEM_STATUS_ENTITY_ACQUIRED 4 +#define AVB_AECP_AEM_STATUS_NOT_AUTHENTICATED 5 +#define AVB_AECP_AEM_STATUS_AUTHENTICATION_DISABLED 6 +#define AVB_AECP_AEM_STATUS_BAD_ARGUMENTS 7 +#define AVB_AECP_AEM_STATUS_NO_RESOURCES 8 +#define AVB_AECP_AEM_STATUS_IN_PROGRESS 9 +#define AVB_AECP_AEM_STATUS_ENTITY_MISBEHAVING 10 +#define AVB_AECP_AEM_STATUS_NOT_SUPPORTED 11 +#define AVB_AECP_AEM_STATUS_STREAM_IS_RUNNING 12 + +#define AVB_AECP_AEM_CMD_ACQUIRE_ENTITY 0x0000 +#define AVB_AECP_AEM_CMD_LOCK_ENTITY 0x0001 +#define AVB_AECP_AEM_CMD_ENTITY_AVAILABLE 0x0002 +#define AVB_AECP_AEM_CMD_CONTROLLER_AVAILABLE 0x0003 +#define AVB_AECP_AEM_CMD_READ_DESCRIPTOR 0x0004 +#define AVB_AECP_AEM_CMD_WRITE_DESCRIPTOR 0x0005 +#define AVB_AECP_AEM_CMD_SET_CONFIGURATION 0x0006 +#define AVB_AECP_AEM_CMD_GET_CONFIGURATION 0x0007 +#define AVB_AECP_AEM_CMD_SET_STREAM_FORMAT 0x0008 +#define AVB_AECP_AEM_CMD_GET_STREAM_FORMAT 0x0009 +#define AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT 0x000a +#define AVB_AECP_AEM_CMD_GET_VIDEO_FORMAT 0x000b +#define AVB_AECP_AEM_CMD_SET_SENSOR_FORMAT 0x000c +#define AVB_AECP_AEM_CMD_GET_SENSOR_FORMAT 0x000d +#define AVB_AECP_AEM_CMD_SET_STREAM_INFO 0x000e +#define AVB_AECP_AEM_CMD_GET_STREAM_INFO 0x000f +#define AVB_AECP_AEM_CMD_SET_NAME 0x0010 +#define AVB_AECP_AEM_CMD_GET_NAME 0x0011 +#define AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID 0x0012 +#define AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID 0x0013 +#define AVB_AECP_AEM_CMD_SET_SAMPLING_RATE 0x0014 +#define AVB_AECP_AEM_CMD_GET_SAMPLING_RATE 0x0015 +#define AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE 0x0016 +#define AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE 0x0017 +#define AVB_AECP_AEM_CMD_SET_CONTROL 0x0018 +#define AVB_AECP_AEM_CMD_GET_CONTROL 0x0019 +#define AVB_AECP_AEM_CMD_INCREMENT_CONTROL 0x001a +#define AVB_AECP_AEM_CMD_DECREMENT_CONTROL 0x001b +#define AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR 0x001c +#define AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR 0x001d +#define AVB_AECP_AEM_CMD_SET_MIXER 0x001e +#define AVB_AECP_AEM_CMD_GET_MIXER 0x001f +#define AVB_AECP_AEM_CMD_SET_MATRIX 0x0020 +#define AVB_AECP_AEM_CMD_GET_MATRIX 0x0021 +#define AVB_AECP_AEM_CMD_START_STREAMING 0x0022 +#define AVB_AECP_AEM_CMD_STOP_STREAMING 0x0023 +#define AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION 0x0024 +#define AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION 0x0025 +#define AVB_AECP_AEM_CMD_IDENTIFY_NOTIFICATION 0x0026 +#define AVB_AECP_AEM_CMD_GET_AVB_INFO 0x0027 +#define AVB_AECP_AEM_CMD_GET_AS_PATH 0x0028 +#define AVB_AECP_AEM_CMD_GET_COUNTERS 0x0029 +#define AVB_AECP_AEM_CMD_REBOOT 0x002a +#define AVB_AECP_AEM_CMD_GET_AUDIO_MAP 0x002b +#define AVB_AECP_AEM_CMD_ADD_AUDIO_MAPPINGS 0x002c +#define AVB_AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS 0x002d +#define AVB_AECP_AEM_CMD_GET_VIDEO_MAP 0x002e +#define AVB_AECP_AEM_CMD_ADD_VIDEO_MAPPINGS 0x002f +#define AVB_AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS 0x0030 +#define AVB_AECP_AEM_CMD_GET_SENSOR_MAP 0x0031 +#define AVB_AECP_AEM_CMD_ADD_SENSOR_MAPPINGS 0x0032 +#define AVB_AECP_AEM_CMD_REMOVE_SENSOR_MAPPINGS 0x0033 +#define AVB_AECP_AEM_CMD_START_OPERATION 0x0034 +#define AVB_AECP_AEM_CMD_ABORT_OPERATION 0x0035 +#define AVB_AECP_AEM_CMD_OPERATION_STATUS 0x0036 +#define AVB_AECP_AEM_CMD_AUTH_ADD_KEY 0x0037 +#define AVB_AECP_AEM_CMD_AUTH_DELETE_KEY 0x0038 +#define AVB_AECP_AEM_CMD_AUTH_GET_KEY_LIST 0x0039 +#define AVB_AECP_AEM_CMD_AUTH_GET_KEY 0x003a +#define AVB_AECP_AEM_CMD_AUTH_ADD_KEY_TO_CHAIN 0x003b +#define AVB_AECP_AEM_CMD_AUTH_DELETE_KEY_FROM_CHAIN 0x003c +#define AVB_AECP_AEM_CMD_AUTH_GET_KEYCHAIN_LIST 0x003d +#define AVB_AECP_AEM_CMD_AUTH_GET_IDENTITY 0x003e +#define AVB_AECP_AEM_CMD_AUTH_ADD_TOKEN 0x003f +#define AVB_AECP_AEM_CMD_AUTH_DELETE_TOKEN 0x0040 +#define AVB_AECP_AEM_CMD_AUTHENTICATE 0x0041 +#define AVB_AECP_AEM_CMD_DEAUTHENTICATE 0x0042 +#define AVB_AECP_AEM_CMD_ENABLE_TRANSPORT_SECURITY 0x0043 +#define AVB_AECP_AEM_CMD_DISABLE_TRANSPORT_SECURITY 0x0044 +#define AVB_AECP_AEM_CMD_ENABLE_STREAM_ENCRYPTION 0x0045 +#define AVB_AECP_AEM_CMD_DISABLE_STREAM_ENCRYPTION 0x0046 +#define AVB_AECP_AEM_CMD_SET_MEMORY_OBJECT_LENGTH 0x0047 +#define AVB_AECP_AEM_CMD_GET_MEMORY_OBJECT_LENGTH 0x0048 +#define AVB_AECP_AEM_CMD_SET_STREAM_BACKUP 0x0049 +#define AVB_AECP_AEM_CMD_GET_STREAM_BACKUP 0x004a +#define AVB_AECP_AEM_CMD_EXPANSION 0x7fff + +#define AVB_AEM_ACQUIRE_ENTITY_PERSISTENT_FLAG (1<<0) + +struct avb_packet_aecp_aem_acquire { + uint32_t flags; + uint64_t owner_guid; + uint16_t descriptor_type; + uint16_t descriptor_id; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_lock { + uint32_t flags; + uint64_t locked_guid; + uint16_t descriptor_type; + uint16_t descriptor_id; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_read_descriptor { + uint16_t configuration; + uint8_t reserved[2]; + uint16_t descriptor_type; + uint16_t descriptor_id; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_configuration { + uint16_t reserved; + uint16_t configuration_index; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_stream_format { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint64_t stream_format; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_video_format { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint32_t format_specific; + uint16_t aspect_ratio; + uint16_t color_space; + uint32_t frame_size; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_sensor_format { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint64_t sensor_format; +} __attribute__ ((__packed__)); + + +#define AVB_AEM_STREAM_INFO_FLAG_CLASS_B (1u<<0) +#define AVB_AEM_STREAM_INFO_FLAG_FAST_CONNECT (1u<<1) +#define AVB_AEM_STREAM_INFO_FLAG_SAVED_STATE (1u<<2) +#define AVB_AEM_STREAM_INFO_FLAG_STREAMING_WAIT (1u<<3) +#define AVB_AEM_STREAM_INFO_FLAG_ENCRYPTED_PDU (1u<<4) +#define AVB_AEM_STREAM_INFO_FLAG_STREAM_VLAN_ID_VALID (1u<<25) +#define AVB_AEM_STREAM_INFO_FLAG_CONNECTED (1u<<26) +#define AVB_AEM_STREAM_INFO_FLAG_MSRP_FAILURE_VALID (1u<<27) +#define AVB_AEM_STREAM_INFO_FLAG_STREAM_DEST_MAC_VALID (1u<<28) +#define AVB_AEM_STREAM_INFO_FLAG_MSRP_ACC_LAT_VALID (1u<<29) +#define AVB_AEM_STREAM_INFO_FLAG_STREAM_ID_VALID (1u<<30) +#define AVB_AEM_STREAM_INFO_FLAG_STREAM_FORMAT_VALID (1u<<31) + +struct avb_packet_aecp_aem_setget_stream_info { + uint16_t descriptor_type; + uint16_t descriptor_index; + uint32_t aem_stream_info_flags; + uint64_t stream_format; + uint64_t stream_id; + uint32_t msrp_accumulated_latency; + uint8_t stream_dest_mac[6]; + uint8_t msrp_failure_code; + uint8_t reserved; + uint64_t msrp_failure_bridge_id; + uint16_t stream_vlan_id; + uint16_t reserved2; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_name { + uint16_t descriptor_type; + uint16_t descriptor_index; + uint16_t name_index; + uint16_t configuration_index; + char name[64]; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_association_id { + uint16_t descriptor_type; + uint16_t descriptor_index; + uint64_t association_id; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_sampling_rate { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint32_t sampling_rate; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_clock_source { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint16_t clock_source_index; + uint16_t reserved; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_control { + uint16_t descriptor_type; + uint16_t descriptor_id; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_incdec_control { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint16_t index_count; + uint16_t reserved; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_signal_selector { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint16_t signal_type; + uint16_t signal_index; + uint16_t signal_output; + uint16_t reserved; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_mixer { + uint16_t descriptor_type; + uint16_t descriptor_id; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_setget_matrix { + uint16_t descriptor_type; + uint16_t descriptor_index; + uint16_t matrix_column; + uint16_t matrix_row; + uint16_t region_width; + uint16_t region_height; + uint16_t rep_direction_value_count; + uint16_t item_offset; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_startstop_streaming { + uint16_t descriptor_type; + uint16_t descriptor_id; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_identify_notification { + uint16_t descriptor_type; + uint16_t descriptor_id; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_msrp_mapping { + uint8_t traffic_class; + uint8_t priority; + uint16_t vlan_id; +} __attribute__ ((__packed__)); + +#define AVB_AEM_AVB_INFO_FLAG_GPTP_GRANDMASTER_SUPPORTED (1u<<0) +#define AVB_AEM_AVB_INFO_FLAG_GPTP_ENABLED (1u<<1) +#define AVB_AEM_AVB_INFO_FLAG_SRP_ENABLED (1u<<2) + +struct avb_packet_aecp_aem_get_avb_info { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint64_t gptp_grandmaster_id; + uint32_t propagation_delay; + uint8_t gptp_domain_number; + uint8_t flags; + uint16_t msrp_mappings_count; + uint8_t msrp_mappings[0]; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_get_as_path { + uint16_t descriptor_index; + uint16_t reserved; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_get_counters { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint32_t counters_valid; + uint8_t counters_block[0]; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_reboot { + uint16_t descriptor_type; + uint16_t descriptor_id; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_start_operation { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint16_t operation_id; + uint16_t operation_type; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem_operation_status { + uint16_t descriptor_type; + uint16_t descriptor_id; + uint16_t operation_id; + uint16_t percent_complete; +} __attribute__ ((__packed__)); + +struct avb_packet_aecp_aem { + struct avb_packet_aecp_header aecp; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned u:1; + unsigned cmd1:7; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned cmd1:7; + unsigned u:1; +#endif + uint8_t cmd2; + uint8_t payload[0]; +} __attribute__ ((__packed__)); + +#define AVB_PACKET_AEM_SET_COMMAND_TYPE(p,v) ((p)->cmd1 = ((v) >> 8),(p)->cmd2 = (v)) + +#define AVB_PACKET_AEM_GET_COMMAND_TYPE(p) ((p)->cmd1 << 8 | (p)->cmd2) + +int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len); +int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len); + +#endif /* AVB_AEM_H */ diff --git a/src/modules/module-avb/aecp.c b/src/modules/module-avb/aecp.c new file mode 100644 index 0000000000000000000000000000000000000000..3c25b0ea31ab81accea0520926869f1ffac01b38 --- /dev/null +++ b/src/modules/module-avb/aecp.c @@ -0,0 +1,169 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <spa/utils/json.h> +#include <spa/debug/mem.h> + +#include <pipewire/pipewire.h> + +#include "aecp.h" +#include "aecp-aem.h" +#include "internal.h" + +static const uint8_t mac[6] = AVB_BROADCAST_MAC; + +struct msg_info { + uint16_t type; + const char *name; + int (*handle) (struct aecp *aecp, const void *p, int len); +}; + +static int reply_not_implemented(struct aecp *aecp, const void *p, int len) +{ + struct server *server = aecp->server; + uint8_t buf[len]; + struct avb_ethernet_header *h = (void*)buf; + struct avb_packet_aecp_header *reply = SPA_PTROFF(h, sizeof(*h), void); + + memcpy(h, p, len); + AVB_PACKET_AECP_SET_STATUS(reply, AVB_AECP_STATUS_NOT_IMPLEMENTED); + + return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, len); +} + +static const struct msg_info msg_info[] = { + { AVB_AECP_MESSAGE_TYPE_AEM_COMMAND, "aem-command", avb_aecp_aem_handle_command, }, + { AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE, "aem-response", avb_aecp_aem_handle_response, }, + { AVB_AECP_MESSAGE_TYPE_ADDRESS_ACCESS_COMMAND, "address-access-command", NULL, }, + { AVB_AECP_MESSAGE_TYPE_ADDRESS_ACCESS_RESPONSE, "address-access-response", NULL, }, + { AVB_AECP_MESSAGE_TYPE_AVC_COMMAND, "avc-command", NULL, }, + { AVB_AECP_MESSAGE_TYPE_AVC_RESPONSE, "avc-response", NULL, }, + { AVB_AECP_MESSAGE_TYPE_VENDOR_UNIQUE_COMMAND, "vendor-unique-command", NULL, }, + { AVB_AECP_MESSAGE_TYPE_VENDOR_UNIQUE_RESPONSE, "vendor-unique-response", NULL, }, + { AVB_AECP_MESSAGE_TYPE_EXTENDED_COMMAND, "extended-command", NULL, }, + { AVB_AECP_MESSAGE_TYPE_EXTENDED_RESPONSE, "extended-response", NULL, }, +}; + +static inline const struct msg_info *find_msg_info(uint16_t type, const char *name) +{ + uint32_t i; + for (i = 0; i < SPA_N_ELEMENTS(msg_info); i++) { + if ((name == NULL && type == msg_info[i].type) || + (name != NULL && spa_streq(name, msg_info[i].name))) + return &msg_info[i]; + } + return NULL; +} + +static int aecp_message(void *data, uint64_t now, const void *message, int len) +{ + struct aecp *aecp = data; + struct server *server = aecp->server; + const struct avb_ethernet_header *h = message; + const struct avb_packet_aecp_header *p = SPA_PTROFF(h, sizeof(*h), void); + const struct msg_info *info; + int message_type; + + if (ntohs(h->type) != AVB_TSN_ETH) + return 0; + if (memcmp(h->dest, mac, 6) != 0 && + memcmp(h->dest, server->mac_addr, 6) != 0) + return 0; + if (AVB_PACKET_GET_SUBTYPE(&p->hdr) != AVB_SUBTYPE_AECP) + return 0; + + message_type = AVB_PACKET_AECP_GET_MESSAGE_TYPE(p); + + info = find_msg_info(message_type, NULL); + if (info == NULL) + return reply_not_implemented(aecp, message, len); + + pw_log_debug("got AECP message %s", info->name); + + if (info->handle == NULL) + return reply_not_implemented(aecp, message, len); + + return info->handle(aecp, message, len); +} + +static void aecp_destroy(void *data) +{ + struct aecp *aecp = data; + spa_hook_remove(&aecp->server_listener); + free(aecp); +} + +static int do_help(struct aecp *aecp, const char *args, FILE *out) +{ + fprintf(out, "{ \"type\": \"help\"," + "\"text\": \"" + "/adp/help: this help \\n" + "\" }"); + return 0; +} + +static int aecp_command(void *data, uint64_t now, const char *command, const char *args, FILE *out) +{ + struct aecp *aecp = data; + int res; + + if (!spa_strstartswith(command, "/aecp/")) + return 0; + + command += strlen("/aecp/"); + + if (spa_streq(command, "help")) + res = do_help(aecp, args, out); + else + res = -ENOTSUP; + + return res; +} + +static const struct server_events server_events = { + AVB_VERSION_SERVER_EVENTS, + .destroy = aecp_destroy, + .message = aecp_message, + .command = aecp_command +}; + +struct avb_aecp *avb_aecp_register(struct server *server) +{ + struct aecp *aecp; + + aecp = calloc(1, sizeof(*aecp)); + if (aecp == NULL) + return NULL; + + aecp->server = server; + + avdecc_server_add_listener(server, &aecp->server_listener, &server_events, aecp); + + return (struct avb_aecp*)aecp; +} + +void avb_aecp_unregister(struct avb_aecp *aecp) +{ + aecp_destroy(aecp); +} diff --git a/src/modules/module-avb/aecp.h b/src/modules/module-avb/aecp.h new file mode 100644 index 0000000000000000000000000000000000000000..a3515f0e7c683ff2934772a48dcc079535f92f83 --- /dev/null +++ b/src/modules/module-avb/aecp.h @@ -0,0 +1,60 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_AECP_H +#define AVB_AECP_H + +#include "packets.h" +#include "internal.h" + +#define AVB_AECP_MESSAGE_TYPE_AEM_COMMAND 0 +#define AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE 1 +#define AVB_AECP_MESSAGE_TYPE_ADDRESS_ACCESS_COMMAND 2 +#define AVB_AECP_MESSAGE_TYPE_ADDRESS_ACCESS_RESPONSE 3 +#define AVB_AECP_MESSAGE_TYPE_AVC_COMMAND 4 +#define AVB_AECP_MESSAGE_TYPE_AVC_RESPONSE 5 +#define AVB_AECP_MESSAGE_TYPE_VENDOR_UNIQUE_COMMAND 6 +#define AVB_AECP_MESSAGE_TYPE_VENDOR_UNIQUE_RESPONSE 7 +#define AVB_AECP_MESSAGE_TYPE_EXTENDED_COMMAND 14 +#define AVB_AECP_MESSAGE_TYPE_EXTENDED_RESPONSE 15 + +#define AVB_AECP_STATUS_SUCCESS 0 +#define AVB_AECP_STATUS_NOT_IMPLEMENTED 1 + +struct avb_packet_aecp_header { + struct avb_packet_header hdr; + uint64_t target_guid; + uint64_t controller_guid; + uint16_t sequence_id; +} __attribute__ ((__packed__)); + +#define AVB_PACKET_AECP_SET_MESSAGE_TYPE(p,v) AVB_PACKET_SET_SUB1(&(p)->hdr, v) +#define AVB_PACKET_AECP_SET_STATUS(p,v) AVB_PACKET_SET_SUB2(&(p)->hdr, v) + +#define AVB_PACKET_AECP_GET_MESSAGE_TYPE(p) AVB_PACKET_GET_SUB1(&(p)->hdr) +#define AVB_PACKET_AECP_GET_STATUS(p) AVB_PACKET_GET_SUB2(&(p)->hdr) + +struct avb_aecp *avb_aecp_register(struct server *server); + +#endif /* AVB_AECP_H */ diff --git a/src/modules/module-avb/avb.c b/src/modules/module-avb/avb.c new file mode 100644 index 0000000000000000000000000000000000000000..34526936f767949f16c2e50ba7eebc90261f3dd9 --- /dev/null +++ b/src/modules/module-avb/avb.c @@ -0,0 +1,108 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "internal.h" + +#include <spa/support/cpu.h> + +struct pw_avb *pw_avb_new(struct pw_context *context, + struct pw_properties *props, size_t user_data_size) +{ + struct impl *impl; + const struct spa_support *support; + uint32_t n_support; + struct spa_cpu *cpu; + const char *str; + int res = 0; + + impl = calloc(1, sizeof(*impl) + user_data_size); + if (impl == NULL) + goto error_exit; + + if (props == NULL) + props = pw_properties_new(NULL, NULL); + if (props == NULL) + goto error_free; + + support = pw_context_get_support(context, &n_support); + cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); + + pw_context_conf_update_props(context, "avb.properties", props); + + if ((str = pw_properties_get(props, "vm.overrides")) != NULL) { + if (cpu != NULL && spa_cpu_get_vm_type(cpu) != SPA_CPU_VM_NONE) + pw_properties_update_string(props, str, strlen(str)); + pw_properties_set(props, "vm.overrides", NULL); + } + + impl->context = context; + impl->loop = pw_context_get_main_loop(context); + impl->props = props; + impl->core = pw_context_get_object(context, PW_TYPE_INTERFACE_Core); + if (impl->core == NULL) { + str = pw_properties_get(props, PW_KEY_REMOTE_NAME); + impl->core = pw_context_connect(context, + pw_properties_new( + PW_KEY_REMOTE_NAME, str, + NULL), + 0); + impl->do_disconnect = true; + } + if (impl->core == NULL) { + res = -errno; + pw_log_error("can't connect: %m"); + goto error_free; + } + + impl->work_queue = pw_context_get_work_queue(context); + + spa_list_init(&impl->servers); + + avdecc_server_new(impl, &props->dict); + + return (struct pw_avb*)impl; + +error_free: + free(impl); +error_exit: + pw_properties_free(props); + if (res < 0) + errno = -res; + return NULL; +} + +static void impl_free(struct impl *impl) +{ + struct server *s; + + spa_list_consume(s, &impl->servers, link) + avdecc_server_free(s); + free(impl); +} + +void pw_avb_destroy(struct pw_avb *avb) +{ + struct impl *impl = (struct impl*)avb; + impl_free(impl); +} diff --git a/src/modules/module-avb/avb.h b/src/modules/module-avb/avb.h new file mode 100644 index 0000000000000000000000000000000000000000..cad7dd2f9f5af85f6faf4fe59cf5359ea6c704ae --- /dev/null +++ b/src/modules/module-avb/avb.h @@ -0,0 +1,44 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef PIPEWIRE_AVB_H +#define PIPEWIRE_AVB_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct pw_context; +struct pw_properties; +struct pw_avb; + +struct pw_avb *pw_avb_new(struct pw_context *context, + struct pw_properties *props, size_t user_data_size); +void pw_avb_destroy(struct pw_avb *avb); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* PIPEWIRE_AVB_H */ diff --git a/src/modules/module-avb/avdecc.c b/src/modules/module-avb/avdecc.c new file mode 100644 index 0000000000000000000000000000000000000000..308ba4807971479431cb6fdf73608a107eb4ab74 --- /dev/null +++ b/src/modules/module-avb/avdecc.c @@ -0,0 +1,335 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <linux/if_ether.h> +#include <linux/if_packet.h> +#include <linux/filter.h> +#include <linux/net_tstamp.h> +#include <limits.h> +#include <net/if.h> +#include <arpa/inet.h> +#include <sys/ioctl.h> +#include <unistd.h> + +#include <spa/support/cpu.h> +#include <spa/debug/mem.h> + +#include <pipewire/pipewire.h> + +#include "avb.h" +#include "packets.h" +#include "internal.h" +#include "stream.h" +#include "acmp.h" +#include "adp.h" +#include "aecp.h" +#include "maap.h" +#include "mmrp.h" +#include "msrp.h" +#include "mvrp.h" +#include "descriptors.h" +#include "utils.h" + +#define DEFAULT_INTERVAL 1 + +#define server_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct server_events, m, v, ##__VA_ARGS__) +#define server_emit_destroy(s) server_emit(s, destroy, 0) +#define server_emit_message(s,n,m,l) server_emit(s, message, 0, n, m, l) +#define server_emit_periodic(s,n) server_emit(s, periodic, 0, n) +#define server_emit_command(s,n,c,a,f) server_emit(s, command, 0, n, c, a, f) + +static void on_timer_event(void *data, uint64_t expirations) +{ + struct server *server = data; + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + server_emit_periodic(server, SPA_TIMESPEC_TO_NSEC(&now)); +} + +static void on_socket_data(void *data, int fd, uint32_t mask) +{ + struct server *server = data; + struct timespec now; + + if (mask & SPA_IO_IN) { + int len; + uint8_t buffer[2048]; + + len = recv(fd, buffer, sizeof(buffer), 0); + + if (len < 0) { + pw_log_warn("got recv error: %m"); + } + else if (len < (int)sizeof(struct avb_packet_header)) { + pw_log_warn("short packet received (%d < %d)", len, + (int)sizeof(struct avb_packet_header)); + } else { + clock_gettime(CLOCK_REALTIME, &now); + server_emit_message(server, SPA_TIMESPEC_TO_NSEC(&now), buffer, len); + } + } +} + +int avb_server_send_packet(struct server *server, const uint8_t dest[6], + uint16_t type, void *data, size_t size) +{ + struct avb_ethernet_header *hdr = (struct avb_ethernet_header*)data; + int res = 0; + + memcpy(hdr->dest, dest, ETH_ALEN); + memcpy(hdr->src, server->mac_addr, ETH_ALEN); + hdr->type = htons(type); + + if (send(server->source->fd, data, size, 0) < 0) { + res = -errno; + pw_log_warn("got send error: %m"); + } + return res; +} + +static int load_filter(int fd, uint16_t eth, const uint8_t dest[6], const uint8_t mac[6]) +{ + struct sock_fprog filter; + struct sock_filter bpf_code[] = { + BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 12), + BPF_JUMP(BPF_JMP|BPF_JEQ, eth, 0, 8), + BPF_STMT(BPF_LD|BPF_W|BPF_ABS, 2), + BPF_JUMP(BPF_JMP|BPF_JEQ, (dest[2] << 24) | + (dest[3] << 16) | + (dest[4] << 8) | + (dest[5]), 0, 2), + BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 0), + BPF_JUMP(BPF_JMP|BPF_JEQ, (dest[0] << 8) | + (dest[1]), 3, 4), + BPF_JUMP(BPF_JMP|BPF_JEQ, (mac[2] << 24) | + (mac[3] << 16) | + (mac[4] << 8) | + (mac[5]), 0, 3), + BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 0), + BPF_JUMP(BPF_JMP|BPF_JEQ, (mac[0] << 8) | + (mac[1]), 0, 1), + BPF_STMT(BPF_RET, 0x00040000), + BPF_STMT(BPF_RET, 0x00000000), + }; + filter.len = sizeof(bpf_code) / 8; + filter.filter = bpf_code; + + if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, + &filter, sizeof(filter)) < 0) { + pw_log_error("setsockopt(ATTACH_FILTER) failed: %m"); + return -errno; + } + return 0; +} + +int avb_server_make_socket(struct server *server, uint16_t type, const uint8_t mac[6]) +{ + int fd, res; + struct ifreq req; + struct packet_mreq mreq; + struct sockaddr_ll sll; + + fd = socket(AF_PACKET, SOCK_RAW|SOCK_NONBLOCK, htons(ETH_P_ALL)); + if (fd < 0) { + pw_log_error("socket() failed: %m"); + return -errno; + } + + spa_zero(req); + snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", server->ifname); + if (ioctl(fd, SIOCGIFINDEX, &req) < 0) { + res = -errno; + pw_log_error("SIOCGIFINDEX %s failed: %m", server->ifname); + goto error_close; + } + server->ifindex = req.ifr_ifindex; + + spa_zero(req); + snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", server->ifname); + if (ioctl(fd, SIOCGIFHWADDR, &req) < 0) { + res = -errno; + pw_log_error("SIOCGIFHWADDR %s failed: %m", server->ifname); + goto error_close; + } + memcpy(server->mac_addr, req.ifr_hwaddr.sa_data, sizeof(server->mac_addr)); + + server->entity_id = (uint64_t)server->mac_addr[0] << 56 | + (uint64_t)server->mac_addr[1] << 48 | + (uint64_t)server->mac_addr[2] << 40 | + (uint64_t)0xff << 32 | + (uint64_t)0xfe << 24 | + (uint64_t)server->mac_addr[3] << 16 | + (uint64_t)server->mac_addr[4] << 8 | + (uint64_t)server->mac_addr[5]; + + spa_zero(sll); + sll.sll_family = AF_PACKET; + sll.sll_protocol = htons(ETH_P_ALL); + sll.sll_ifindex = server->ifindex; + if (bind(fd, (struct sockaddr *) &sll, sizeof(sll)) < 0) { + res = -errno; + pw_log_error("bind() failed: %m"); + goto error_close; + } + + spa_zero(mreq); + mreq.mr_ifindex = server->ifindex; + mreq.mr_type = PACKET_MR_MULTICAST; + mreq.mr_alen = ETH_ALEN; + memcpy(mreq.mr_address, mac, ETH_ALEN); + + if (setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, + &mreq, sizeof(mreq)) < 0) { + res = -errno; + pw_log_error("setsockopt(ADD_MEMBERSHIP) failed: %m"); + goto error_close; + } + + if ((res = load_filter(fd, type, mac, server->mac_addr)) < 0) + goto error_close; + + return fd; + +error_close: + close(fd); + return res; +} + +static int setup_socket(struct server *server) +{ + struct impl *impl = server->impl; + int fd, res; + static const uint8_t bmac[6] = AVB_BROADCAST_MAC; + struct timespec value, interval; + + fd = avb_server_make_socket(server, AVB_TSN_ETH, bmac); + if (fd < 0) + return fd; + + pw_log_info("0x%"PRIx64" %d", server->entity_id, server->ifindex); + + server->source = pw_loop_add_io(impl->loop, fd, SPA_IO_IN, true, on_socket_data, server); + if (server->source == NULL) { + res = -errno; + pw_log_error("server %p: can't create server source: %m", impl); + goto error_no_source; + } + server->timer = pw_loop_add_timer(impl->loop, on_timer_event, server); + if (server->timer == NULL) { + res = -errno; + pw_log_error("server %p: can't create timer source: %m", impl); + goto error_no_timer; + } + value.tv_sec = 0; + value.tv_nsec = 1; + interval.tv_sec = DEFAULT_INTERVAL; + interval.tv_nsec = 0; + pw_loop_update_timer(impl->loop, server->timer, &value, &interval, false); + + return 0; + +error_no_timer: + pw_loop_destroy_source(impl->loop, server->source); + server->source = NULL; +error_no_source: + close(fd); + return res; +} + +struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props) +{ + struct server *server; + int res = 0; + + server = calloc(1, sizeof(*server)); + if (server == NULL) + return NULL; + + server->impl = impl; + spa_list_append(&impl->servers, &server->link); + server->ifname = strdup(spa_dict_lookup(props, "ifname")); + spa_hook_list_init(&server->listener_list); + spa_list_init(&server->descriptors); + spa_list_init(&server->streams); + + server->debug_messages = false; + + if ((res = setup_socket(server)) < 0) + goto error_free; + + init_descriptors(server); + + server->mrp = avb_mrp_new(server); + if (server->mrp == NULL) + goto error_free; + + avb_aecp_register(server); + server->maap = avb_maap_register(server); + server->mmrp = avb_mmrp_register(server); + server->msrp = avb_msrp_register(server); + server->mvrp = avb_mvrp_register(server); + avb_adp_register(server); + avb_acmp_register(server); + + server->domain_attr = avb_msrp_attribute_new(server->msrp, + AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN); + server->domain_attr->attr.domain.sr_class_id = AVB_MSRP_CLASS_ID_DEFAULT; + server->domain_attr->attr.domain.sr_class_priority = AVB_MSRP_PRIORITY_DEFAULT; + server->domain_attr->attr.domain.sr_class_vid = htons(AVB_DEFAULT_VLAN); + + avb_mrp_attribute_begin(server->domain_attr->mrp, 0); + avb_mrp_attribute_join(server->domain_attr->mrp, 0, true); + + server_create_stream(server, SPA_DIRECTION_INPUT, 0); + server_create_stream(server, SPA_DIRECTION_OUTPUT, 0); + + avb_maap_reserve(server->maap, 1); + + return server; + +error_free: + free(server); + if (res < 0) + errno = -res; + return NULL; +} + +void avdecc_server_add_listener(struct server *server, struct spa_hook *listener, + const struct server_events *events, void *data) +{ + spa_hook_list_append(&server->listener_list, listener, events, data); +} + +void avdecc_server_free(struct server *server) +{ + struct impl *impl = server->impl; + + spa_list_remove(&server->link); + if (server->source) + pw_loop_destroy_source(impl->loop, server->source); + if (server->timer) + pw_loop_destroy_source(impl->loop, server->source); + spa_hook_list_clean(&server->listener_list); + free(server); +} diff --git a/src/modules/module-avb/descriptors.h b/src/modules/module-avb/descriptors.h new file mode 100644 index 0000000000000000000000000000000000000000..56397e3d33f51b7191b5e2cfd610ba55926c2138 --- /dev/null +++ b/src/modules/module-avb/descriptors.h @@ -0,0 +1,274 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "aecp-aem.h" +#include "aecp-aem-descriptors.h" +#include "internal.h" + +void init_descriptors(struct server *server) +{ + server_add_descriptor(server, AVB_AEM_DESC_STRINGS, 0, + sizeof(struct avb_aem_desc_strings), + &(struct avb_aem_desc_strings) + { + .string_0 = "PipeWire", + .string_1 = "Configuration 1", + .string_2 = "Wim Taymans", + }); + server_add_descriptor(server, AVB_AEM_DESC_LOCALE, 0, + sizeof(struct avb_aem_desc_locale), + &(struct avb_aem_desc_locale) + { + .locale_identifier = "en-EN", + .number_of_strings = htons(1), + .base_strings = htons(0) + }); + server_add_descriptor(server, AVB_AEM_DESC_ENTITY, 0, + sizeof(struct avb_aem_desc_entity), + &(struct avb_aem_desc_entity) + { + .entity_id = htobe64(server->entity_id), + .entity_model_id = htobe64(0), + .entity_capabilities = htonl( + AVB_ADP_ENTITY_CAPABILITY_AEM_SUPPORTED | + AVB_ADP_ENTITY_CAPABILITY_CLASS_A_SUPPORTED | + AVB_ADP_ENTITY_CAPABILITY_GPTP_SUPPORTED | + AVB_ADP_ENTITY_CAPABILITY_AEM_IDENTIFY_CONTROL_INDEX_VALID | + AVB_ADP_ENTITY_CAPABILITY_AEM_INTERFACE_INDEX_VALID), + + .talker_stream_sources = htons(8), + .talker_capabilities = htons( + AVB_ADP_TALKER_CAPABILITY_IMPLEMENTED | + AVB_ADP_TALKER_CAPABILITY_AUDIO_SOURCE), + .listener_stream_sinks = htons(8), + .listener_capabilities = htons( + AVB_ADP_LISTENER_CAPABILITY_IMPLEMENTED | + AVB_ADP_LISTENER_CAPABILITY_AUDIO_SINK), + .controller_capabilities = htons(0), + .available_index = htonl(0), + .association_id = htobe64(0), + .entity_name = "PipeWire", + .vendor_name_string = htons(2), + .model_name_string = htons(0), + .firmware_version = "0.3.48", + .group_name = "", + .serial_number = "", + .configurations_count = htons(1), + .current_configuration = htons(0) + }); + struct { + struct avb_aem_desc_configuration desc; + struct avb_aem_desc_descriptor_count descriptor_counts[8]; + } __attribute__ ((__packed__)) config = + { + { + .object_name = "Configuration 1", + .localized_description = htons(1), + .descriptor_counts_count = htons(8), + .descriptor_counts_offset = htons( + 4 + sizeof(struct avb_aem_desc_configuration)), + }, + .descriptor_counts = { + { htons(AVB_AEM_DESC_AUDIO_UNIT), htons(1) }, + { htons(AVB_AEM_DESC_STREAM_INPUT), htons(1) }, + { htons(AVB_AEM_DESC_STREAM_OUTPUT), htons(1) }, + { htons(AVB_AEM_DESC_AVB_INTERFACE), htons(1) }, + { htons(AVB_AEM_DESC_CLOCK_SOURCE), htons(1) }, + { htons(AVB_AEM_DESC_CONTROL), htons(2) }, + { htons(AVB_AEM_DESC_LOCALE), htons(1) }, + { htons(AVB_AEM_DESC_CLOCK_DOMAIN), htons(1) } + } + }; + server_add_descriptor(server, AVB_AEM_DESC_CONFIGURATION, 0, + sizeof(config), &config); + + struct { + struct avb_aem_desc_audio_unit desc; + struct avb_aem_desc_sampling_rate sampling_rates[6]; + } __attribute__ ((__packed__)) audio_unit = + { + { + .object_name = "PipeWire", + .localized_description = htons(0), + .clock_domain_index = htons(0), + .number_of_stream_input_ports = htons(1), + .base_stream_input_port = htons(0), + .number_of_stream_output_ports = htons(1), + .base_stream_output_port = htons(0), + .number_of_external_input_ports = htons(8), + .base_external_input_port = htons(0), + .number_of_external_output_ports = htons(8), + .base_external_output_port = htons(0), + .number_of_internal_input_ports = htons(0), + .base_internal_input_port = htons(0), + .number_of_internal_output_ports = htons(0), + .base_internal_output_port = htons(0), + .number_of_controls = htons(0), + .base_control = htons(0), + .number_of_signal_selectors = htons(0), + .base_signal_selector = htons(0), + .number_of_mixers = htons(0), + .base_mixer = htons(0), + .number_of_matrices = htons(0), + .base_matrix = htons(0), + .number_of_splitters = htons(0), + .base_splitter = htons(0), + .number_of_combiners = htons(0), + .base_combiner = htons(0), + .number_of_demultiplexers = htons(0), + .base_demultiplexer = htons(0), + .number_of_multiplexers = htons(0), + .base_multiplexer = htons(0), + .number_of_transcoders = htons(0), + .base_transcoder = htons(0), + .number_of_control_blocks = htons(0), + .base_control_block = htons(0), + .current_sampling_rate = htonl(48000), + .sampling_rates_offset = htons( + 4 + sizeof(struct avb_aem_desc_audio_unit)), + .sampling_rates_count = htons(6), + }, + .sampling_rates = { + { .pull_frequency = htonl(44100) }, + { .pull_frequency = htonl(48000) }, + { .pull_frequency = htonl(88200) }, + { .pull_frequency = htonl(96000) }, + { .pull_frequency = htonl(176400) }, + { .pull_frequency = htonl(192000) }, + } + }; + server_add_descriptor(server, AVB_AEM_DESC_AUDIO_UNIT, 0, + sizeof(audio_unit), &audio_unit); + + struct { + struct avb_aem_desc_stream desc; + uint64_t stream_formats[6]; + } __attribute__ ((__packed__)) stream_input_0 = + { + { + .object_name = "Stream Input 1", + .localized_description = htons(0xffff), + .clock_domain_index = htons(0), + .stream_flags = htons( + AVB_AEM_DESC_STREAM_FLAG_SYNC_SOURCE | + AVB_AEM_DESC_STREAM_FLAG_CLASS_A), + .current_format = htobe64(0x00a0020840000800ULL), + .formats_offset = htons( + 4 + sizeof(struct avb_aem_desc_stream)), + .number_of_formats = htons(6), + .backup_talker_entity_id_0 = htobe64(0), + .backup_talker_unique_id_0 = htons(0), + .backup_talker_entity_id_1 = htobe64(0), + .backup_talker_unique_id_1 = htons(0), + .backup_talker_entity_id_2 = htobe64(0), + .backup_talker_unique_id_2 = htons(0), + .backedup_talker_entity_id = htobe64(0), + .backedup_talker_unique = htons(0), + .avb_interface_index = htons(0), + .buffer_length = htons(8) + }, + .stream_formats = { + htobe64(0x00a0010860000800ULL), + htobe64(0x00a0020860000800ULL), + htobe64(0x00a0030860000800ULL), + htobe64(0x00a0040860000800ULL), + htobe64(0x00a0050860000800ULL), + htobe64(0x00a0060860000800ULL), + }, + }; + server_add_descriptor(server, AVB_AEM_DESC_STREAM_INPUT, 0, + sizeof(stream_input_0), &stream_input_0); + + struct { + struct avb_aem_desc_stream desc; + uint64_t stream_formats[6]; + } __attribute__ ((__packed__)) stream_output_0 = + { + { + .object_name = "Stream Output 1", + .localized_description = htons(0xffff), + .clock_domain_index = htons(0), + .stream_flags = htons( + AVB_AEM_DESC_STREAM_FLAG_CLASS_A), + .current_format = htobe64(0x00a0020840000800ULL), + .formats_offset = htons( + 4 + sizeof(struct avb_aem_desc_stream)), + .number_of_formats = htons(6), + .backup_talker_entity_id_0 = htobe64(0), + .backup_talker_unique_id_0 = htons(0), + .backup_talker_entity_id_1 = htobe64(0), + .backup_talker_unique_id_1 = htons(0), + .backup_talker_entity_id_2 = htobe64(0), + .backup_talker_unique_id_2 = htons(0), + .backedup_talker_entity_id = htobe64(0), + .backedup_talker_unique = htons(0), + .avb_interface_index = htons(0), + .buffer_length = htons(8) + }, + .stream_formats = { + htobe64(0x00a0010860000800ULL), + htobe64(0x00a0020860000800ULL), + htobe64(0x00a0030860000800ULL), + htobe64(0x00a0040860000800ULL), + htobe64(0x00a0050860000800ULL), + htobe64(0x00a0060860000800ULL), + }, + }; + server_add_descriptor(server, AVB_AEM_DESC_STREAM_OUTPUT, 0, + sizeof(stream_output_0), &stream_output_0); + + struct avb_aem_desc_avb_interface avb_interface = { + .localized_description = htons(0xffff), + .interface_flags = htons( + AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_GRANDMASTER_SUPPORTED), + .clock_identity = htobe64(0), + .priority1 = 0, + .clock_class = 0, + .offset_scaled_log_variance = htons(0), + .clock_accuracy = 0, + .priority2 = 0, + .domain_number = 0, + .log_sync_interval = 0, + .log_announce_interval = 0, + .log_pdelay_interval = 0, + .port_number = 0, + }; + strncpy(avb_interface.object_name, server->ifname, 63); + memcpy(avb_interface.mac_address, server->mac_addr, 6); + server_add_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, 0, + sizeof(avb_interface), &avb_interface); + + struct avb_aem_desc_clock_source clock_source = { + .object_name = "Stream Clock", + .localized_description = htons(0xffff), + .clock_source_flags = htons(0), + .clock_source_type = htons( + AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INPUT_STREAM), + .clock_source_identifier = htobe64(0), + .clock_source_location_type = htons(AVB_AEM_DESC_STREAM_INPUT), + .clock_source_location_index = htons(0), + }; + server_add_descriptor(server, AVB_AEM_DESC_CLOCK_SOURCE, 0, + sizeof(clock_source), &clock_source); +} diff --git a/src/modules/module-avb/iec61883.h b/src/modules/module-avb/iec61883.h new file mode 100644 index 0000000000000000000000000000000000000000..6ca8724ad71a080b26ffaf018197c2e698db36b0 --- /dev/null +++ b/src/modules/module-avb/iec61883.h @@ -0,0 +1,110 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_IEC61883_H +#define AVB_IEC61883_H + +#include "packets.h" + +struct avb_packet_iec61883 { + uint8_t subtype; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned sv:1; + unsigned version:3; + unsigned mr:1; + unsigned _r1:1; + unsigned gv:1; + unsigned tv:1; + + uint8_t seq_num; + + unsigned _r2:7; + unsigned tu:1; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned tv:1; + unsigned gv:1; + unsigned _r1:1; + unsigned mr:1; + unsigned version:3; + unsigned sv:1; + + uint8_t seq_num; + + unsigned tu:1; + unsigned _r2:7; +#endif + uint64_t stream_id; + uint32_t timestamp; + uint32_t gateway_info; + uint16_t data_len; +#if __BYTE_ORDER == __BIG_ENDIAN + uint8_t tag:2; + uint8_t channel:6; + + uint8_t tcode:4; + uint8_t app:4; + + uint8_t qi1:2; /* CIP Quadlet Indicator 1 */ + uint8_t sid:6; /* CIP Source ID */ + + uint8_t dbs; /* CIP Data Block Size */ + + uint8_t fn:2; /* CIP Fraction Number */ + uint8_t qpc:3; /* CIP Quadlet Padding Count */ + uint8_t sph:1; /* CIP Source Packet Header */ + uint8_t _r3:2; + + uint8_t dbc; /* CIP Data Block Continuity */ + + uint8_t qi2:2; /* CIP Quadlet Indicator 2 */ + uint8_t format_id:6; /* CIP Format ID */ +#elif __BYTE_ORDER == __LITTLE_ENDIAN + uint8_t channel:6; + uint8_t tag:2; + + uint8_t app:4; + uint8_t tcode:4; + + uint8_t sid:6; /* CIP Source ID */ + uint8_t qi1:2; /* CIP Quadlet Indicator 1 */ + + uint8_t dbs; /* CIP Data Block Size */ + + uint8_t _r3:2; + uint8_t sph:1; /* CIP Source Packet Header */ + uint8_t qpc:3; /* CIP Quadlet Padding Count */ + uint8_t fn:2; /* CIP Fraction Number */ + + uint8_t dbc; /* CIP Data Block Continuity */ + + uint8_t format_id:6; /* CIP Format ID */ + uint8_t qi2:2; /* CIP Quadlet Indicator 2 */ +#endif + uint8_t fdf; /* CIP Format Dependent Field */ + uint16_t syt; + + uint8_t payload[0]; +} __attribute__ ((__packed__)); + +#endif /* AVB_IEC61883_H */ diff --git a/src/modules/module-avb/internal.h b/src/modules/module-avb/internal.h new file mode 100644 index 0000000000000000000000000000000000000000..f0a1c14995454db901e9fe7ce75d74393aeb170f --- /dev/null +++ b/src/modules/module-avb/internal.h @@ -0,0 +1,167 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_INTERNAL_H +#define AVB_INTERNAL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <pipewire/pipewire.h> + +struct server; +struct avb_mrp; + +#define AVB_TSN_ETH 0x22f0 +#define AVB_BROADCAST_MAC { 0x91, 0xe0, 0xf0, 0x01, 0x00, 0x00 }; + +struct impl { + struct pw_loop *loop; + struct pw_context *context; + struct spa_hook context_listener; + struct pw_core *core; + unsigned do_disconnect:1; + + struct pw_properties *props; + struct pw_work_queue *work_queue; + + struct spa_list servers; +}; + +struct server_events { +#define AVB_VERSION_SERVER_EVENTS 0 + uint32_t version; + + /** the server is destroyed */ + void (*destroy) (void *data); + + int (*message) (void *data, uint64_t now, const void *message, int len); + + void (*periodic) (void *data, uint64_t now); + + int (*command) (void *data, uint64_t now, const char *command, const char *args, FILE *out); +}; + +struct descriptor { + struct spa_list link; + uint16_t type; + uint16_t index; + uint32_t size; + void *ptr; +}; + +struct server { + struct spa_list link; + struct impl *impl; + + char *ifname; + uint8_t mac_addr[6]; + uint64_t entity_id; + int ifindex; + + struct spa_source *source; + struct spa_source *timer; + + struct spa_hook_list listener_list; + + struct spa_list descriptors; + struct spa_list streams; + + unsigned debug_messages:1; + + struct avb_mrp *mrp; + struct avb_mmrp *mmrp; + struct avb_mvrp *mvrp; + struct avb_msrp *msrp; + struct avb_maap *maap; + + struct avb_msrp_attribute *domain_attr; +}; + +#include "stream.h" + +static inline const struct descriptor *server_find_descriptor(struct server *server, + uint16_t type, uint16_t index) +{ + struct descriptor *d; + spa_list_for_each(d, &server->descriptors, link) { + if (d->type == type && + d->index == index) + return d; + } + return NULL; +} +static inline void *server_add_descriptor(struct server *server, + uint16_t type, uint16_t index, size_t size, void *ptr) +{ + struct descriptor *d; + + if ((d = calloc(1, sizeof(struct descriptor) + size)) == NULL) + return NULL; + + d->type = type; + d->index = index; + d->size = size; + d->ptr = SPA_PTROFF(d, sizeof(struct descriptor), void); + if (ptr) + memcpy(d->ptr, ptr, size); + spa_list_append(&server->descriptors, &d->link); + return d->ptr; +} + +static inline struct stream *server_find_stream(struct server *server, + enum spa_direction direction, uint16_t index) +{ + struct stream *s; + spa_list_for_each(s, &server->streams, link) { + if (s->direction == direction && + s->index == index) + return s; + } + return NULL; +} + +struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props); +void avdecc_server_free(struct server *server); + +void avdecc_server_add_listener(struct server *server, struct spa_hook *listener, + const struct server_events *events, void *data); + +int avb_server_make_socket(struct server *server, uint16_t type, const uint8_t mac[6]); + +int avb_server_send_packet(struct server *server, const uint8_t dest[6], + uint16_t type, void *data, size_t size); + +struct aecp { + struct server *server; + struct spa_hook server_listener; +}; + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AVB_INTERNAL_H */ diff --git a/src/modules/module-avb/maap.c b/src/modules/module-avb/maap.c new file mode 100644 index 0000000000000000000000000000000000000000..bd72a2635d918d115e4709c5b5855e58744b00ab --- /dev/null +++ b/src/modules/module-avb/maap.c @@ -0,0 +1,470 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <unistd.h> + +#include <spa/utils/json.h> + +#include <pipewire/pipewire.h> +#include <pipewire/conf.h> + +#include "utils.h" +#include "maap.h" + +#define MAAP_ALLOCATION_POOL_SIZE 0xFE00 +#define MAAP_ALLOCATION_POOL_BASE { 0x91, 0xe0, 0xf0, 0x00, 0x00, 0x00 } +static uint8_t maap_base[6] = MAAP_ALLOCATION_POOL_BASE; + +#define MAAP_PROBE_RETRANSMITS 3 + +#define MAAP_PROBE_INTERVAL_MS 500 +#define MAAP_PROBE_INTERVAL_VAR_MS 100 + +#define MAAP_ANNOUNCE_INTERVAL_MS 3000 +#define MAAP_ANNOUNCE_INTERVAL_VAR_MS 2000 + +struct maap { + struct server *server; + struct spa_hook server_listener; + + struct pw_properties *props; + + struct spa_source *source; + +#define STATE_IDLE 0 +#define STATE_PROBE 1 +#define STATE_ANNOUNCE 2 + uint32_t state; + uint64_t timeout; + uint32_t probe_count; + + unsigned short xsubi[3]; + + uint16_t offset; + uint16_t count; +}; + +static const char *message_type_as_string(uint8_t message_type) +{ + switch (message_type) { + case AVB_MAAP_MESSAGE_TYPE_PROBE: + return "PROBE"; + case AVB_MAAP_MESSAGE_TYPE_DEFEND: + return "DEFEND"; + case AVB_MAAP_MESSAGE_TYPE_ANNOUNCE: + return "ANNOUNCE"; + } + return "INVALID"; +} + +static void maap_message_debug(struct maap *maap, const struct avb_packet_maap *p) +{ + uint32_t v; + const uint8_t *addr; + + v = AVB_PACKET_MAAP_GET_MESSAGE_TYPE(p); + pw_log_info("message-type: %d (%s)", v, message_type_as_string(v)); + pw_log_info(" maap-version: %d", AVB_PACKET_MAAP_GET_MAAP_VERSION(p)); + pw_log_info(" length: %d", AVB_PACKET_GET_LENGTH(&p->hdr)); + + pw_log_info(" stream-id: 0x%"PRIx64, AVB_PACKET_MAAP_GET_STREAM_ID(p)); + addr = AVB_PACKET_MAAP_GET_REQUEST_START(p); + pw_log_info(" request-start: %02x:%02x:%02x:%02x:%02x:%02x", + addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + pw_log_info(" request-count: %d", AVB_PACKET_MAAP_GET_REQUEST_COUNT(p)); + addr = AVB_PACKET_MAAP_GET_CONFLICT_START(p); + pw_log_info(" conflict-start: %02x:%02x:%02x:%02x:%02x:%02x", + addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + pw_log_info(" conflict-count: %d", AVB_PACKET_MAAP_GET_CONFLICT_COUNT(p)); +} + +#define PROBE_TIMEOUT(n) ((n) + (MAAP_PROBE_INTERVAL_MS + \ + drand48() * MAAP_PROBE_INTERVAL_VAR_MS) * SPA_NSEC_PER_MSEC) +#define ANNOUNCE_TIMEOUT(n) ((n) + (MAAP_ANNOUNCE_INTERVAL_MS + \ + drand48() * MAAP_ANNOUNCE_INTERVAL_VAR_MS) * SPA_NSEC_PER_MSEC) + +static int make_new_address(struct maap *maap, uint64_t now, int range) +{ + maap->offset = nrand48(maap->xsubi) % (MAAP_ALLOCATION_POOL_SIZE - range); + maap->count = range; + maap->state = STATE_PROBE; + maap->probe_count = MAAP_PROBE_RETRANSMITS; + maap->timeout = PROBE_TIMEOUT(now); + return 0; +} + +static uint16_t maap_check_conflict(struct maap *maap, const uint8_t request_start[6], + uint16_t request_count, uint8_t conflict_start[6]) +{ + uint16_t our_start, our_end; + uint16_t req_start, req_end; + uint16_t conf_start, conf_count = 0; + + if (memcmp(request_start, maap_base, 4) != 0) + return 0; + + our_start = maap->offset; + our_end = our_start + maap->count; + req_start = request_start[4] << 8 | request_start[5]; + req_end = req_start + request_count; + + if (our_start >= req_start && our_start <= req_end) { + conf_start = our_start; + conf_count = SPA_MIN(our_end, req_end) - our_start; + } + else if (req_start >= our_start && req_start <= our_end) { + conf_start = req_start; + conf_count = SPA_MIN(req_end, our_end) - req_start; + } + if (conf_count == 0) + return 0; + + conflict_start[4] = conf_start >> 8; + conflict_start[5] = conf_start; + return conf_count; +} + +static int send_packet(struct maap *maap, uint64_t now, + uint8_t type, const uint8_t conflict_start[6], uint16_t conflict_count) +{ + struct avb_ethernet_header *h; + struct avb_packet_maap *p; + uint8_t buf[1024]; + uint8_t bmac[6] = AVB_MAAP_MAC; + int res = 0; + uint8_t start[6]; + + spa_memzero(buf, sizeof(buf)); + h = (void*)buf; + p = SPA_PTROFF(h, sizeof(*h), void); + + memcpy(h->dest, bmac, 6); + memcpy(h->src, maap->server->mac_addr, 6); + h->type = htons(AVB_TSN_ETH); + + p->hdr.subtype = AVB_SUBTYPE_MAAP; + AVB_PACKET_SET_LENGTH(&p->hdr, sizeof(*p)); + + AVB_PACKET_MAAP_SET_MAAP_VERSION(p, 1); + AVB_PACKET_MAAP_SET_MESSAGE_TYPE(p, type); + + memcpy(start, maap_base, 4); + start[4] = maap->offset >> 8; + start[5] = maap->offset; + AVB_PACKET_MAAP_SET_REQUEST_START(p, start); + AVB_PACKET_MAAP_SET_REQUEST_COUNT(p, maap->count); + if (conflict_count) { + AVB_PACKET_MAAP_SET_CONFLICT_START(p, conflict_start); + AVB_PACKET_MAAP_SET_CONFLICT_COUNT(p, conflict_count); + } + + if (maap->server->debug_messages) { + pw_log_info("send: %d (%s)", type, message_type_as_string(type)); + maap_message_debug(maap, p); + } + + if (send(maap->source->fd, p, sizeof(*h) + sizeof(*p), 0) < 0) { + res = -errno; + pw_log_warn("got send error: %m"); + } + return res; +} + +static int handle_probe(struct maap *maap, uint64_t now, const struct avb_packet_maap *p) +{ + uint8_t conflict_start[6]; + uint16_t conflict_count; + + conflict_count = maap_check_conflict(maap, p->request_start, ntohs(p->request_count), + conflict_start); + if (conflict_count == 0) + return 0; + + switch (maap->state) { + case STATE_PROBE: + make_new_address(maap, now, 8); + break; + case STATE_ANNOUNCE: + send_packet(maap, now, AVB_MAAP_MESSAGE_TYPE_DEFEND, conflict_start, conflict_count); + break; + } + return 0; +} + +static int handle_defend(struct maap *maap, uint64_t now, const struct avb_packet_maap *p) +{ + uint8_t conflict_start[6]; + uint16_t conflict_count; + + conflict_count = maap_check_conflict(maap, p->conflict_start, ntohs(p->conflict_count), + conflict_start); + if (conflict_count != 0) + make_new_address(maap, now, 8); + return 0; +} + +static int maap_message(struct maap *maap, uint64_t now, const void *message, int len) +{ + const struct avb_packet_maap *p = message; + + if (AVB_PACKET_GET_SUBTYPE(&p->hdr) != AVB_SUBTYPE_MAAP) + return 0; + + if (maap->server->debug_messages) + maap_message_debug(maap, p); + + switch (AVB_PACKET_MAAP_GET_MESSAGE_TYPE(p)) { + case AVB_MAAP_MESSAGE_TYPE_PROBE: + handle_probe(maap, now, p); + break; + case AVB_MAAP_MESSAGE_TYPE_DEFEND: + case AVB_MAAP_MESSAGE_TYPE_ANNOUNCE: + handle_defend(maap, now, p); + break; + } + return 0; +} + +static void on_socket_data(void *data, int fd, uint32_t mask) +{ + struct maap *maap = data; + struct timespec now; + + if (mask & SPA_IO_IN) { + int len; + uint8_t buffer[2048]; + + len = recv(fd, buffer, sizeof(buffer), 0); + + if (len < 0) { + pw_log_warn("got recv error: %m"); + } + else if (len < (int)sizeof(struct avb_packet_header)) { + pw_log_warn("short packet received (%d < %d)", len, + (int)sizeof(struct avb_packet_header)); + } else { + clock_gettime(CLOCK_REALTIME, &now); + maap_message(maap, SPA_TIMESPEC_TO_NSEC(&now), buffer, len); + } + } +} + +static int load_state(struct maap *maap) +{ + const char *str; + char key[512]; + struct spa_json it[3]; + bool have_offset = false; + int count = 0, offset = 0; + + snprintf(key, sizeof(key), "maap.%s", maap->server->ifname); + pw_conf_load_state("module-avb", key, maap->props); + + if ((str = pw_properties_get(maap->props, "maap.addresses")) == NULL) + return 0; + + spa_json_init(&it[0], str, strlen(str)); + if (spa_json_enter_array(&it[0], &it[1]) <= 0) + return 0; + + if (spa_json_enter_object(&it[1], &it[2]) <= 0) + return 0; + + while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) { + const char *val; + int len; + + if ((len = spa_json_next(&it[2], &val)) <= 0) + break; + + if (spa_streq(key, "start")) { + uint8_t addr[6]; + if (avb_utils_parse_addr(val, len, addr) >= 0 && + memcmp(addr, maap_base, 4) == 0) { + offset = addr[4] << 8 | addr[5]; + have_offset = true; + } + } + else if (spa_streq(key, "count")) { + spa_json_parse_int(val, len, &count); + } + } + if (count > 0 && have_offset) { + maap->count = count; + maap->offset = offset; + maap->state = STATE_PROBE; + maap->probe_count = MAAP_PROBE_RETRANSMITS; + maap->timeout = PROBE_TIMEOUT(0); + } + return 0; +} + +static int save_state(struct maap *maap) +{ + char *ptr; + size_t size; + FILE *f; + char key[512]; + uint32_t count; + + if ((f = open_memstream(&ptr, &size)) == NULL) + return -errno; + + fprintf(f, "[ "); + fprintf(f, "{ \"start\": \"%02x:%02x:%02x:%02x:%02x:%02x\", ", + maap_base[0], maap_base[1], maap_base[2], + maap_base[3], (maap->offset >> 8) & 0xff, + maap->offset & 0xff); + fprintf(f, " \"count\": %u } ", maap->count); + fprintf(f, "]"); + fclose(f); + + count = pw_properties_set(maap->props, "maap.addresses", ptr); + free(ptr); + + if (count > 0) { + snprintf(key, sizeof(key), "maap.%s", maap->server->ifname); + pw_conf_save_state("module-avb", key, maap->props); + } + return 0; +} + +static void maap_periodic(void *data, uint64_t now) +{ + struct maap *maap = data; + + if (now < maap->timeout) + return; + + switch(maap->state) { + case STATE_IDLE: + break; + case STATE_PROBE: + send_packet(maap, now, AVB_MAAP_MESSAGE_TYPE_PROBE, NULL, 0); + if (--maap->probe_count == 0) { + maap->state = STATE_ANNOUNCE; + save_state(maap); + } + maap->timeout = PROBE_TIMEOUT(now); + break; + case STATE_ANNOUNCE: + send_packet(maap, now, AVB_MAAP_MESSAGE_TYPE_ANNOUNCE, NULL, 0); + maap->timeout = ANNOUNCE_TIMEOUT(now); + break; + } +} + +static void maap_free(struct maap *maap) +{ + pw_loop_destroy_source(maap->server->impl->loop, maap->source); + spa_hook_remove(&maap->server_listener); + pw_properties_free(maap->props); + free(maap); +} + +static void maap_destroy(void *data) +{ + struct maap *maap = data; + maap_free(maap); +} + +static const struct server_events server_events = { + AVB_VERSION_SERVER_EVENTS, + .destroy = maap_destroy, + .periodic = maap_periodic, +}; + +struct avb_maap *avb_maap_register(struct server *server) +{ + struct maap *maap; + uint8_t bmac[6] = AVB_MAAP_MAC; + int fd, res; + + fd = avb_server_make_socket(server, AVB_TSN_ETH, bmac); + if (fd < 0) { + res = fd; + goto error; + } + + maap = calloc(1, sizeof(*maap)); + if (maap == NULL) { + res = -errno; + goto error_close; + } + maap->props = pw_properties_new(NULL, NULL); + if (maap->props == NULL) { + res = -errno; + goto error_free; + } + + maap->server = server; + pw_log_info("0x%"PRIx64" %d", server->entity_id, server->ifindex); + + if (pw_getrandom(maap->xsubi, sizeof(maap->xsubi), 0) != sizeof(maap->xsubi)) { + res = -errno; + goto error_free; + } + load_state(maap); + + maap->source = pw_loop_add_io(server->impl->loop, fd, SPA_IO_IN, true, on_socket_data, maap); + if (maap->source == NULL) { + res = -errno; + pw_log_error("maap %p: can't create maap source: %m", maap); + goto error_free; + } + avdecc_server_add_listener(server, &maap->server_listener, &server_events, maap); + + return (struct avb_maap *)maap; + +error_free: + free(maap); +error_close: + close(fd); +error: + errno = -res; + return NULL; +} + +int avb_maap_reserve(struct avb_maap *m, uint32_t count) +{ + struct maap *maap = (struct maap*)m; + if (count > maap->count) + make_new_address(maap, 0, count); + return 0; +} + +int avb_maap_get_address(struct avb_maap *m, uint8_t addr[6], uint32_t index) +{ + struct maap *maap = (struct maap*)m; + uint16_t offset; + + if (maap->state != STATE_ANNOUNCE) + return -EAGAIN; + + memcpy(addr, maap_base, 6); + offset = maap->offset + index; + addr[4] = offset >> 8; + addr[5] = offset; + return 0; +} diff --git a/src/modules/module-avb/maap.h b/src/modules/module-avb/maap.h new file mode 100644 index 0000000000000000000000000000000000000000..6e56f8e9ae857a24913fff9871da203c150d550b --- /dev/null +++ b/src/modules/module-avb/maap.h @@ -0,0 +1,70 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_MAAP_H +#define AVB_MAAP_H + +#include "packets.h" +#include "internal.h" + +#define AVB_TSN_ETH 0x22f0 +#define AVB_MAAP_MAC { 0x91, 0xe0, 0xf0, 0x00, 0xff, 0x00 }; + +#define AVB_MAAP_MESSAGE_TYPE_PROBE 1 +#define AVB_MAAP_MESSAGE_TYPE_DEFEND 2 +#define AVB_MAAP_MESSAGE_TYPE_ANNOUNCE 3 + +struct avb_packet_maap { + struct avb_packet_header hdr; + uint64_t stream_id; + uint8_t request_start[6]; + uint16_t request_count; + uint8_t conflict_start[6]; + uint16_t conflict_count; +} __attribute__ ((__packed__)); + +#define AVB_PACKET_MAAP_SET_MESSAGE_TYPE(p,v) AVB_PACKET_SET_SUB1(&(p)->hdr, v) +#define AVB_PACKET_MAAP_SET_MAAP_VERSION(p,v) AVB_PACKET_SET_SUB2(&(p)->hdr, v) +#define AVB_PACKET_MAAP_SET_STREAM_ID(p,v) ((p)->stream_id = htobe64(v)) +#define AVB_PACKET_MAAP_SET_REQUEST_START(p,v) memcpy((p)->request_start, (v), 6) +#define AVB_PACKET_MAAP_SET_REQUEST_COUNT(p,v) ((p)->request_count = htons(v)) +#define AVB_PACKET_MAAP_SET_CONFLICT_START(p,v) memcpy((p)->conflict_start, (v), 6) +#define AVB_PACKET_MAAP_SET_CONFLICT_COUNT(p,v) ((p)->conflict_count = htons(v)) + +#define AVB_PACKET_MAAP_GET_MESSAGE_TYPE(p) AVB_PACKET_GET_SUB1(&(p)->hdr) +#define AVB_PACKET_MAAP_GET_MAAP_VERSION(p) AVB_PACKET_GET_SUB2(&(p)->hdr) +#define AVB_PACKET_MAAP_GET_STREAM_ID(p) be64toh((p)->stream_id) +#define AVB_PACKET_MAAP_GET_REQUEST_START(p) ((p)->request_start) +#define AVB_PACKET_MAAP_GET_REQUEST_COUNT(p) ntohs((p)->request_count) +#define AVB_PACKET_MAAP_GET_CONFLICT_START(p) ((p)->conflict_start) +#define AVB_PACKET_MAAP_GET_CONFLICT_COUNT(p) ntohs((p)->conflict_count) + +struct avb_maap; + +struct avb_maap *avb_maap_register(struct server *server); + +int avb_maap_reserve(struct avb_maap *maap, uint32_t count); +int avb_maap_get_address(struct avb_maap *maap, uint8_t addr[6], uint32_t index); + +#endif /* AVB_MAAP_H */ diff --git a/src/modules/module-avb/mmrp.c b/src/modules/module-avb/mmrp.c new file mode 100644 index 0000000000000000000000000000000000000000..022aea8b3c25334fb55e03de848d7a1995cd624f --- /dev/null +++ b/src/modules/module-avb/mmrp.c @@ -0,0 +1,233 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <unistd.h> + +#include <pipewire/pipewire.h> + +#include "utils.h" +#include "mmrp.h" + +static const uint8_t mmrp_mac[6] = AVB_MMRP_MAC; + +struct attr { + struct avb_mmrp_attribute attr; + struct spa_list link; +}; + +struct mmrp { + struct server *server; + struct spa_hook server_listener; + + struct spa_source *source; + + struct spa_list attributes; +}; + +static bool mmrp_check_header(void *data, const void *hdr, size_t *hdr_size, bool *has_params) +{ + const struct avb_packet_mmrp_msg *msg = hdr; + uint8_t attr_type = msg->attribute_type; + + if (!AVB_MMRP_ATTRIBUTE_TYPE_VALID(attr_type)) + return false; + + *hdr_size = sizeof(*msg); + *has_params = false; + return true; +} + +static int mmrp_attr_event(void *data, uint64_t now, uint8_t attribute_type, uint8_t event) +{ + struct mmrp *mmrp = data; + struct attr *a; + spa_list_for_each(a, &mmrp->attributes, link) + if (a->attr.type == attribute_type) + avb_mrp_attribute_update_state(a->attr.mrp, now, event); + return 0; +} + +static void debug_service_requirement(const struct avb_packet_mmrp_service_requirement *t) +{ + char buf[128]; + pw_log_info("service requirement"); + pw_log_info(" %s", avb_utils_format_addr(buf, sizeof(buf), t->addr)); +} + +static int process_service_requirement(struct mmrp *mmrp, uint64_t now, uint8_t attr_type, + const void *m, uint8_t event, uint8_t param, int num) +{ + const struct avb_packet_mmrp_service_requirement *t = m; + struct attr *a; + + debug_service_requirement(t); + + spa_list_for_each(a, &mmrp->attributes, link) + if (a->attr.type == attr_type && + memcmp(a->attr.attr.service_requirement.addr, t->addr, 6) == 0) + avb_mrp_attribute_rx_event(a->attr.mrp, now, event); + return 0; +} + +static void debug_process_mac(const struct avb_packet_mmrp_mac *t) +{ + char buf[128]; + pw_log_info("mac"); + pw_log_info(" %s", avb_utils_format_addr(buf, sizeof(buf), t->addr)); +} + +static int process_mac(struct mmrp *mmrp, uint64_t now, uint8_t attr_type, + const void *m, uint8_t event, uint8_t param, int num) +{ + const struct avb_packet_mmrp_mac *t = m; + struct attr *a; + + debug_process_mac(t); + + spa_list_for_each(a, &mmrp->attributes, link) + if (a->attr.type == attr_type && + memcmp(a->attr.attr.mac.addr, t->addr, 6) == 0) + avb_mrp_attribute_rx_event(a->attr.mrp, now, event); + return 0; +} + +static const struct { + int (*dispatch) (struct mmrp *mmrp, uint64_t now, uint8_t attr_type, + const void *m, uint8_t event, uint8_t param, int num); +} dispatch[] = { + [AVB_MMRP_ATTRIBUTE_TYPE_SERVICE_REQUIREMENT] = { process_service_requirement, }, + [AVB_MMRP_ATTRIBUTE_TYPE_MAC] = { process_mac, }, +}; + +static int mmrp_process(void *data, uint64_t now, uint8_t attribute_type, const void *value, + uint8_t event, uint8_t param, int index) +{ + struct mmrp *mmrp = data; + return dispatch[attribute_type].dispatch(mmrp, now, + attribute_type, value, event, param, index); +} + +static const struct avb_mrp_parse_info info = { + AVB_VERSION_MRP_PARSE_INFO, + .check_header = mmrp_check_header, + .attr_event = mmrp_attr_event, + .process = mmrp_process, +}; + +static int mmrp_message(struct mmrp *mmrp, uint64_t now, const void *message, int len) +{ + pw_log_debug("MMRP"); + return avb_mrp_parse_packet(mmrp->server->mrp, + now, message, len, &info, mmrp); +} + +static void on_socket_data(void *data, int fd, uint32_t mask) +{ + struct mmrp *mmrp = data; + struct timespec now; + + if (mask & SPA_IO_IN) { + int len; + uint8_t buffer[2048]; + + len = recv(fd, buffer, sizeof(buffer), 0); + + if (len < 0) { + pw_log_warn("got recv error: %m"); + } + else if (len < (int)sizeof(struct avb_packet_header)) { + pw_log_warn("short packet received (%d < %d)", len, + (int)sizeof(struct avb_packet_header)); + } else { + clock_gettime(CLOCK_REALTIME, &now); + mmrp_message(mmrp, SPA_TIMESPEC_TO_NSEC(&now), buffer, len); + } + } +} +static void mmrp_destroy(void *data) +{ + struct mmrp *mmrp = data; + spa_hook_remove(&mmrp->server_listener); + pw_loop_destroy_source(mmrp->server->impl->loop, mmrp->source); + free(mmrp); +} + +static const struct server_events server_events = { + AVB_VERSION_SERVER_EVENTS, + .destroy = mmrp_destroy, +}; + +struct avb_mmrp_attribute *avb_mmrp_attribute_new(struct avb_mmrp *m, + uint8_t type) +{ + struct mmrp *mmrp = (struct mmrp*)m; + struct avb_mrp_attribute *attr; + struct attr *a; + + attr = avb_mrp_attribute_new(mmrp->server->mrp, sizeof(struct attr)); + + a = attr->user_data; + a->attr.mrp = attr; + a->attr.type = type; + spa_list_append(&mmrp->attributes, &a->link); + + return &a->attr; +} + +struct avb_mmrp *avb_mmrp_register(struct server *server) +{ + struct mmrp *mmrp; + int fd, res; + + fd = avb_server_make_socket(server, AVB_MMRP_ETH, mmrp_mac); + if (fd < 0) { + errno = -fd; + return NULL; + } + mmrp = calloc(1, sizeof(*mmrp)); + if (mmrp == NULL) { + res = -errno; + goto error_close; + } + + mmrp->server = server; + spa_list_init(&mmrp->attributes); + + mmrp->source = pw_loop_add_io(server->impl->loop, fd, SPA_IO_IN, true, on_socket_data, mmrp); + if (mmrp->source == NULL) { + res = -errno; + pw_log_error("mmrp %p: can't create mmrp source: %m", mmrp); + goto error_no_source; + } + avdecc_server_add_listener(server, &mmrp->server_listener, &server_events, mmrp); + + return (struct avb_mmrp*)mmrp; + +error_no_source: + free(mmrp); +error_close: + close(fd); + errno = -res; + return NULL; +} diff --git a/src/modules/module-avb/mmrp.h b/src/modules/module-avb/mmrp.h new file mode 100644 index 0000000000000000000000000000000000000000..b7bcf8c46ef78d5189a3ae58c79b669d21fe11e4 --- /dev/null +++ b/src/modules/module-avb/mmrp.h @@ -0,0 +1,68 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_MMRP_H +#define AVB_MMRP_H + +#include "mrp.h" +#include "internal.h" + +#define AVB_MMRP_ETH 0x88f6 +#define AVB_MMRP_MAC { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x20 } + +#define AVB_MMRP_ATTRIBUTE_TYPE_SERVICE_REQUIREMENT 1 +#define AVB_MMRP_ATTRIBUTE_TYPE_MAC 2 +#define AVB_MMRP_ATTRIBUTE_TYPE_VALID(t) ((t)>=1 && (t)<=2) + +struct avb_packet_mmrp_msg { + uint8_t attribute_type; + uint8_t attribute_length; + uint8_t attribute_list[0]; +} __attribute__ ((__packed__)); + +struct avb_packet_mmrp_service_requirement { + unsigned char addr[6]; +} __attribute__ ((__packed__)); + +struct avb_packet_mmrp_mac { + unsigned char addr[6]; +} __attribute__ ((__packed__)); + +struct avb_mmrp; + +struct avb_mmrp_attribute { + struct avb_mrp_attribute *mrp; + uint8_t type; + union { + struct avb_packet_mmrp_service_requirement service_requirement; + struct avb_packet_mmrp_mac mac; + } attr; +}; + +struct avb_mmrp_attribute *avb_mmrp_attribute_new(struct avb_mmrp *mmrp, + uint8_t type); + +struct avb_mmrp *avb_mmrp_register(struct server *server); + +#endif /* AVB_MMRP_H */ diff --git a/src/modules/module-avb/mrp.c b/src/modules/module-avb/mrp.c new file mode 100644 index 0000000000000000000000000000000000000000..7b6bc464681a9591a2b68e75a3fde7d5aeb1ceec --- /dev/null +++ b/src/modules/module-avb/mrp.c @@ -0,0 +1,612 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <pipewire/pipewire.h> + +#include "mrp.h" + +#define MRP_JOINTIMER_MS 100 +#define MRP_LVTIMER_MS 1000 +#define MRP_LVATIMER_MS 10000 +#define MRP_PERIODTIMER_MS 1000 + +#define mrp_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct avb_mrp_events, m, v, ##__VA_ARGS__) +#define mrp_emit_event(s,n,e) mrp_emit(s,event,0,n,e) +#define mrp_emit_notify(s,n,a,e) mrp_emit(s,notify,0,n,a,e) + +#define mrp_attribute_emit(a,m,v,...) spa_hook_list_call(&a->listener_list, struct avb_mrp_attribute_events, m, v, ##__VA_ARGS__) +#define mrp_attribute_emit_notify(a,n,e) mrp_attribute_emit(a,notify,0,n,e) + + +struct mrp; + +struct attribute { + struct avb_mrp_attribute attr; + struct mrp *mrp; + struct spa_list link; + uint8_t applicant_state; + uint8_t registrar_state; + uint64_t leave_timeout; + unsigned joined:1; + struct spa_hook_list listener_list; +}; + +struct mrp { + struct server *server; + struct spa_hook server_listener; + + struct spa_hook_list listener_list; + + struct spa_list attributes; + + uint64_t periodic_timeout; + uint64_t leave_all_timeout; + uint64_t join_timeout; +}; + +static void mrp_destroy(void *data) +{ + struct mrp *mrp = data; + spa_hook_remove(&mrp->server_listener); + free(mrp); +} + +static void global_event(struct mrp *mrp, uint64_t now, uint8_t event) +{ + struct attribute *a; + spa_list_for_each(a, &mrp->attributes, link) + avb_mrp_attribute_update_state(&a->attr, now, event); + mrp_emit_event(mrp, now, event); +} + +static void mrp_periodic(void *data, uint64_t now) +{ + struct mrp *mrp = data; + bool leave_all = false; + struct attribute *a; + + if (now > mrp->periodic_timeout) { + if (mrp->periodic_timeout > 0) + global_event(mrp, now, AVB_MRP_EVENT_PERIODIC); + mrp->periodic_timeout = now + MRP_PERIODTIMER_MS * SPA_NSEC_PER_MSEC; + } + if (now > mrp->leave_all_timeout) { + if (mrp->leave_all_timeout > 0) { + global_event(mrp, now, AVB_MRP_EVENT_RX_LVA); + leave_all = true; + } + mrp->leave_all_timeout = now + (MRP_LVATIMER_MS + (random() % (MRP_LVATIMER_MS / 2))) + * SPA_NSEC_PER_MSEC; + } + + if (now > mrp->join_timeout) { + if (mrp->join_timeout > 0) { + uint8_t event = leave_all ? AVB_MRP_EVENT_TX_LVA : AVB_MRP_EVENT_TX; + global_event(mrp, now, event); + } + mrp->join_timeout = now + MRP_JOINTIMER_MS * SPA_NSEC_PER_MSEC; + } + + spa_list_for_each(a, &mrp->attributes, link) { + if (a->leave_timeout > 0 && now > a->leave_timeout) { + a->leave_timeout = 0; + avb_mrp_attribute_update_state(&a->attr, now, AVB_MRP_EVENT_LV_TIMER); + } + } +} + +static const struct server_events server_events = { + AVB_VERSION_SERVER_EVENTS, + .destroy = mrp_destroy, + .periodic = mrp_periodic, +}; + +int avb_mrp_parse_packet(struct avb_mrp *mrp, uint64_t now, const void *pkt, int len, + const struct avb_mrp_parse_info *info, void *data) +{ + uint8_t *e = SPA_PTROFF(pkt, len, uint8_t); + uint8_t *m = SPA_PTROFF(pkt, sizeof(struct avb_packet_mrp), uint8_t); + + while (m < e && (m[0] != 0 || m[1] != 0)) { + const struct avb_packet_mrp_hdr *hdr = (const struct avb_packet_mrp_hdr*)m; + uint8_t attr_type = hdr->attribute_type; + uint8_t attr_len = hdr->attribute_length; + size_t hdr_size; + bool has_param; + + if (!info->check_header(data, hdr, &hdr_size, &has_param)) + return -EINVAL; + + m += hdr_size; + + while (m < e && (m[0] != 0 || m[1] != 0)) { + const struct avb_packet_mrp_vector *v = + (const struct avb_packet_mrp_vector*)m; + uint16_t i, num_values = AVB_MRP_VECTOR_GET_NUM_VALUES(v); + uint8_t event_len = (num_values+2)/3; + uint8_t param_len = has_param ? (num_values+3)/4 : 0; + int plen = sizeof(*v) + attr_len + event_len + param_len; + const uint8_t *first = v->first_value; + uint8_t event[3], param[4] = { 0, }; + + if (m + plen > e) + return -EPROTO; + + if (v->lva) + info->attr_event(data, now, attr_type, AVB_MRP_EVENT_RX_LVA); + + for (i = 0; i < num_values; i++) { + if (i % 3 == 0) { + uint8_t ep = first[attr_len + i/3]; + event[2] = ep % 6; ep /= 6; + event[1] = ep % 6; ep /= 6; + event[0] = ep % 6; + } + if (has_param && (i % 4 == 0)) { + uint8_t ep = first[attr_len + event_len + i/4]; + param[3] = ep % 4; ep /= 4; + param[2] = ep % 4; ep /= 4; + param[1] = ep % 4; ep /= 4; + param[0] = ep % 4; + } + info->process(data, now, attr_type, first, + event[i%3], param[i%4], i); + } + m += plen; + } + m += 2; + } + return 0; +} + +const char *avb_mrp_notify_name(uint8_t notify) +{ + switch(notify) { + case AVB_MRP_NOTIFY_NEW: + return "new"; + case AVB_MRP_NOTIFY_JOIN: + return "join"; + case AVB_MRP_NOTIFY_LEAVE: + return "leave"; + } + return "unknown"; +} + +const char *avb_mrp_send_name(uint8_t send) +{ + switch(send) { + case AVB_MRP_SEND_NEW: + return "new"; + case AVB_MRP_SEND_JOININ: + return "joinin"; + case AVB_MRP_SEND_IN: + return "in"; + case AVB_MRP_SEND_JOINMT: + return "joinmt"; + case AVB_MRP_SEND_MT: + return "mt"; + case AVB_MRP_SEND_LV: + return "leave"; + } + return "unknown"; +} + +struct avb_mrp_attribute *avb_mrp_attribute_new(struct avb_mrp *m, + size_t user_size) +{ + struct mrp *mrp = (struct mrp*)m; + struct attribute *a; + + a = calloc(1, sizeof(*a) + user_size); + if (a == NULL) + return NULL; + + a->mrp = mrp; + a->attr.user_data = SPA_PTROFF(a, sizeof(*a), void); + spa_hook_list_init(&a->listener_list); + spa_list_append(&mrp->attributes, &a->link); + + return &a->attr; +} + +void avb_mrp_attribute_destroy(struct avb_mrp_attribute *attr) +{ + struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr); + spa_list_remove(&a->link); + free(a); +} + +void avb_mrp_attribute_add_listener(struct avb_mrp_attribute *attr, struct spa_hook *listener, + const struct avb_mrp_attribute_events *events, void *data) +{ + struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr); + spa_hook_list_append(&a->listener_list, listener, events, data); +} + +void avb_mrp_attribute_update_state(struct avb_mrp_attribute *attr, uint64_t now, + int event) +{ + struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr); + struct mrp *mrp = a->mrp; + uint8_t notify = 0, state; + uint8_t send = 0; + + state = a->registrar_state; + + switch (event) { + case AVB_MRP_EVENT_BEGIN: + state = AVB_MRP_MT; + break; + case AVB_MRP_EVENT_RX_NEW: + notify = AVB_MRP_NOTIFY_NEW; + switch (state) { + case AVB_MRP_LV: + a->leave_timeout = 0; + break; + } + state = AVB_MRP_IN; + break; + case AVB_MRP_EVENT_RX_JOININ: + case AVB_MRP_EVENT_RX_JOINMT: + switch (state) { + case AVB_MRP_LV: + a->leave_timeout = 0; + break; + case AVB_MRP_MT: + notify = AVB_MRP_NOTIFY_JOIN; + break; + } + state = AVB_MRP_IN; + break; + case AVB_MRP_EVENT_RX_LV: + case AVB_MRP_EVENT_RX_LVA: + case AVB_MRP_EVENT_TX_LVA: + case AVB_MRP_EVENT_REDECLARE: + switch (state) { + case AVB_MRP_IN: + a->leave_timeout = now + MRP_LVTIMER_MS * SPA_NSEC_PER_MSEC; + //state = AVB_MRP_LV; + break; + } + break; + case AVB_MRP_EVENT_FLUSH: + switch (state) { + case AVB_MRP_LV: + notify = AVB_MRP_NOTIFY_LEAVE; + break; + } + state = AVB_MRP_MT; + break; + case AVB_MRP_EVENT_LV_TIMER: + switch (state) { + case AVB_MRP_LV: + notify = AVB_MRP_NOTIFY_LEAVE; + state = AVB_MRP_MT; + break; + } + break; + default: + break; + } + if (notify) { + mrp_attribute_emit_notify(a, now, notify); + mrp_emit_notify(mrp, now, &a->attr, notify); + } + + if (a->registrar_state != state || notify) { + pw_log_debug("attr %p: %d %d -> %d %d", a, event, a->registrar_state, state, notify); + a->registrar_state = state; + } + + state = a->applicant_state; + + switch (event) { + case AVB_MRP_EVENT_BEGIN: + state = AVB_MRP_VO; + break; + case AVB_MRP_EVENT_NEW: + switch (state) { + case AVB_MRP_VN: + case AVB_MRP_AN: + break; + default: + state = AVB_MRP_VN; + break; + } + break; + case AVB_MRP_EVENT_JOIN: + switch (state) { + case AVB_MRP_VO: + case AVB_MRP_LO: + state = AVB_MRP_VP; + break; + case AVB_MRP_LA: + state = AVB_MRP_AA; + break; + case AVB_MRP_AO: + state = AVB_MRP_AP; + break; + case AVB_MRP_QO: + state = AVB_MRP_QP; + break; + } + break; + case AVB_MRP_EVENT_LV: + switch (state) { + case AVB_MRP_VP: + state = AVB_MRP_VO; + break; + case AVB_MRP_VN: + case AVB_MRP_AN: + case AVB_MRP_AA: + case AVB_MRP_QA: + state = AVB_MRP_LA; + break; + case AVB_MRP_AP: + state = AVB_MRP_AO; + break; + case AVB_MRP_QP: + state = AVB_MRP_QO; + break; + } + break; + case AVB_MRP_EVENT_RX_JOININ: + switch (state) { + case AVB_MRP_VO: + state = AVB_MRP_AO; + break; + case AVB_MRP_VP: + state = AVB_MRP_AP; + break; + case AVB_MRP_AA: + state = AVB_MRP_QA; + break; + case AVB_MRP_AO: + state = AVB_MRP_QO; + break; + case AVB_MRP_AP: + state = AVB_MRP_QP; + break; + } + SPA_FALLTHROUGH; + case AVB_MRP_EVENT_RX_IN: + switch (state) { + case AVB_MRP_AA: + state = AVB_MRP_QA; + break; + } + break; + case AVB_MRP_EVENT_RX_JOINMT: + case AVB_MRP_EVENT_RX_MT: + switch (state) { + case AVB_MRP_QA: + state = AVB_MRP_AA; + break; + case AVB_MRP_QO: + state = AVB_MRP_AO; + break; + case AVB_MRP_QP: + state = AVB_MRP_AP; + break; + case AVB_MRP_LO: + state = AVB_MRP_VO; + break; + } + break; + case AVB_MRP_EVENT_RX_LV: + case AVB_MRP_EVENT_RX_LVA: + case AVB_MRP_EVENT_REDECLARE: + switch (state) { + case AVB_MRP_VO: + case AVB_MRP_AO: + case AVB_MRP_QO: + state = AVB_MRP_LO; + break; + case AVB_MRP_AN: + state = AVB_MRP_VN; + break; + case AVB_MRP_AA: + case AVB_MRP_QA: + case AVB_MRP_AP: + case AVB_MRP_QP: + state = AVB_MRP_VP; + break; + } + break; + case AVB_MRP_EVENT_PERIODIC: + switch (state) { + case AVB_MRP_QA: + state = AVB_MRP_AA; + break; + case AVB_MRP_QP: + state = AVB_MRP_AP; + break; + } + break; + case AVB_MRP_EVENT_TX: + switch (state) { + case AVB_MRP_VP: + case AVB_MRP_AA: + case AVB_MRP_AP: + if (a->registrar_state == AVB_MRP_IN) + send = AVB_MRP_SEND_JOININ; + else + send = AVB_MRP_SEND_JOINMT; + break; + case AVB_MRP_VN: + case AVB_MRP_AN: + send = AVB_MRP_SEND_NEW; + break; + case AVB_MRP_LA: + send = AVB_MRP_SEND_LV; + break; + case AVB_MRP_LO: + if (a->registrar_state == AVB_MRP_IN) + send = AVB_MRP_SEND_IN; + else + send = AVB_MRP_SEND_MT; + break; + } + switch (state) { + case AVB_MRP_VP: + state = AVB_MRP_AA; + break; + case AVB_MRP_VN: + state = AVB_MRP_AN; + break; + case AVB_MRP_AN: + if(a->registrar_state == AVB_MRP_IN) + state = AVB_MRP_QA; + else + state = AVB_MRP_AA; + break; + case AVB_MRP_AA: + case AVB_MRP_AP: + state = AVB_MRP_QA; + break; + case AVB_MRP_LA: + case AVB_MRP_LO: + state = AVB_MRP_VO; + break; + } + break; + case AVB_MRP_EVENT_TX_LVA: + { + switch (state) { + case AVB_MRP_VP: + if (a->registrar_state == AVB_MRP_IN) + send = AVB_MRP_SEND_IN; + else + send = AVB_MRP_SEND_MT; + break; + case AVB_MRP_VN: + case AVB_MRP_AN: + send = AVB_MRP_SEND_NEW; + break; + case AVB_MRP_AA: + case AVB_MRP_QA: + case AVB_MRP_AP: + case AVB_MRP_QP: + if (a->registrar_state == AVB_MRP_IN) + send = AVB_MRP_SEND_JOININ; + else + send = AVB_MRP_SEND_JOINMT; + break; + } + switch (state) { + case AVB_MRP_VO: + case AVB_MRP_LA: + case AVB_MRP_AO: + case AVB_MRP_QO: + state = AVB_MRP_LO; + break; + case AVB_MRP_VP: + state = AVB_MRP_AA; + break; + case AVB_MRP_VN: + state = AVB_MRP_AN; + break; + case AVB_MRP_AN: + case AVB_MRP_AA: + case AVB_MRP_AP: + case AVB_MRP_QP: + state = AVB_MRP_QA; + break; + } + break; + } + default: + break; + } + if (a->applicant_state != state || send) { + pw_log_debug("attr %p: %d %d -> %d %d", a, event, a->applicant_state, state, send); + a->applicant_state = state; + } + if (a->joined) + a->attr.pending_send = send; +} + +void avb_mrp_attribute_rx_event(struct avb_mrp_attribute *attr, uint64_t now, uint8_t event) +{ + static const int map[] = { + [AVB_MRP_ATTRIBUTE_EVENT_NEW] = AVB_MRP_EVENT_RX_NEW, + [AVB_MRP_ATTRIBUTE_EVENT_JOININ] = AVB_MRP_EVENT_RX_JOININ, + [AVB_MRP_ATTRIBUTE_EVENT_IN] = AVB_MRP_EVENT_RX_IN, + [AVB_MRP_ATTRIBUTE_EVENT_JOINMT] = AVB_MRP_EVENT_RX_JOINMT, + [AVB_MRP_ATTRIBUTE_EVENT_MT] = AVB_MRP_EVENT_RX_MT, + [AVB_MRP_ATTRIBUTE_EVENT_LV] = AVB_MRP_EVENT_RX_LV, + }; + avb_mrp_attribute_update_state(attr, now, map[event]); +} + +void avb_mrp_attribute_begin(struct avb_mrp_attribute *attr, uint64_t now) +{ + struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr); + a->leave_timeout = 0; + avb_mrp_attribute_update_state(attr, now, AVB_MRP_EVENT_BEGIN); +} + +void avb_mrp_attribute_join(struct avb_mrp_attribute *attr, uint64_t now, bool is_new) +{ + struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr); + a->joined = true; + int event = is_new ? AVB_MRP_EVENT_NEW : AVB_MRP_EVENT_JOIN; + avb_mrp_attribute_update_state(attr, now, event); +} + +void avb_mrp_attribute_leave(struct avb_mrp_attribute *attr, uint64_t now) +{ + struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr); + avb_mrp_attribute_update_state(attr, now, AVB_MRP_EVENT_LV); + a->joined = false; +} + +void avb_mrp_destroy(struct avb_mrp *mrp) +{ + mrp_destroy(mrp); +} + +struct avb_mrp *avb_mrp_new(struct server *server) +{ + struct mrp *mrp; + + mrp = calloc(1, sizeof(*mrp)); + if (mrp == NULL) + return NULL; + + mrp->server = server; + spa_list_init(&mrp->attributes); + spa_hook_list_init(&mrp->listener_list); + + avdecc_server_add_listener(server, &mrp->server_listener, &server_events, mrp); + + return (struct avb_mrp*)mrp; +} + +void avb_mrp_add_listener(struct avb_mrp *m, struct spa_hook *listener, + const struct avb_mrp_events *events, void *data) +{ + struct mrp *mrp = (struct mrp*)m; + spa_hook_list_append(&mrp->listener_list, listener, events, data); +} diff --git a/src/modules/module-avb/mrp.h b/src/modules/module-avb/mrp.h new file mode 100644 index 0000000000000000000000000000000000000000..0a05d4b4da34878bec760e169da1581e13f90b96 --- /dev/null +++ b/src/modules/module-avb/mrp.h @@ -0,0 +1,181 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_MRP_H +#define AVB_MRP_H + +#include "packets.h" +#include "internal.h" + +#define AVB_MRP_PROTOCOL_VERSION 0 + +struct avb_packet_mrp { + struct avb_ethernet_header eth; + uint8_t version; +} __attribute__ ((__packed__)); + +struct avb_packet_mrp_hdr { + uint8_t attribute_type; + uint8_t attribute_length; +} __attribute__ ((__packed__)); + +struct avb_packet_mrp_vector { +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned lva:3; + unsigned nv1:5; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned nv1:5; + unsigned lva:3; +#endif + uint8_t nv2; + uint8_t first_value[0]; +} __attribute__ ((__packed__)); + +#define AVB_MRP_VECTOR_SET_NUM_VALUES(a,v) ((a)->nv1 = ((v) >> 8),(a)->nv2 = (v)) +#define AVB_MRP_VECTOR_GET_NUM_VALUES(a) ((a)->nv1 << 8 | (a)->nv2) + +struct avb_packet_mrp_footer { + uint16_t end_mark; +} __attribute__ ((__packed__)); + +/* applicant states */ +#define AVB_MRP_VO 0 /* Very anxious Observer */ +#define AVB_MRP_VP 1 /* Very anxious Passive */ +#define AVB_MRP_VN 2 /* Very anxious New */ +#define AVB_MRP_AN 3 /* Anxious New */ +#define AVB_MRP_AA 4 /* Anxious Active */ +#define AVB_MRP_QA 5 /* Quiet Active */ +#define AVB_MRP_LA 6 /* Leaving Active */ +#define AVB_MRP_AO 7 /* Anxious Observer */ +#define AVB_MRP_QO 8 /* Quiet Observer */ +#define AVB_MRP_AP 9 /* Anxious Passive */ +#define AVB_MRP_QP 10 /* Quiet Passive */ +#define AVB_MRP_LO 11 /* Leaving Observer */ + +/* registrar states */ +#define AVB_MRP_IN 16 +#define AVB_MRP_LV 17 +#define AVB_MRP_MT 18 + +/* events */ +#define AVB_MRP_EVENT_BEGIN 0 +#define AVB_MRP_EVENT_NEW 1 +#define AVB_MRP_EVENT_JOIN 2 +#define AVB_MRP_EVENT_LV 3 +#define AVB_MRP_EVENT_TX 4 +#define AVB_MRP_EVENT_TX_LVA 5 +#define AVB_MRP_EVENT_TX_LVAF 6 +#define AVB_MRP_EVENT_RX_NEW 7 +#define AVB_MRP_EVENT_RX_JOININ 8 +#define AVB_MRP_EVENT_RX_IN 9 +#define AVB_MRP_EVENT_RX_JOINMT 10 +#define AVB_MRP_EVENT_RX_MT 11 +#define AVB_MRP_EVENT_RX_LV 12 +#define AVB_MRP_EVENT_RX_LVA 13 +#define AVB_MRP_EVENT_FLUSH 14 +#define AVB_MRP_EVENT_REDECLARE 15 +#define AVB_MRP_EVENT_PERIODIC 16 +#define AVB_MRP_EVENT_LV_TIMER 17 +#define AVB_MRP_EVENT_LVA_TIMER 18 + +/* attribute events */ +#define AVB_MRP_ATTRIBUTE_EVENT_NEW 0 +#define AVB_MRP_ATTRIBUTE_EVENT_JOININ 1 +#define AVB_MRP_ATTRIBUTE_EVENT_IN 2 +#define AVB_MRP_ATTRIBUTE_EVENT_JOINMT 3 +#define AVB_MRP_ATTRIBUTE_EVENT_MT 4 +#define AVB_MRP_ATTRIBUTE_EVENT_LV 5 + +#define AVB_MRP_SEND_NEW 1 +#define AVB_MRP_SEND_JOININ 2 +#define AVB_MRP_SEND_IN 3 +#define AVB_MRP_SEND_JOINMT 4 +#define AVB_MRP_SEND_MT 5 +#define AVB_MRP_SEND_LV 6 + +#define AVB_MRP_NOTIFY_NEW 1 +#define AVB_MRP_NOTIFY_JOIN 2 +#define AVB_MRP_NOTIFY_LEAVE 3 + +const char *avb_mrp_notify_name(uint8_t notify); +const char *avb_mrp_send_name(uint8_t send); + +struct avb_mrp_attribute { + uint8_t pending_send; + void *user_data; +}; + +struct avb_mrp_attribute_events { +#define AVB_VERSION_MRP_ATTRIBUTE_EVENTS 0 + uint32_t version; + + void (*notify) (void *data, uint64_t now, uint8_t notify); +}; + +struct avb_mrp_attribute *avb_mrp_attribute_new(struct avb_mrp *mrp, + size_t user_size); +void avb_mrp_attribute_destroy(struct avb_mrp_attribute *attr); + +void avb_mrp_attribute_update_state(struct avb_mrp_attribute *attr, uint64_t now, int event); + +void avb_mrp_attribute_rx_event(struct avb_mrp_attribute *attr, uint64_t now, uint8_t event); + +void avb_mrp_attribute_begin(struct avb_mrp_attribute *attr, uint64_t now); +void avb_mrp_attribute_join(struct avb_mrp_attribute *attr, uint64_t now, bool is_new); +void avb_mrp_attribute_leave(struct avb_mrp_attribute *attr, uint64_t now); + +void avb_mrp_attribute_add_listener(struct avb_mrp_attribute *attr, struct spa_hook *listener, + const struct avb_mrp_attribute_events *events, void *data); + +struct avb_mrp_parse_info { +#define AVB_VERSION_MRP_PARSE_INFO 0 + uint32_t version; + + bool (*check_header) (void *data, const void *hdr, size_t *hdr_size, bool *has_params); + + int (*attr_event) (void *data, uint64_t now, uint8_t attribute_type, uint8_t event); + + int (*process) (void *data, uint64_t now, uint8_t attribute_type, const void *value, + uint8_t event, uint8_t param, int index); +}; + +int avb_mrp_parse_packet(struct avb_mrp *mrp, uint64_t now, const void *pkt, int size, + const struct avb_mrp_parse_info *cb, void *data); + +struct avb_mrp_events { +#define AVB_VERSION_MRP_EVENTS 0 + uint32_t version; + + void (*event) (void *data, uint64_t now, uint8_t event); + + void (*notify) (void *data, uint64_t now, struct avb_mrp_attribute *attr, uint8_t notify); +}; + +struct avb_mrp *avb_mrp_new(struct server *server); +void avb_mrp_destroy(struct avb_mrp *mrp); + +void avb_mrp_add_listener(struct avb_mrp *mrp, struct spa_hook *listener, + const struct avb_mrp_events *events, void *data); + +#endif /* AVB_MRP_H */ diff --git a/src/modules/module-avb/msrp.c b/src/modules/module-avb/msrp.c new file mode 100644 index 0000000000000000000000000000000000000000..85d3ff9be7aa9e8d3826166c0ada503d277063af --- /dev/null +++ b/src/modules/module-avb/msrp.c @@ -0,0 +1,459 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <unistd.h> + +#include <spa/debug/mem.h> + +#include <pipewire/pipewire.h> + +#include "utils.h" +#include "msrp.h" + +static const uint8_t msrp_mac[6] = AVB_MSRP_MAC; + +struct attr { + struct avb_msrp_attribute attr; + struct msrp *msrp; + struct spa_hook listener; + struct spa_list link; +}; + +struct msrp { + struct server *server; + struct spa_hook server_listener; + struct spa_hook mrp_listener; + + struct spa_source *source; + + struct spa_list attributes; +}; + +static void debug_msrp_talker_common(const struct avb_packet_msrp_talker *t) +{ + char buf[128]; + pw_log_info(" stream-id: %s", avb_utils_format_id(buf, sizeof(buf), be64toh(t->stream_id))); + pw_log_info(" dest-addr: %s", avb_utils_format_addr(buf, sizeof(buf), t->dest_addr)); + pw_log_info(" vlan-id: %d", ntohs(t->vlan_id)); + pw_log_info(" tspec-max-frame-size: %d", ntohs(t->tspec_max_frame_size)); + pw_log_info(" tspec-max-interval-frames: %d", ntohs(t->tspec_max_interval_frames)); + pw_log_info(" priority: %d", t->priority); + pw_log_info(" rank: %d", t->rank); + pw_log_info(" accumulated-latency: %d", ntohl(t->accumulated_latency)); +} + +static void debug_msrp_talker(const struct avb_packet_msrp_talker *t) +{ + pw_log_info("talker"); + debug_msrp_talker_common(t); +} + +static void notify_talker(struct msrp *msrp, uint64_t now, struct attr *attr, uint8_t notify) +{ + pw_log_info("> notify talker: %s", avb_mrp_notify_name(notify)); + debug_msrp_talker(&attr->attr.attr.talker); +} + +static int process_talker(struct msrp *msrp, uint64_t now, uint8_t attr_type, + const void *m, uint8_t event, uint8_t param, int num) +{ + const struct avb_packet_msrp_talker *t = m; + struct attr *a; + spa_list_for_each(a, &msrp->attributes, link) + if (a->attr.type == attr_type && + a->attr.attr.talker.stream_id == t->stream_id) { + a->attr.attr.talker = *t; + avb_mrp_attribute_rx_event(a->attr.mrp, now, event); + } + return 0; +} +static int encode_talker(struct msrp *msrp, struct attr *a, void *m) +{ + struct avb_packet_msrp_msg *msg = m; + struct avb_packet_mrp_vector *v; + struct avb_packet_msrp_talker *t; + struct avb_packet_mrp_footer *f; + uint8_t *ev; + size_t attr_list_length = sizeof(*v) + sizeof(*t) + sizeof(*f) + 1; + + msg->attribute_type = AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE; + msg->attribute_length = sizeof(*t); + msg->attribute_list_length = htons(attr_list_length); + + v = (struct avb_packet_mrp_vector *)msg->attribute_list; + v->lva = 0; + AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1); + + t = (struct avb_packet_msrp_talker *)v->first_value; + *t = a->attr.attr.talker; + + ev = SPA_PTROFF(t, sizeof(*t), uint8_t); + *ev = a->attr.mrp->pending_send * 6 * 6; + + f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer); + f->end_mark = 0; + + return attr_list_length + sizeof(*msg); +} + + +static void debug_msrp_talker_fail(const struct avb_packet_msrp_talker_fail *t) +{ + char buf[128]; + pw_log_info("talker fail"); + debug_msrp_talker_common(&t->talker); + pw_log_info(" bridge-id: %s", avb_utils_format_id(buf, sizeof(buf), be64toh(t->bridge_id))); + pw_log_info(" failure-code: %d", t->failure_code); +} + +static int process_talker_fail(struct msrp *msrp, uint64_t now, uint8_t attr_type, + const void *m, uint8_t event, uint8_t param, int num) +{ + const struct avb_packet_msrp_talker_fail *t = m; + struct attr *a; + + debug_msrp_talker_fail(t); + + spa_list_for_each(a, &msrp->attributes, link) + if (a->attr.type == attr_type && + a->attr.attr.talker_fail.talker.stream_id == t->talker.stream_id) + avb_mrp_attribute_rx_event(a->attr.mrp, now, event); + return 0; +} + +static void debug_msrp_listener(const struct avb_packet_msrp_listener *l, uint8_t param) +{ + char buf[128]; + pw_log_info("listener"); + pw_log_info(" %s", avb_utils_format_id(buf, sizeof(buf), be64toh(l->stream_id))); + pw_log_info(" %d", param); +} + +static void notify_listener(struct msrp *msrp, uint64_t now, struct attr *attr, uint8_t notify) +{ + pw_log_info("> notify listener: %s", avb_mrp_notify_name(notify)); + debug_msrp_listener(&attr->attr.attr.listener, attr->attr.param); +} + +static int process_listener(struct msrp *msrp, uint64_t now, uint8_t attr_type, + const void *m, uint8_t event, uint8_t param, int num) +{ + const struct avb_packet_msrp_listener *l = m; + struct attr *a; + spa_list_for_each(a, &msrp->attributes, link) + if (a->attr.type == attr_type && + a->attr.attr.listener.stream_id == l->stream_id) + avb_mrp_attribute_rx_event(a->attr.mrp, now, event); + return 0; +} +static int encode_listener(struct msrp *msrp, struct attr *a, void *m) +{ + struct avb_packet_msrp_msg *msg = m; + struct avb_packet_mrp_vector *v; + struct avb_packet_msrp_listener *l; + struct avb_packet_mrp_footer *f; + uint8_t *ev; + size_t attr_list_length = sizeof(*v) + sizeof(*l) + sizeof(*f) + 1 + 1; + + msg->attribute_type = AVB_MSRP_ATTRIBUTE_TYPE_LISTENER; + msg->attribute_length = sizeof(*l); + msg->attribute_list_length = htons(attr_list_length); + + v = (struct avb_packet_mrp_vector *)msg->attribute_list; + v->lva = 0; + AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1); + + l = (struct avb_packet_msrp_listener *)v->first_value; + *l = a->attr.attr.listener; + + ev = SPA_PTROFF(l, sizeof(*l), uint8_t); + *ev = a->attr.mrp->pending_send * 6 * 6; + + ev = SPA_PTROFF(ev, sizeof(*ev), uint8_t); + *ev = a->attr.param * 4 * 4 * 4; + + f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer); + f->end_mark = 0; + + return attr_list_length + sizeof(*msg); +} + +static void debug_msrp_domain(const struct avb_packet_msrp_domain *d) +{ + pw_log_info("domain"); + pw_log_info(" id: %d", d->sr_class_id); + pw_log_info(" prio: %d", d->sr_class_priority); + pw_log_info(" vid: %d", ntohs(d->sr_class_vid)); +} + +static void notify_domain(struct msrp *msrp, uint64_t now, struct attr *attr, uint8_t notify) +{ + pw_log_info("> notify domain: %s", avb_mrp_notify_name(notify)); + debug_msrp_domain(&attr->attr.attr.domain); +} + +static int process_domain(struct msrp *msrp, uint64_t now, uint8_t attr_type, + const void *m, uint8_t event, uint8_t param, int num) +{ + struct attr *a; + spa_list_for_each(a, &msrp->attributes, link) + if (a->attr.type == attr_type) + avb_mrp_attribute_rx_event(a->attr.mrp, now, event); + return 0; +} + +static int encode_domain(struct msrp *msrp, struct attr *a, void *m) +{ + struct avb_packet_msrp_msg *msg = m; + struct avb_packet_mrp_vector *v; + struct avb_packet_msrp_domain *d; + struct avb_packet_mrp_footer *f; + uint8_t *ev; + size_t attr_list_length = sizeof(*v) + sizeof(*d) + sizeof(*f) + 1; + + msg->attribute_type = AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN; + msg->attribute_length = sizeof(*d); + msg->attribute_list_length = htons(attr_list_length); + + v = (struct avb_packet_mrp_vector *)msg->attribute_list; + v->lva = 0; + AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1); + + d = (struct avb_packet_msrp_domain *)v->first_value; + *d = a->attr.attr.domain; + + ev = SPA_PTROFF(d, sizeof(*d), uint8_t); + *ev = a->attr.mrp->pending_send * 36; + + f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer); + f->end_mark = 0; + + return attr_list_length + sizeof(*msg); +} + +static const struct { + const char *name; + int (*process) (struct msrp *msrp, uint64_t now, uint8_t attr_type, + const void *m, uint8_t event, uint8_t param, int num); + int (*encode) (struct msrp *msrp, struct attr *attr, void *m); + void (*notify) (struct msrp *msrp, uint64_t now, struct attr *attr, uint8_t notify); +} dispatch[] = { + [AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE] = { "talker", process_talker, encode_talker, notify_talker, }, + [AVB_MSRP_ATTRIBUTE_TYPE_TALKER_FAILED] = { "talker-fail", process_talker_fail, NULL, NULL }, + [AVB_MSRP_ATTRIBUTE_TYPE_LISTENER] = { "listener", process_listener, encode_listener, notify_listener }, + [AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN] = { "domain", process_domain, encode_domain, notify_domain, }, +}; + +static bool msrp_check_header(void *data, const void *hdr, size_t *hdr_size, bool *has_params) +{ + const struct avb_packet_msrp_msg *msg = hdr; + uint8_t attr_type = msg->attribute_type; + + if (!AVB_MSRP_ATTRIBUTE_TYPE_VALID(attr_type)) + return false; + + *hdr_size = sizeof(*msg); + *has_params = attr_type == AVB_MSRP_ATTRIBUTE_TYPE_LISTENER; + return true; +} + +static int msrp_attr_event(void *data, uint64_t now, uint8_t attribute_type, uint8_t event) +{ + struct msrp *msrp = data; + struct attr *a; + spa_list_for_each(a, &msrp->attributes, link) + if (a->attr.type == attribute_type) + avb_mrp_attribute_update_state(a->attr.mrp, now, event); + return 0; +} + +static int msrp_process(void *data, uint64_t now, uint8_t attribute_type, const void *value, + uint8_t event, uint8_t param, int index) +{ + struct msrp *msrp = data; + return dispatch[attribute_type].process(msrp, now, + attribute_type, value, event, param, index); +} + +static const struct avb_mrp_parse_info info = { + AVB_VERSION_MRP_PARSE_INFO, + .check_header = msrp_check_header, + .attr_event = msrp_attr_event, + .process = msrp_process, +}; + + +static int msrp_message(struct msrp *msrp, uint64_t now, const void *message, int len) +{ + return avb_mrp_parse_packet(msrp->server->mrp, + now, message, len, &info, msrp); +} +static void on_socket_data(void *data, int fd, uint32_t mask) +{ + struct msrp *msrp = data; + struct timespec now; + + if (mask & SPA_IO_IN) { + int len; + uint8_t buffer[2048]; + + len = recv(fd, buffer, sizeof(buffer), 0); + + if (len < 0) { + pw_log_warn("got recv error: %m"); + } + else if (len < (int)sizeof(struct avb_packet_header)) { + pw_log_warn("short packet received (%d < %d)", len, + (int)sizeof(struct avb_packet_header)); + } else { + clock_gettime(CLOCK_REALTIME, &now); + msrp_message(msrp, SPA_TIMESPEC_TO_NSEC(&now), buffer, len); + } + } +} + +static void msrp_destroy(void *data) +{ + struct msrp *msrp = data; + spa_hook_remove(&msrp->server_listener); + pw_loop_destroy_source(msrp->server->impl->loop, msrp->source); + free(msrp); +} + +static const struct server_events server_events = { + AVB_VERSION_SERVER_EVENTS, + .destroy = msrp_destroy, +}; + +static void msrp_notify(void *data, uint64_t now, uint8_t notify) +{ + struct attr *a = data; + struct msrp *msrp = a->msrp; + return dispatch[a->attr.type].notify(msrp, now, a, notify); +} + +static const struct avb_mrp_attribute_events mrp_attr_events = { + AVB_VERSION_MRP_ATTRIBUTE_EVENTS, + .notify = msrp_notify, +}; + +struct avb_msrp_attribute *avb_msrp_attribute_new(struct avb_msrp *m, + uint8_t type) +{ + struct msrp *msrp = (struct msrp*)m; + struct avb_mrp_attribute *attr; + struct attr *a; + + attr = avb_mrp_attribute_new(msrp->server->mrp, sizeof(struct attr)); + + a = attr->user_data; + a->msrp = msrp; + a->attr.mrp = attr; + a->attr.type = type; + spa_list_append(&msrp->attributes, &a->link); + avb_mrp_attribute_add_listener(attr, &a->listener, &mrp_attr_events, a); + + return &a->attr; +} + +static void msrp_event(void *data, uint64_t now, uint8_t event) +{ + struct msrp *msrp = data; + uint8_t buffer[2048]; + struct avb_packet_mrp *p = (struct avb_packet_mrp*)buffer; + struct avb_packet_mrp_footer *f; + void *msg = SPA_PTROFF(buffer, sizeof(*p), void); + struct attr *a; + int len, count = 0; + size_t total = sizeof(*p) + 2; + + p->version = AVB_MRP_PROTOCOL_VERSION; + + spa_list_for_each(a, &msrp->attributes, link) { + if (!a->attr.mrp->pending_send) + continue; + if (dispatch[a->attr.type].encode == NULL) + continue; + + pw_log_debug("send %s %s", dispatch[a->attr.type].name, + avb_mrp_send_name(a->attr.mrp->pending_send)); + + len = dispatch[a->attr.type].encode(msrp, a, msg); + if (len < 0) + break; + + count++; + msg = SPA_PTROFF(msg, len, void); + total += len; + } + f = (struct avb_packet_mrp_footer *)msg; + f->end_mark = 0; + + if (count > 0) + avb_server_send_packet(msrp->server, msrp_mac, AVB_MSRP_ETH, + buffer, total); +} + +static const struct avb_mrp_events mrp_events = { + AVB_VERSION_MRP_EVENTS, + .event = msrp_event, +}; + +struct avb_msrp *avb_msrp_register(struct server *server) +{ + struct msrp *msrp; + int fd, res; + + fd = avb_server_make_socket(server, AVB_MSRP_ETH, msrp_mac); + if (fd < 0) { + errno = -fd; + return NULL; + } + msrp = calloc(1, sizeof(*msrp)); + if (msrp == NULL) { + res = -errno; + goto error_close; + } + + msrp->server = server; + spa_list_init(&msrp->attributes); + + msrp->source = pw_loop_add_io(server->impl->loop, fd, SPA_IO_IN, true, on_socket_data, msrp); + if (msrp->source == NULL) { + res = -errno; + pw_log_error("msrp %p: can't create msrp source: %m", msrp); + goto error_no_source; + } + avdecc_server_add_listener(server, &msrp->server_listener, &server_events, msrp); + avb_mrp_add_listener(server->mrp, &msrp->mrp_listener, &mrp_events, msrp); + + return (struct avb_msrp*)msrp; + +error_no_source: + free(msrp); +error_close: + close(fd); + errno = -res; + return NULL; +} diff --git a/src/modules/module-avb/msrp.h b/src/modules/module-avb/msrp.h new file mode 100644 index 0000000000000000000000000000000000000000..0922e6bf9499168e48cbff6568a1a5a4f2a2b78a --- /dev/null +++ b/src/modules/module-avb/msrp.h @@ -0,0 +1,134 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_MSRP_H +#define AVB_MSRP_H + +#include "internal.h" +#include "mrp.h" + +#define AVB_MSRP_ETH 0x22ea +#define AVB_MSRP_MAC { 0x01, 0x80, 0xc2, 0x00, 0x00, 0xe }; + +#define AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE 1 +#define AVB_MSRP_ATTRIBUTE_TYPE_TALKER_FAILED 2 +#define AVB_MSRP_ATTRIBUTE_TYPE_LISTENER 3 +#define AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN 4 +#define AVB_MSRP_ATTRIBUTE_TYPE_VALID(t) ((t)>=1 && (t)<=4) + +struct avb_packet_msrp_msg { + uint8_t attribute_type; + uint8_t attribute_length; + uint16_t attribute_list_length; + uint8_t attribute_list[0]; +} __attribute__ ((__packed__)); + +#define AVB_MSRP_TSPEC_MAX_INTERVAL_FRAMES_DEFAULT 1 +#define AVB_MSRP_RANK_DEFAULT 1 +#define AVB_MSRP_PRIORITY_DEFAULT 3 + +struct avb_packet_msrp_talker { + uint64_t stream_id; + uint8_t dest_addr[6]; + uint16_t vlan_id; + uint16_t tspec_max_frame_size; + uint16_t tspec_max_interval_frames; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned priority:3; + unsigned rank:1; + unsigned reserved:4; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned reserved:4; + unsigned rank:1; + unsigned priority:3; +#endif + uint32_t accumulated_latency; +} __attribute__ ((__packed__)); + +/* failure codes */ +#define AVB_MRP_FAIL_BANDWIDTH 1 +#define AVB_MRP_FAIL_BRIDGE 2 +#define AVB_MRP_FAIL_TC_BANDWIDTH 3 +#define AVB_MRP_FAIL_ID_BUSY 4 +#define AVB_MRP_FAIL_DSTADDR_BUSY 5 +#define AVB_MRP_FAIL_PREEMPTED 6 +#define AVB_MRP_FAIL_LATENCY_CHNG 7 +#define AVB_MRP_FAIL_PORT_NOT_AVB 8 +#define AVB_MRP_FAIL_DSTADDR_FULL 9 +#define AVB_MRP_FAIL_AVB_MRP_RESOURCE 10 +#define AVB_MRP_FAIL_MMRP_RESOURCE 11 +#define AVB_MRP_FAIL_DSTADDR_FAIL 12 +#define AVB_MRP_FAIL_PRIO_NOT_SR 13 +#define AVB_MRP_FAIL_FRAME_SIZE 14 +#define AVB_MRP_FAIL_FANIN_EXCEED 15 +#define AVB_MRP_FAIL_STREAM_CHANGE 16 +#define AVB_MRP_FAIL_VLAN_BLOCKED 17 +#define AVB_MRP_FAIL_VLAN_DISABLED 18 +#define AVB_MRP_FAIL_SR_PRIO_ERR 19 + +struct avb_packet_msrp_talker_fail { + struct avb_packet_msrp_talker talker; + uint64_t bridge_id; + uint8_t failure_code; +} __attribute__ ((__packed__)); + +struct avb_packet_msrp_listener { + uint64_t stream_id; +} __attribute__ ((__packed__)); + +/* domain discovery */ +#define AVB_MSRP_CLASS_ID_DEFAULT 6 +#define AVB_DEFAULT_VLAN 2 + +struct avb_packet_msrp_domain { + uint8_t sr_class_id; + uint8_t sr_class_priority; + uint16_t sr_class_vid; +} __attribute__ ((__packed__)); + +#define AVB_MSRP_LISTENER_PARAM_IGNORE 0 +#define AVB_MSRP_LISTENER_PARAM_ASKING_FAILED 1 +#define AVB_MSRP_LISTENER_PARAM_READY 2 +#define AVB_MSRP_LISTENER_PARAM_READY_FAILED 3 + +struct avb_msrp_attribute { + struct avb_mrp_attribute *mrp; + uint8_t type; + uint8_t param; + union { + struct avb_packet_msrp_talker talker; + struct avb_packet_msrp_talker_fail talker_fail; + struct avb_packet_msrp_listener listener; + struct avb_packet_msrp_domain domain; + } attr; +}; + +struct avb_msrp; + +struct avb_msrp_attribute *avb_msrp_attribute_new(struct avb_msrp *msrp, + uint8_t type); + +struct avb_msrp *avb_msrp_register(struct server *server); + +#endif /* AVB_MSRP_H */ diff --git a/src/modules/module-avb/mvrp.c b/src/modules/module-avb/mvrp.c new file mode 100644 index 0000000000000000000000000000000000000000..2f5f6eaa7a216ee28dea62daad7951397b52db4c --- /dev/null +++ b/src/modules/module-avb/mvrp.c @@ -0,0 +1,297 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <unistd.h> + +#include <pipewire/pipewire.h> + +#include "mvrp.h" + +static const uint8_t mvrp_mac[6] = AVB_MVRP_MAC; + +struct attr { + struct avb_mvrp_attribute attr; + struct spa_hook listener; + struct spa_list link; + struct mvrp *mvrp; +}; + +struct mvrp { + struct server *server; + struct spa_hook server_listener; + struct spa_hook mrp_listener; + + struct spa_source *source; + + struct spa_list attributes; +}; + +static bool mvrp_check_header(void *data, const void *hdr, size_t *hdr_size, bool *has_params) +{ + const struct avb_packet_mvrp_msg *msg = hdr; + uint8_t attr_type = msg->attribute_type; + + if (!AVB_MVRP_ATTRIBUTE_TYPE_VALID(attr_type)) + return false; + + *hdr_size = sizeof(*msg); + *has_params = false; + return true; +} + +static int mvrp_attr_event(void *data, uint64_t now, uint8_t attribute_type, uint8_t event) +{ + struct mvrp *mvrp = data; + struct attr *a; + spa_list_for_each(a, &mvrp->attributes, link) + if (a->attr.type == attribute_type) + avb_mrp_attribute_rx_event(a->attr.mrp, now, event); + return 0; +} + +static void debug_vid(const struct avb_packet_mvrp_vid *t) +{ + pw_log_info("vid"); + pw_log_info(" %d", ntohs(t->vlan)); +} + +static int process_vid(struct mvrp *mvrp, uint64_t now, uint8_t attr_type, + const void *m, uint8_t event, uint8_t param, int num) +{ + return mvrp_attr_event(mvrp, now, attr_type, event); +} + +static int encode_vid(struct mvrp *mvrp, struct attr *a, void *m) +{ + struct avb_packet_mvrp_msg *msg = m; + struct avb_packet_mrp_vector *v; + struct avb_packet_mvrp_vid *d; + struct avb_packet_mrp_footer *f; + uint8_t *ev; + size_t attr_list_length = sizeof(*v) + sizeof(*d) + sizeof(*f) + 1; + + msg->attribute_type = AVB_MVRP_ATTRIBUTE_TYPE_VID; + msg->attribute_length = sizeof(*d); + + v = (struct avb_packet_mrp_vector *)msg->attribute_list; + v->lva = 0; + AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1); + + d = (struct avb_packet_mvrp_vid *)v->first_value; + *d = a->attr.attr.vid; + + ev = SPA_PTROFF(d, sizeof(*d), uint8_t); + *ev = a->attr.mrp->pending_send * 36; + + f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer); + f->end_mark = 0; + + return attr_list_length + sizeof(*msg); +} + +static void notify_vid(struct mvrp *mvrp, uint64_t now, struct attr *attr, uint8_t notify) +{ + pw_log_info("> notify vid: %s", avb_mrp_notify_name(notify)); + debug_vid(&attr->attr.attr.vid); +} + +static const struct { + const char *name; + int (*process) (struct mvrp *mvrp, uint64_t now, uint8_t attr_type, + const void *m, uint8_t event, uint8_t param, int num); + int (*encode) (struct mvrp *mvrp, struct attr *attr, void *m); + void (*notify) (struct mvrp *mvrp, uint64_t now, struct attr *attr, uint8_t notify); +} dispatch[] = { + [AVB_MVRP_ATTRIBUTE_TYPE_VID] = { "vid", process_vid, encode_vid, notify_vid }, +}; + +static int mvrp_process(void *data, uint64_t now, uint8_t attribute_type, const void *value, + uint8_t event, uint8_t param, int index) +{ + struct mvrp *mvrp = data; + return dispatch[attribute_type].process(mvrp, now, + attribute_type, value, event, param, index); +} + +static const struct avb_mrp_parse_info info = { + AVB_VERSION_MRP_PARSE_INFO, + .check_header = mvrp_check_header, + .attr_event = mvrp_attr_event, + .process = mvrp_process, +}; + +static int mvrp_message(struct mvrp *mvrp, uint64_t now, const void *message, int len) +{ + pw_log_debug("MVRP"); + return avb_mrp_parse_packet(mvrp->server->mrp, + now, message, len, &info, mvrp); +} + +static void on_socket_data(void *data, int fd, uint32_t mask) +{ + struct mvrp *mvrp = data; + struct timespec now; + + if (mask & SPA_IO_IN) { + int len; + uint8_t buffer[2048]; + + len = recv(fd, buffer, sizeof(buffer), 0); + + if (len < 0) { + pw_log_warn("got recv error: %m"); + } + else if (len < (int)sizeof(struct avb_packet_header)) { + pw_log_warn("short packet received (%d < %d)", len, + (int)sizeof(struct avb_packet_header)); + } else { + clock_gettime(CLOCK_REALTIME, &now); + mvrp_message(mvrp, SPA_TIMESPEC_TO_NSEC(&now), buffer, len); + } + } +} + +static void mvrp_destroy(void *data) +{ + struct mvrp *mvrp = data; + spa_hook_remove(&mvrp->server_listener); + pw_loop_destroy_source(mvrp->server->impl->loop, mvrp->source); + free(mvrp); +} + +static const struct server_events server_events = { + AVB_VERSION_SERVER_EVENTS, + .destroy = mvrp_destroy, +}; + +static void mvrp_notify(void *data, uint64_t now, uint8_t notify) +{ + struct attr *a = data; + struct mvrp *mvrp = a->mvrp; + return dispatch[a->attr.type].notify(mvrp, now, a, notify); +} + +static const struct avb_mrp_attribute_events mrp_attr_events = { + AVB_VERSION_MRP_ATTRIBUTE_EVENTS, + .notify = mvrp_notify, +}; + +struct avb_mvrp_attribute *avb_mvrp_attribute_new(struct avb_mvrp *m, + uint8_t type) +{ + struct mvrp *mvrp = (struct mvrp*)m; + struct avb_mrp_attribute *attr; + struct attr *a; + + attr = avb_mrp_attribute_new(mvrp->server->mrp, sizeof(struct attr)); + + a = attr->user_data; + a->attr.mrp = attr; + a->attr.type = type; + spa_list_append(&mvrp->attributes, &a->link); + avb_mrp_attribute_add_listener(attr, &a->listener, &mrp_attr_events, a); + + return &a->attr; +} + +static void mvrp_event(void *data, uint64_t now, uint8_t event) +{ + struct mvrp *mvrp = data; + uint8_t buffer[2048]; + struct avb_packet_mrp *p = (struct avb_packet_mrp*)buffer; + struct avb_packet_mrp_footer *f; + void *msg = SPA_PTROFF(buffer, sizeof(*p), void); + struct attr *a; + int len, count = 0; + size_t total = sizeof(*p) + 2; + + p->version = AVB_MRP_PROTOCOL_VERSION; + + spa_list_for_each(a, &mvrp->attributes, link) { + if (!a->attr.mrp->pending_send) + continue; + if (dispatch[a->attr.type].encode == NULL) + continue; + + pw_log_debug("send %s %s", dispatch[a->attr.type].name, + avb_mrp_send_name(a->attr.mrp->pending_send)); + + len = dispatch[a->attr.type].encode(mvrp, a, msg); + if (len < 0) + break; + + count++; + msg = SPA_PTROFF(msg, len, void); + total += len; + } + f = (struct avb_packet_mrp_footer *)msg; + f->end_mark = 0; + + if (count > 0) + avb_server_send_packet(mvrp->server, mvrp_mac, AVB_MVRP_ETH, + buffer, total); +} + +static const struct avb_mrp_events mrp_events = { + AVB_VERSION_MRP_EVENTS, + .event = mvrp_event, +}; + +struct avb_mvrp *avb_mvrp_register(struct server *server) +{ + struct mvrp *mvrp; + int fd, res; + + fd = avb_server_make_socket(server, AVB_MVRP_ETH, mvrp_mac); + if (fd < 0) { + errno = -fd; + return NULL; + } + mvrp = calloc(1, sizeof(*mvrp)); + if (mvrp == NULL) { + res = -errno; + goto error_close; + } + + mvrp->server = server; + spa_list_init(&mvrp->attributes); + + mvrp->source = pw_loop_add_io(server->impl->loop, fd, SPA_IO_IN, true, on_socket_data, mvrp); + if (mvrp->source == NULL) { + res = -errno; + pw_log_error("mvrp %p: can't create mvrp source: %m", mvrp); + goto error_no_source; + } + avdecc_server_add_listener(server, &mvrp->server_listener, &server_events, mvrp); + avb_mrp_add_listener(server->mrp, &mvrp->mrp_listener, &mrp_events, mvrp); + + return (struct avb_mvrp*)mvrp; + +error_no_source: + free(mvrp); +error_close: + close(fd); + errno = -res; + return NULL; +} diff --git a/src/modules/module-avb/mvrp.h b/src/modules/module-avb/mvrp.h new file mode 100644 index 0000000000000000000000000000000000000000..da3d5dcf82fa025c037df0544030d7b729a29614 --- /dev/null +++ b/src/modules/module-avb/mvrp.h @@ -0,0 +1,62 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_MVRP_H +#define AVB_MVRP_H + +#include "mrp.h" +#include "internal.h" + +#define AVB_MVRP_ETH 0x88f5 +#define AVB_MVRP_MAC { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x21 }; + +struct avb_packet_mvrp_msg { + uint8_t attribute_type; + uint8_t attribute_length; + uint8_t attribute_list[0]; +} __attribute__ ((__packed__)); + +#define AVB_MVRP_ATTRIBUTE_TYPE_VID 1 +#define AVB_MVRP_ATTRIBUTE_TYPE_VALID(t) ((t)==1) + +struct avb_packet_mvrp_vid { + uint16_t vlan; +} __attribute__ ((__packed__)); + +struct avb_mvrp; + +struct avb_mvrp_attribute { + struct avb_mrp_attribute *mrp; + uint8_t type; + union { + struct avb_packet_mvrp_vid vid; + } attr; +}; + +struct avb_mvrp_attribute *avb_mvrp_attribute_new(struct avb_mvrp *mvrp, + uint8_t type); + +struct avb_mvrp *avb_mvrp_register(struct server *server); + +#endif /* AVB_MVRP_H */ diff --git a/src/modules/module-avb/packets.h b/src/modules/module-avb/packets.h new file mode 100644 index 0000000000000000000000000000000000000000..f35738a433c14818922a3d75db2cad0c10542762 --- /dev/null +++ b/src/modules/module-avb/packets.h @@ -0,0 +1,101 @@ +/* Spa AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_PACKETS_H +#define AVB_PACKETS_H + +#include <arpa/inet.h> + +#define AVB_SUBTYPE_61883_IIDC 0x00 +#define AVB_SUBTYPE_MMA_STREAM 0x01 +#define AVB_SUBTYPE_AAF 0x02 +#define AVB_SUBTYPE_CVF 0x03 +#define AVB_SUBTYPE_CRF 0x04 +#define AVB_SUBTYPE_TSCF 0x05 +#define AVB_SUBTYPE_SVF 0x06 +#define AVB_SUBTYPE_RVF 0x07 +#define AVB_SUBTYPE_AEF_CONTINUOUS 0x6E +#define AVB_SUBTYPE_VSF_STREAM 0x6F +#define AVB_SUBTYPE_EF_STREAM 0x7F +#define AVB_SUBTYPE_NTSCF 0x82 +#define AVB_SUBTYPE_ESCF 0xEC +#define AVB_SUBTYPE_EECF 0xED +#define AVB_SUBTYPE_AEF_DISCRETE 0xEE +#define AVB_SUBTYPE_ADP 0xFA +#define AVB_SUBTYPE_AECP 0xFB +#define AVB_SUBTYPE_ACMP 0xFC +#define AVB_SUBTYPE_MAAP 0xFE +#define AVB_SUBTYPE_EF_CONTROL 0xFF + +struct avb_ethernet_header { + uint8_t dest[6]; + uint8_t src[6]; + uint16_t type; +} __attribute__ ((__packed__)); + +struct avb_frame_header { + uint8_t dest[6]; + uint8_t src[6]; + uint16_t type; /* 802.1Q Virtual Lan 0x8100 */ + uint16_t prio_cfi_id; + uint16_t etype; +} __attribute__ ((__packed__)); + +struct avb_packet_header { + uint8_t subtype; +#if __BYTE_ORDER == __BIG_ENDIAN + unsigned sv:1; /* stream_id valid */ + unsigned version:3; + unsigned subtype_data1:4; + + unsigned subtype_data2:5; + unsigned len1:3; +#elif __BYTE_ORDER == __LITTLE_ENDIAN + unsigned subtype_data1:4; + unsigned version:3; + unsigned sv:1; + + unsigned len1:3; + unsigned subtype_data2:5; +#elif +#error "Unknown byte order" +#endif + uint8_t len2:8; +} __attribute__ ((__packed__)); + +#define AVB_PACKET_SET_SUBTYPE(p,v) ((p)->subtype = (v)) +#define AVB_PACKET_SET_SV(p,v) ((p)->sv = (v)) +#define AVB_PACKET_SET_VERSION(p,v) ((p)->version = (v)) +#define AVB_PACKET_SET_SUB1(p,v) ((p)->subtype_data1 = (v)) +#define AVB_PACKET_SET_SUB2(p,v) ((p)->subtype_data2 = (v)) +#define AVB_PACKET_SET_LENGTH(p,v) ((p)->len1 = ((v) >> 8),(p)->len2 = (v)) + +#define AVB_PACKET_GET_SUBTYPE(p) ((p)->subtype) +#define AVB_PACKET_GET_SV(p) ((p)->sv) +#define AVB_PACKET_GET_VERSION(p) ((p)->version) +#define AVB_PACKET_GET_SUB1(p) ((p)->subtype_data1) +#define AVB_PACKET_GET_SUB2(p) ((p)->subtype_data2) +#define AVB_PACKET_GET_LENGTH(p) ((p)->len1 << 8 | (p)->len2) + +#endif /* AVB_PACKETS_H */ diff --git a/src/modules/module-avb/srp.c b/src/modules/module-avb/srp.c new file mode 100644 index 0000000000000000000000000000000000000000..89d75f12a98f202cbb0d772ded9a3954888c9ffd --- /dev/null +++ b/src/modules/module-avb/srp.c @@ -0,0 +1,59 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <pipewire/pipewire.h> + +#include "srp.h" + +struct srp { + struct server *server; + struct spa_hook server_listener; +}; + +static void srp_destroy(void *data) +{ + struct srp *srp = data; + spa_hook_remove(&srp->server_listener); + free(srp); +} + +static const struct server_events server_events = { + AVB_VERSION_SERVER_EVENTS, + .destroy = srp_destroy, +}; + +int avb_srp_register(struct server *server) +{ + struct srp *srp; + + srp = calloc(1, sizeof(*srp)); + if (srp == NULL) + return -errno; + + srp->server = server; + + avdecc_server_add_listener(server, &srp->server_listener, &server_events, srp); + + return 0; +} diff --git a/src/modules/module-avb/srp.h b/src/modules/module-avb/srp.h new file mode 100644 index 0000000000000000000000000000000000000000..853321fb366fd27d983e0ad32a5c753453f97063 --- /dev/null +++ b/src/modules/module-avb/srp.h @@ -0,0 +1,32 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_SRP_H +#define AVB_SRP_H + +#include "internal.h" + +int avb_srp_register(struct server *server); + +#endif /* AVB_SRP_H */ diff --git a/src/modules/module-avb/stream.c b/src/modules/module-avb/stream.c new file mode 100644 index 0000000000000000000000000000000000000000..b86f0814c36452907bd1df197b74a4bdd799852f --- /dev/null +++ b/src/modules/module-avb/stream.c @@ -0,0 +1,589 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <unistd.h> +#include <linux/if_ether.h> +#include <linux/if_packet.h> +#include <linux/net_tstamp.h> +#include <net/if.h> +#include <sys/ioctl.h> + +#include <spa/debug/mem.h> +#include <spa/pod/builder.h> +#include <spa/param/audio/format-utils.h> + +#include "iec61883.h" +#include "stream.h" +#include "utils.h" +#include "aecp-aem-descriptors.h" + +static void on_stream_destroy(void *d) +{ + struct stream *stream = d; + spa_hook_remove(&stream->stream_listener); + stream->stream = NULL; +} + +static void on_source_stream_process(void *data) +{ + struct stream *stream = data; + struct pw_buffer *buf; + struct spa_data *d; + uint32_t index, n_bytes; + int32_t avail, wanted; + + if ((buf = pw_stream_dequeue_buffer(stream->stream)) == NULL) { + pw_log_debug("out of buffers: %m"); + return; + } + + d = buf->buffer->datas; + + wanted = buf->requested ? buf->requested * stream->stride : d[0].maxsize; + + n_bytes = SPA_MIN(d[0].maxsize, (uint32_t)wanted); + + avail = spa_ringbuffer_get_read_index(&stream->ring, &index); + + if (avail < wanted) { + pw_log_debug("capture underrun %d < %d", avail, wanted); + memset(d[0].data, 0, n_bytes); + } else { + spa_ringbuffer_read_data(&stream->ring, + stream->buffer_data, + stream->buffer_size, + index % stream->buffer_size, + d[0].data, n_bytes); + index += n_bytes; + spa_ringbuffer_read_update(&stream->ring, index); + } + + d[0].chunk->size = n_bytes; + d[0].chunk->stride = stream->stride; + d[0].chunk->offset = 0; + buf->size = n_bytes / stream->stride; + + pw_stream_queue_buffer(stream->stream, buf); +} + +static const struct pw_stream_events source_stream_events = { + PW_VERSION_STREAM_EVENTS, + .destroy = on_stream_destroy, + .process = on_source_stream_process +}; + +static inline void +set_iovec(struct spa_ringbuffer *rbuf, void *buffer, uint32_t size, + uint32_t offset, struct iovec *iov, uint32_t len) +{ + iov[0].iov_len = SPA_MIN(len, size - offset); + iov[0].iov_base = SPA_PTROFF(buffer, offset, void); + iov[1].iov_len = len - iov[0].iov_len; + iov[1].iov_base = buffer; +} + +static int flush_write(struct stream *stream, uint64_t current_time) +{ + int32_t avail; + uint32_t index; + uint64_t ptime, txtime; + int pdu_count; + ssize_t n; + struct avb_frame_header *h = (void*)stream->pdu; + struct avb_packet_iec61883 *p = SPA_PTROFF(h, sizeof(*h), void); + uint8_t dbc; + + avail = spa_ringbuffer_get_read_index(&stream->ring, &index); + + pdu_count = (avail / stream->stride) / stream->frames_per_pdu; + + txtime = current_time + stream->t_uncertainty; + ptime = txtime + stream->mtt; + dbc = stream->dbc; + + while (pdu_count--) { + *(uint64_t*)CMSG_DATA(stream->cmsg) = txtime; + + set_iovec(&stream->ring, + stream->buffer_data, + stream->buffer_size, + index % stream->buffer_size, + &stream->iov[1], stream->payload_size); + + p->seq_num = stream->pdu_seq++; + p->tv = 1; + p->timestamp = ptime; + p->dbc = dbc; + + n = sendmsg(stream->source->fd, &stream->msg, 0); + if (n < 0 || n != (ssize_t)stream->pdu_size) { + pw_log_error("sendmsg() failed %zd != %zd: %m", + n, stream->pdu_size); + } + txtime += stream->pdu_period; + ptime += stream->pdu_period; + index += stream->payload_size; + dbc += stream->frames_per_pdu; + } + stream->dbc = dbc; + spa_ringbuffer_read_update(&stream->ring, index); + return 0; +} + +static void on_sink_stream_process(void *data) +{ + struct stream *stream = data; + struct pw_buffer *buf; + struct spa_data *d; + int32_t filled; + uint32_t index, offs, avail, size; + struct timespec now; + + if ((buf = pw_stream_dequeue_buffer(stream->stream)) == NULL) { + pw_log_debug("out of buffers: %m"); + return; + } + + d = buf->buffer->datas; + + offs = SPA_MIN(d[0].chunk->offset, d[0].maxsize); + size = SPA_MIN(d[0].chunk->size, d[0].maxsize - offs); + avail = size - offs; + + filled = spa_ringbuffer_get_write_index(&stream->ring, &index); + + if (filled >= (int32_t)stream->buffer_size) { + pw_log_warn("playback overrun %d >= %zd", filled, stream->buffer_size); + } else { + spa_ringbuffer_write_data(&stream->ring, + stream->buffer_data, + stream->buffer_size, + index % stream->buffer_size, + SPA_PTROFF(d[0].data, offs, void), avail); + index += avail; + spa_ringbuffer_write_update(&stream->ring, index); + } + pw_stream_queue_buffer(stream->stream, buf); + + clock_gettime(CLOCK_TAI, &now); + flush_write(stream, SPA_TIMESPEC_TO_NSEC(&now)); +} + +static void setup_pdu(struct stream *stream) +{ + struct avb_frame_header *h; + struct avb_packet_iec61883 *p; + ssize_t payload_size, hdr_size, pdu_size; + + spa_memzero(stream->pdu, sizeof(stream->pdu)); + h = (struct avb_frame_header*)stream->pdu; + p = SPA_PTROFF(h, sizeof(*h), void); + + hdr_size = sizeof(*h) + sizeof(*p); + payload_size = stream->stride * stream->frames_per_pdu; + pdu_size = hdr_size + payload_size; + + h->type = htons(0x8100); + h->prio_cfi_id = htons((stream->prio << 13) | stream->vlan_id); + h->etype = htons(0x22f0); + + if (stream->direction == SPA_DIRECTION_OUTPUT) { + p->subtype = AVB_SUBTYPE_61883_IIDC; + p->sv = 1; + p->stream_id = htobe64(stream->id); + p->data_len = htons(payload_size+8); + p->tag = 0x1; + p->channel = 0x1f; + p->tcode = 0xa; + p->sid = 0x3f; + p->dbs = stream->info.info.raw.channels; + p->qi2 = 0x2; + p->format_id = 0x10; + p->fdf = 0x2; + p->syt = htons(0x0008); + } + stream->hdr_size = hdr_size; + stream->payload_size = payload_size; + stream->pdu_size = pdu_size; +} + +static int setup_msg(struct stream *stream) +{ + stream->iov[0].iov_base = stream->pdu; + stream->iov[0].iov_len = stream->hdr_size; + stream->iov[1].iov_base = SPA_PTROFF(stream->pdu, stream->hdr_size, void); + stream->iov[1].iov_len = stream->payload_size; + stream->iov[2].iov_base = SPA_PTROFF(stream->pdu, stream->hdr_size, void); + stream->iov[2].iov_len = 0; + stream->msg.msg_name = &stream->sock_addr; + stream->msg.msg_namelen = sizeof(stream->sock_addr); + stream->msg.msg_iov = stream->iov; + stream->msg.msg_iovlen = 3; + stream->msg.msg_control = stream->control; + stream->msg.msg_controllen = sizeof(stream->control); + stream->cmsg = CMSG_FIRSTHDR(&stream->msg); + stream->cmsg->cmsg_level = SOL_SOCKET; + stream->cmsg->cmsg_type = SCM_TXTIME; + stream->cmsg->cmsg_len = CMSG_LEN(sizeof(__u64)); + return 0; +} + +static const struct pw_stream_events sink_stream_events = { + PW_VERSION_STREAM_EVENTS, + .destroy = on_stream_destroy, + .process = on_sink_stream_process +}; + +struct stream *server_create_stream(struct server *server, + enum spa_direction direction, uint16_t index) +{ + struct stream *stream; + const struct descriptor *desc; + uint32_t n_params; + const struct spa_pod *params[1]; + uint8_t buffer[1024]; + struct spa_pod_builder b; + int res; + + desc = server_find_descriptor(server, + direction == SPA_DIRECTION_INPUT ? + AVB_AEM_DESC_STREAM_INPUT : + AVB_AEM_DESC_STREAM_OUTPUT, index); + if (desc == NULL) + return NULL; + + stream = calloc(1, sizeof(*stream)); + if (stream == NULL) + return NULL; + + stream->server = server; + stream->direction = direction; + stream->index = index; + stream->desc = desc; + spa_list_append(&server->streams, &stream->link); + + stream->prio = AVB_MSRP_PRIORITY_DEFAULT; + stream->vlan_id = AVB_DEFAULT_VLAN; + + stream->id = (uint64_t)server->mac_addr[0] << 56 | + (uint64_t)server->mac_addr[1] << 48 | + (uint64_t)server->mac_addr[2] << 40 | + (uint64_t)server->mac_addr[3] << 32 | + (uint64_t)server->mac_addr[4] << 24 | + (uint64_t)server->mac_addr[5] << 16 | + htons(index); + + stream->vlan_attr = avb_mvrp_attribute_new(server->mvrp, + AVB_MVRP_ATTRIBUTE_TYPE_VID); + stream->vlan_attr->attr.vid.vlan = htons(stream->vlan_id); + + stream->buffer_data = calloc(1, BUFFER_SIZE); + stream->buffer_size = BUFFER_SIZE; + spa_ringbuffer_init(&stream->ring); + + if (direction == SPA_DIRECTION_INPUT) { + stream->stream = pw_stream_new(server->impl->core, "source", + pw_properties_new( + PW_KEY_MEDIA_CLASS, "Audio/Source", + PW_KEY_NODE_NAME, "avb.source", + PW_KEY_NODE_DESCRIPTION, "AVB Source", + PW_KEY_NODE_WANT_DRIVER, "true", + NULL)); + } else { + stream->stream = pw_stream_new(server->impl->core, "sink", + pw_properties_new( + PW_KEY_MEDIA_CLASS, "Audio/Sink", + PW_KEY_NODE_NAME, "avb.sink", + PW_KEY_NODE_DESCRIPTION, "AVB Sink", + PW_KEY_NODE_WANT_DRIVER, "true", + NULL)); + } + if (stream->stream == NULL) + goto error_free; + + pw_stream_add_listener(stream->stream, + &stream->stream_listener, + direction == SPA_DIRECTION_INPUT ? + &source_stream_events : + &sink_stream_events, + stream); + + stream->info.info.raw.format = SPA_AUDIO_FORMAT_S24_32_BE; + stream->info.info.raw.flags = SPA_AUDIO_FLAG_UNPOSITIONED; + stream->info.info.raw.rate = 48000; + stream->info.info.raw.channels = 8; + stream->stride = stream->info.info.raw.channels * 4; + + n_params = 0; + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + params[n_params++] = spa_format_audio_raw_build(&b, + SPA_PARAM_EnumFormat, &stream->info.info.raw); + + if ((res = pw_stream_connect(stream->stream, + pw_direction_reverse(direction), + PW_ID_ANY, + PW_STREAM_FLAG_MAP_BUFFERS | + PW_STREAM_FLAG_INACTIVE | + PW_STREAM_FLAG_RT_PROCESS, + params, n_params)) < 0) + goto error_free_stream; + + stream->frames_per_pdu = 6; + stream->pdu_period = SPA_NSEC_PER_SEC * stream->frames_per_pdu / + stream->info.info.raw.rate; + + setup_pdu(stream); + setup_msg(stream); + + stream->listener_attr = avb_msrp_attribute_new(server->msrp, + AVB_MSRP_ATTRIBUTE_TYPE_LISTENER); + stream->talker_attr = avb_msrp_attribute_new(server->msrp, + AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE); + stream->talker_attr->attr.talker.vlan_id = htons(stream->vlan_id); + stream->talker_attr->attr.talker.tspec_max_frame_size = htons(32 + stream->frames_per_pdu * stream->stride); + stream->talker_attr->attr.talker.tspec_max_interval_frames = + htons(AVB_MSRP_TSPEC_MAX_INTERVAL_FRAMES_DEFAULT); + stream->talker_attr->attr.talker.priority = stream->prio; + stream->talker_attr->attr.talker.rank = AVB_MSRP_RANK_DEFAULT; + stream->talker_attr->attr.talker.accumulated_latency = htonl(95); + + return stream; + +error_free_stream: + pw_stream_destroy(stream->stream); + errno = -res; +error_free: + free(stream); + return NULL; +} + +void stream_destroy(struct stream *stream) +{ + avb_mrp_attribute_destroy(stream->listener_attr->mrp); + spa_list_remove(&stream->link); + free(stream); +} + +static int setup_socket(struct stream *stream) +{ + struct server *server = stream->server; + int fd, res; + char buf[128]; + struct ifreq req; + + fd = socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, htons(ETH_P_ALL)); + if (fd < 0) { + pw_log_error("socket() failed: %m"); + return -errno; + } + + spa_zero(req); + snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", server->ifname); + res = ioctl(fd, SIOCGIFINDEX, &req); + if (res < 0) { + pw_log_error("SIOCGIFINDEX %s failed: %m", server->ifname); + res = -errno; + goto error_close; + } + + spa_zero(stream->sock_addr); + stream->sock_addr.sll_family = AF_PACKET; + stream->sock_addr.sll_protocol = htons(ETH_P_TSN); + stream->sock_addr.sll_ifindex = req.ifr_ifindex; + + if (stream->direction == SPA_DIRECTION_OUTPUT) { + struct sock_txtime txtime_cfg; + + res = setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &stream->prio, + sizeof(stream->prio)); + if (res < 0) { + pw_log_error("setsockopt(SO_PRIORITY %d) failed: %m", stream->prio); + res = -errno; + goto error_close; + } + + txtime_cfg.clockid = CLOCK_TAI; + txtime_cfg.flags = 0; + res = setsockopt(fd, SOL_SOCKET, SO_TXTIME, &txtime_cfg, + sizeof(txtime_cfg)); + if (res < 0) { + pw_log_error("setsockopt(SO_TXTIME) failed: %m"); + res = -errno; + goto error_close; + } + } else { + struct packet_mreq mreq; + + res = bind(fd, (struct sockaddr *) &stream->sock_addr, sizeof(stream->sock_addr)); + if (res < 0) { + pw_log_error("bind() failed: %m"); + res = -errno; + goto error_close; + } + + spa_zero(mreq); + mreq.mr_ifindex = req.ifr_ifindex; + mreq.mr_type = PACKET_MR_MULTICAST; + mreq.mr_alen = ETH_ALEN; + memcpy(&mreq.mr_address, stream->addr, ETH_ALEN); + res = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, + &mreq, sizeof(struct packet_mreq)); + + pw_log_info("join %s", avb_utils_format_addr(buf, 128, stream->addr)); + + if (res < 0) { + pw_log_error("setsockopt(ADD_MEMBERSHIP) failed: %m"); + res = -errno; + goto error_close; + } + } + return fd; + +error_close: + close(fd); + return res; +} + +static void handle_iec61883_packet(struct stream *stream, + struct avb_packet_iec61883 *p, int len) +{ + uint32_t index, n_bytes; + int32_t filled; + + filled = spa_ringbuffer_get_write_index(&stream->ring, &index); + n_bytes = ntohs(p->data_len) - 8; + + if (filled + n_bytes > stream->buffer_size) { + pw_log_debug("capture overrun"); + } else { + spa_ringbuffer_write_data(&stream->ring, + stream->buffer_data, + stream->buffer_size, + index % stream->buffer_size, + p->payload, n_bytes); + index += n_bytes; + spa_ringbuffer_write_update(&stream->ring, index); + } +} + +static void on_socket_data(void *data, int fd, uint32_t mask) +{ + struct stream *stream = data; + + if (mask & SPA_IO_IN) { + int len; + uint8_t buffer[2048]; + + len = recv(fd, buffer, sizeof(buffer), 0); + + if (len < 0) { + pw_log_warn("got recv error: %m"); + } + else if (len < (int)sizeof(struct avb_packet_header)) { + pw_log_warn("short packet received (%d < %d)", len, + (int)sizeof(struct avb_packet_header)); + } else { + struct avb_frame_header *h = (void*)buffer; + struct avb_packet_iec61883 *p = SPA_PTROFF(h, sizeof(*h), void); + + if (memcmp(h->dest, stream->addr, 6) != 0 || + p->subtype != AVB_SUBTYPE_61883_IIDC) + return; + + handle_iec61883_packet(stream, p, len - sizeof(*h)); + } + } +} + +int stream_activate(struct stream *stream, uint64_t now) +{ + struct server *server = stream->server; + struct avb_frame_header *h = (void*)stream->pdu; + int fd, res; + + if (stream->source == NULL) { + if ((fd = setup_socket(stream)) < 0) + return fd; + + stream->source = pw_loop_add_io(server->impl->loop, fd, + SPA_IO_IN, true, on_socket_data, stream); + if (stream->source == NULL) { + res = -errno; + pw_log_error("stream %p: can't create source: %m", stream); + close(fd); + return res; + } + } + + avb_mrp_attribute_begin(stream->vlan_attr->mrp, now); + avb_mrp_attribute_join(stream->vlan_attr->mrp, now, true); + + if (stream->direction == SPA_DIRECTION_INPUT) { + stream->listener_attr->attr.listener.stream_id = htobe64(stream->peer_id); + stream->listener_attr->param = AVB_MSRP_LISTENER_PARAM_READY; + avb_mrp_attribute_begin(stream->listener_attr->mrp, now); + avb_mrp_attribute_join(stream->listener_attr->mrp, now, true); + + stream->talker_attr->attr.talker.stream_id = htobe64(stream->peer_id); + avb_mrp_attribute_begin(stream->talker_attr->mrp, now); + } else { + if ((res = avb_maap_get_address(server->maap, stream->addr, stream->index)) < 0) + return res; + + stream->listener_attr->attr.listener.stream_id = htobe64(stream->id); + stream->listener_attr->param = AVB_MSRP_LISTENER_PARAM_IGNORE; + avb_mrp_attribute_begin(stream->listener_attr->mrp, now); + + stream->talker_attr->attr.talker.stream_id = htobe64(stream->id); + memcpy(stream->talker_attr->attr.talker.dest_addr, stream->addr, 6); + + stream->sock_addr.sll_halen = ETH_ALEN; + memcpy(&stream->sock_addr.sll_addr, stream->addr, ETH_ALEN); + memcpy(h->dest, stream->addr, 6); + memcpy(h->src, server->mac_addr, 6); + avb_mrp_attribute_begin(stream->talker_attr->mrp, now); + avb_mrp_attribute_join(stream->talker_attr->mrp, now, true); + } + pw_stream_set_active(stream->stream, true); + return 0; +} + +int stream_deactivate(struct stream *stream, uint64_t now) +{ + pw_stream_set_active(stream->stream, false); + + if (stream->source != NULL) { + pw_loop_destroy_source(stream->server->impl->loop, stream->source); + stream->source = NULL; + } + + avb_mrp_attribute_leave(stream->vlan_attr->mrp, now); + + if (stream->direction == SPA_DIRECTION_INPUT) { + avb_mrp_attribute_leave(stream->listener_attr->mrp, now); + } else { + avb_mrp_attribute_leave(stream->talker_attr->mrp, now); + } + return 0; +} diff --git a/src/modules/module-avb/stream.h b/src/modules/module-avb/stream.h new file mode 100644 index 0000000000000000000000000000000000000000..7062e2552b1a21dacd0693862b02f712a1fa75da --- /dev/null +++ b/src/modules/module-avb/stream.h @@ -0,0 +1,104 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_STREAM_H +#define AVB_STREAM_H + +#include <sys/socket.h> +#include <sys/types.h> +#include <linux/if_packet.h> +#include <net/if.h> + +#include <spa/utils/ringbuffer.h> +#include <spa/param/audio/format.h> + +#include <pipewire/pipewire.h> + +#define BUFFER_SIZE (1u<<16) +#define BUFFER_MASK (BUFFER_SIZE-1) + +struct stream { + struct spa_list link; + + struct server *server; + + uint16_t direction; + uint16_t index; + const struct descriptor *desc; + uint64_t id; + uint64_t peer_id; + + struct pw_stream *stream; + struct spa_hook stream_listener; + + uint8_t addr[6]; + struct spa_source *source; + int prio; + int vlan_id; + int mtt; + int t_uncertainty; + uint32_t frames_per_pdu; + int ptime_tolerance; + + uint8_t pdu[2048]; + size_t hdr_size; + size_t payload_size; + size_t pdu_size; + int64_t pdu_period; + uint8_t pdu_seq; + uint8_t prev_seq; + uint8_t dbc; + + struct iovec iov[3]; + struct sockaddr_ll sock_addr; + struct msghdr msg; + char control[CMSG_SPACE(sizeof(uint64_t))]; + struct cmsghdr *cmsg; + + struct spa_ringbuffer ring; + void *buffer_data; + size_t buffer_size; + + uint64_t format; + uint32_t stride; + struct spa_audio_info info; + + struct avb_msrp_attribute *talker_attr; + struct avb_msrp_attribute *listener_attr; + struct avb_mvrp_attribute *vlan_attr; +}; + +#include "msrp.h" +#include "mvrp.h" +#include "maap.h" + +struct stream *server_create_stream(struct server *server, + enum spa_direction direction, uint16_t index); + +void stream_destroy(struct stream *stream); + +int stream_activate(struct stream *stream, uint64_t now); +int stream_deactivate(struct stream *stream, uint64_t now); + +#endif /* AVB_STREAM_H */ diff --git a/src/modules/module-avb/utils.h b/src/modules/module-avb/utils.h new file mode 100644 index 0000000000000000000000000000000000000000..f6267224b244ffe18e0f26187a119e06102038d2 --- /dev/null +++ b/src/modules/module-avb/utils.h @@ -0,0 +1,86 @@ +/* AVB support + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef AVB_UTILS_H +#define AVB_UTILS_H + +#include <spa/utils/json.h> + +#include "internal.h" + +static inline char *avb_utils_format_id(char *str, size_t size, const uint64_t id) +{ + snprintf(str, size, "%02x:%02x:%02x:%02x:%02x:%02x:%04x", + (uint8_t)(id >> 56), + (uint8_t)(id >> 48), + (uint8_t)(id >> 40), + (uint8_t)(id >> 32), + (uint8_t)(id >> 24), + (uint8_t)(id >> 16), + (uint16_t)(id)); + return str; +} + +static inline int avb_utils_parse_id(const char *str, int len, uint64_t *id) +{ + char s[64]; + uint8_t v[6]; + uint16_t unique_id; + if (spa_json_parse_stringn(str, len, s, sizeof(s)) <= 0) + return -EINVAL; + if (sscanf(s, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx:%hx", + &v[0], &v[1], &v[2], &v[3], + &v[4], &v[5], &unique_id) == 7) { + *id = (uint64_t) v[0] << 56 | + (uint64_t) v[1] << 48 | + (uint64_t) v[2] << 40 | + (uint64_t) v[3] << 32 | + (uint64_t) v[4] << 24 | + (uint64_t) v[5] << 16 | + unique_id; + } else if (!spa_atou64(str, id, 0)) + return -EINVAL; + return 0; +} + +static inline char *avb_utils_format_addr(char *str, size_t size, const uint8_t addr[6]) +{ + snprintf(str, size, "%02x:%02x:%02x:%02x:%02x:%02x", + addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + return str; +} +static inline int avb_utils_parse_addr(const char *str, int len, uint8_t addr[6]) +{ + char s[64]; + uint8_t v[6]; + if (spa_json_parse_stringn(str, len, s, sizeof(s)) <= 0) + return -EINVAL; + if (sscanf(s, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &v[0], &v[1], &v[2], &v[3], &v[4], &v[5]) != 6) + return -EINVAL; + memcpy(addr, v, 6); + return 0; +} + +#endif /* AVB_UTILS_H */ diff --git a/src/modules/module-client-node/remote-node.c b/src/modules/module-client-node/remote-node.c index c0af9a21ffa0174997770e57894de7c05d5ade9c..387711515ec4c16f00df59e92f01661b79b76327 100644 --- a/src/modules/module-client-node/remote-node.c +++ b/src/modules/module-client-node/remote-node.c @@ -857,14 +857,8 @@ static int link_signal_func(void *user_data) { struct link *link = user_data; struct spa_system *data_system = link->data->context->data_system; - struct timespec ts = { 0, 0 }; - - pw_log_trace_fp("link %p: signal", link); - - spa_system_clock_gettime(data_system, CLOCK_MONOTONIC, &ts); - link->target.activation->status = PW_NODE_ACTIVATION_TRIGGERED; - link->target.activation->signal_time = SPA_TIMESPEC_TO_NSEC(&ts); + pw_log_trace_fp("link %p: signal %p", link, link->target.activation); if (SPA_UNLIKELY(spa_system_eventfd_write(data_system, link->signalfd, 1) < 0)) pw_log_warn("link %p: write failed %m", link); @@ -930,7 +924,7 @@ client_node_set_activation(void *_data, link->map = mm; link->target.activation = ptr; link->signalfd = signalfd; - link->target.signal = link_signal_func; + link->target.signal_func = link_signal_func; link->target.data = link; link->target.node = NULL; spa_list_append(&data->links, &link->link); diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index 8d1205a2a2e98633d3bd2040226e1f626f210375..5ac3cb6be336d48c8e8fe42cf7e952c7bb53824d 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -80,6 +80,7 @@ * * - \ref PW_KEY_AUDIO_RATE * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_MEDIA_CLASS * - \ref PW_KEY_NODE_LATENCY * - \ref PW_KEY_NODE_NAME @@ -89,7 +90,6 @@ * - \ref PW_KEY_NODE_VIRTUAL * - \ref PW_KEY_NODE_LATENCY * - \ref PW_KEY_REMOTE_NAME - * - \ref SPA_KEY_AUDIO_POSITION * * ## Example configuration *\code{.unparsed} @@ -125,6 +125,10 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic +#define DEFAULT_RATE 48000 +#define DEFAULT_CHANNELS 2 +#define DEFAULT_POSITION "[ FL FR ]" + /* Hopefully this is enough for any combination of AEC engine and resampler * input requirement for rate matching */ #define MAX_BUFSIZE_MS 100 @@ -859,9 +863,15 @@ static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_ *info = SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32P); info->rate = pw_properties_get_uint32(props, PW_KEY_AUDIO_RATE, info->rate); + if (info->rate == 0) + info->rate = DEFAULT_RATE; + info->channels = pw_properties_get_uint32(props, PW_KEY_AUDIO_CHANNELS, info->channels); + info->channels = SPA_MIN(info->channels, SPA_AUDIO_MAX_CHANNELS); if ((str = pw_properties_get(props, SPA_KEY_AUDIO_POSITION)) != NULL) parse_position(info, str, strlen(str)); + if (info->channels == 0) + parse_position(info, DEFAULT_POSITION, strlen(DEFAULT_POSITION)); } static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) @@ -927,14 +937,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) parse_audio_info(props, &impl->info); - if (impl->info.channels == 0) { - impl->info.channels = 2; - impl->info.position[0] = SPA_AUDIO_CHANNEL_FL; - impl->info.position[1] = SPA_AUDIO_CHANNEL_FR; - } - if (impl->info.rate == 0) - impl->info.rate = 48000; - if ((str = pw_properties_get(props, "source.props")) != NULL) pw_properties_update_string(impl->source_props, str, strlen(str)); if ((str = pw_properties_get(props, "sink.props")) != NULL) @@ -991,8 +993,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) goto error; } - (void)SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_AUDIO_AEC, (struct spa_audio_aec *)impl->aec); - pw_log_info("Using plugin AEC %s", impl->aec->name); if ((str = pw_properties_get(props, "aec.args")) != NULL) diff --git a/src/modules/module-example-sink.c b/src/modules/module-example-sink.c index 1d5face8701145a61f397700c0fb2acebe248411..5d8fe1c6e0bdf5113757214fc697093b4f71d958 100644 --- a/src/modules/module-example-sink.c +++ b/src/modules/module-example-sink.c @@ -64,6 +64,7 @@ * 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 @@ -107,7 +108,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); "[ node.description=<description of the nodes> ] " \ "[ audio.format=<format, default:"DEFAULT_FORMAT"> ] " \ "[ audio.rate=<sample rate, default: "SPA_STRINGIFY(DEFAULT_RATE)"> ] " \ - "[ audio.channels=<number of channels, default:"SPA_STRINGIFY(EFAULT_CHANNELS) "> ] " \ + "[ audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS) "> ] " \ "[ audio.position=<channel map, default:"DEFAULT_POSITION"> ] " \ "[ stream.props=<properties> ] " @@ -340,56 +341,59 @@ static void parse_position(struct spa_audio_info_raw *info, const char *val, siz } } -static int parse_audio_info(struct impl *impl) +static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - struct pw_properties *props = impl->stream_props; - struct spa_audio_info_raw *info = &impl->info; const char *str; spa_zero(*info); - if ((str = pw_properties_get(props, PW_KEY_AUDIO_FORMAT)) == NULL) str = DEFAULT_FORMAT; info->format = format_from_name(str, strlen(str)); + + info->rate = pw_properties_get_uint32(props, PW_KEY_AUDIO_RATE, info->rate); + if (info->rate == 0) + info->rate = DEFAULT_RATE; + + info->channels = pw_properties_get_uint32(props, PW_KEY_AUDIO_CHANNELS, info->channels); + info->channels = SPA_MIN(info->channels, SPA_AUDIO_MAX_CHANNELS); + if ((str = pw_properties_get(props, SPA_KEY_AUDIO_POSITION)) != NULL) + parse_position(info, str, strlen(str)); + if (info->channels == 0) + parse_position(info, DEFAULT_POSITION, strlen(DEFAULT_POSITION)); +} + +static int calc_frame_size(const struct spa_audio_info_raw *info) +{ + int res = info->channels; switch (info->format) { - case SPA_AUDIO_FORMAT_S8: case SPA_AUDIO_FORMAT_U8: - impl->frame_size = 1; - break; + case SPA_AUDIO_FORMAT_S8: + case SPA_AUDIO_FORMAT_ALAW: + case SPA_AUDIO_FORMAT_ULAW: + return res; case SPA_AUDIO_FORMAT_S16: - impl->frame_size = 2; - break; + case SPA_AUDIO_FORMAT_S16_OE: + case SPA_AUDIO_FORMAT_U16: + return res * 2; case SPA_AUDIO_FORMAT_S24: - impl->frame_size = 3; - break; + case SPA_AUDIO_FORMAT_S24_OE: + case SPA_AUDIO_FORMAT_U24: + return res * 3; case SPA_AUDIO_FORMAT_S24_32: + case SPA_AUDIO_FORMAT_S24_32_OE: case SPA_AUDIO_FORMAT_S32: + case SPA_AUDIO_FORMAT_S32_OE: + case SPA_AUDIO_FORMAT_U32: + case SPA_AUDIO_FORMAT_U32_OE: case SPA_AUDIO_FORMAT_F32: - impl->frame_size = 4; - break; + case SPA_AUDIO_FORMAT_F32_OE: + return res * 4; case SPA_AUDIO_FORMAT_F64: - impl->frame_size = 8; - break; + case SPA_AUDIO_FORMAT_F64_OE: + return res * 8; default: - pw_log_error("unsupported format '%s'", str); - return -EINVAL; - } - info->rate = pw_properties_get_uint32(props, PW_KEY_AUDIO_RATE, DEFAULT_RATE); - if (info->rate == 0) { - pw_log_error("invalid rate '%s'", str); - return -EINVAL; + return 0; } - info->channels = pw_properties_get_uint32(props, PW_KEY_AUDIO_CHANNELS, DEFAULT_CHANNELS); - if ((str = pw_properties_get(props, SPA_KEY_AUDIO_POSITION)) == NULL) - str = DEFAULT_POSITION; - parse_position(info, str, strlen(str)); - if (info->channels == 0) { - pw_log_error("invalid channels '%s'", str); - return -EINVAL; - } - impl->frame_size *= info->channels; - - return 0; } static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) @@ -467,7 +471,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_MEDIA_CLASS); - if ((res = parse_audio_info(impl)) < 0) { + parse_audio_info(impl->stream_props, &impl->info); + + impl->frame_size = calc_frame_size(&impl->info); + if (impl->frame_size == 0) { + res = -EINVAL; pw_log_error( "can't parse audio format"); goto error; } diff --git a/src/modules/module-example-source.c b/src/modules/module-example-source.c index 2b480538f947c87abdccfa6036d91095986b35a0..ccf6f388b9a5cf531153f0815538376b7839382f 100644 --- a/src/modules/module-example-source.c +++ b/src/modules/module-example-source.c @@ -64,6 +64,7 @@ * 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 @@ -107,7 +108,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); "[ node.description=<description of the nodes> ] " \ "[ audio.format=<format, default:"DEFAULT_FORMAT"> ] " \ "[ audio.rate=<sample rate, default: "SPA_STRINGIFY(DEFAULT_RATE)"> ] " \ - "[ audio.channels=<number of channels, default:"SPA_STRINGIFY(EFAULT_CHANNELS) "> ] " \ + "[ audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS) "> ] " \ "[ audio.position=<channel map, default:"DEFAULT_POSITION"> ] " \ "[ stream.props=<properties> ] " @@ -344,56 +345,59 @@ static void parse_position(struct spa_audio_info_raw *info, const char *val, siz } } -static int parse_audio_info(struct impl *impl) +static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - struct pw_properties *props = impl->stream_props; - struct spa_audio_info_raw *info = &impl->info; const char *str; spa_zero(*info); - if ((str = pw_properties_get(props, PW_KEY_AUDIO_FORMAT)) == NULL) str = DEFAULT_FORMAT; info->format = format_from_name(str, strlen(str)); + + info->rate = pw_properties_get_uint32(props, PW_KEY_AUDIO_RATE, info->rate); + if (info->rate == 0) + info->rate = DEFAULT_RATE; + + info->channels = pw_properties_get_uint32(props, PW_KEY_AUDIO_CHANNELS, info->channels); + info->channels = SPA_MIN(info->channels, SPA_AUDIO_MAX_CHANNELS); + if ((str = pw_properties_get(props, SPA_KEY_AUDIO_POSITION)) != NULL) + parse_position(info, str, strlen(str)); + if (info->channels == 0) + parse_position(info, DEFAULT_POSITION, strlen(DEFAULT_POSITION)); +} + +static int calc_frame_size(const struct spa_audio_info_raw *info) +{ + int res = info->channels; switch (info->format) { - case SPA_AUDIO_FORMAT_S8: case SPA_AUDIO_FORMAT_U8: - impl->frame_size = 1; - break; + case SPA_AUDIO_FORMAT_S8: + case SPA_AUDIO_FORMAT_ALAW: + case SPA_AUDIO_FORMAT_ULAW: + return res; case SPA_AUDIO_FORMAT_S16: - impl->frame_size = 2; - break; + case SPA_AUDIO_FORMAT_S16_OE: + case SPA_AUDIO_FORMAT_U16: + return res * 2; case SPA_AUDIO_FORMAT_S24: - impl->frame_size = 3; - break; + case SPA_AUDIO_FORMAT_S24_OE: + case SPA_AUDIO_FORMAT_U24: + return res * 3; case SPA_AUDIO_FORMAT_S24_32: + case SPA_AUDIO_FORMAT_S24_32_OE: case SPA_AUDIO_FORMAT_S32: + case SPA_AUDIO_FORMAT_S32_OE: + case SPA_AUDIO_FORMAT_U32: + case SPA_AUDIO_FORMAT_U32_OE: case SPA_AUDIO_FORMAT_F32: - impl->frame_size = 4; - break; + case SPA_AUDIO_FORMAT_F32_OE: + return res * 4; case SPA_AUDIO_FORMAT_F64: - impl->frame_size = 8; - break; + case SPA_AUDIO_FORMAT_F64_OE: + return res * 8; default: - pw_log_error("unsupported format '%s'", str); - return -EINVAL; - } - info->rate = pw_properties_get_uint32(props, PW_KEY_AUDIO_RATE, DEFAULT_RATE); - if (info->rate == 0) { - pw_log_error("invalid rate '%s'", str); - return -EINVAL; + return 0; } - info->channels = pw_properties_get_uint32(props, PW_KEY_AUDIO_CHANNELS, DEFAULT_CHANNELS); - if ((str = pw_properties_get(props, SPA_KEY_AUDIO_POSITION)) == NULL) - str = DEFAULT_POSITION; - parse_position(info, str, strlen(str)); - if (info->channels == 0) { - pw_log_error("invalid channels '%s'", str); - return -EINVAL; - } - impl->frame_size *= info->channels; - - return 0; } static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) @@ -471,7 +475,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_MEDIA_CLASS); - if ((res = parse_audio_info(impl)) < 0) { + parse_audio_info(impl->stream_props, &impl->info); + + impl->frame_size = calc_frame_size(&impl->info); + if (impl->frame_size == 0) { + res = -EINVAL; pw_log_error( "can't parse audio format"); goto error; } diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 9e8b475b9eea7effe515cef8ac31a29a77b1b7aa..d63567407269d719dd2348b888640fe1ac085617 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -591,6 +591,8 @@ static void capture_process(void *d) struct graph *graph = &impl->graph; uint32_t i, outsize = 0, n_hndl = graph->n_hndl; int32_t stride = 0; + struct graph_port *port; + struct spa_data *bd; if ((in = pw_stream_dequeue_buffer(impl->capture)) == NULL) pw_log_debug("out of capture buffers: %m"); @@ -602,30 +604,37 @@ static void capture_process(void *d) goto done; for (i = 0; i < in->buffer->n_datas; i++) { - struct spa_data *ds = &in->buffer->datas[i]; - struct graph_port *port = &graph->input[i]; uint32_t offs, size; - offs = SPA_MIN(ds->chunk->offset, ds->maxsize); - size = SPA_MIN(ds->chunk->size, ds->maxsize - offs); + bd = &in->buffer->datas[i]; - if (port->desc) + offs = SPA_MIN(bd->chunk->offset, bd->maxsize); + size = SPA_MIN(bd->chunk->size, bd->maxsize - offs); + + port = i < graph->n_input ? &graph->input[i] : NULL; + + if (port && port->desc) port->desc->connect_port(port->hndl, port->port, - SPA_PTROFF(ds->data, offs, void)); + SPA_PTROFF(bd->data, offs, void)); - outsize = SPA_MAX(outsize, size); - stride = SPA_MAX(stride, ds->chunk->stride); + outsize = i == 0 ? size : SPA_MIN(outsize, size); + stride = SPA_MAX(stride, bd->chunk->stride); } for (i = 0; i < out->buffer->n_datas; i++) { - struct spa_data *dd = &out->buffer->datas[i]; - struct graph_port *port = &graph->output[i]; - if (port->desc) - port->desc->connect_port(port->hndl, port->port, dd->data); + bd = &out->buffer->datas[i]; + + outsize = SPA_MIN(outsize, bd->maxsize); + + port = i < graph->n_output ? &graph->output[i] : NULL; + + if (port && port->desc) + port->desc->connect_port(port->hndl, port->port, bd->data); else - memset(dd->data, 0, outsize); - dd->chunk->offset = 0; - dd->chunk->size = outsize; - dd->chunk->stride = stride; + memset(bd->data, 0, outsize); + + bd->chunk->offset = 0; + bd->chunk->size = outsize; + bd->chunk->stride = stride; } for (i = 0; i < n_hndl; i++) { struct graph_hndl *hndl = &graph->hndl[i]; @@ -1690,15 +1699,29 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ * graph n_hndl times when needed. */ n_hndl = impl->capture_info.channels / n_input; if (n_hndl != impl->playback_info.channels / n_output) { - pw_log_error("invalid channels"); + pw_log_error("invalid channels. The capture stream has %1$d channels and " + "the filter has %2$d inputs. The playback stream has %3$d channels " + "and the filter has %4$d outputs. capture:%1$d / input:%2$d != " + "playback:%3$d / output:%4$d. Check inputs and outputs objects.", + impl->capture_info.channels, n_input, + impl->playback_info.channels, n_output); res = -EINVAL; goto error; } if (n_hndl > MAX_HNDL) { - pw_log_error("too many channels"); + pw_log_error("too many channels. %d > %d", n_hndl, MAX_HNDL); res = -EINVAL; goto error; } + if (n_hndl == 0) { + n_hndl = 1; + pw_log_warn("The capture stream has %1$d channels and " + "the filter has %2$d inputs. The playback stream has %3$d channels " + "and the filter has %4$d outputs. Some filter ports will be " + "unconnected..", + impl->capture_info.channels, n_input, + impl->playback_info.channels, n_output); + } pw_log_info("using %d instances %d %d", n_hndl, n_input, n_output); /* now go over all nodes and create instances. */ @@ -2083,8 +2106,9 @@ static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_ *info = SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32P); - info->rate = pw_properties_get_int32(props, PW_KEY_AUDIO_RATE, 0); - info->channels = pw_properties_get_int32(props, PW_KEY_AUDIO_CHANNELS, 0); + info->rate = pw_properties_get_int32(props, PW_KEY_AUDIO_RATE, info->rate); + info->channels = pw_properties_get_int32(props, PW_KEY_AUDIO_CHANNELS, info->channels); + info->channels = SPA_MIN(info->channels, SPA_AUDIO_MAX_CHANNELS); if ((str = pw_properties_get(props, SPA_KEY_AUDIO_POSITION)) != NULL) parse_position(info, str, strlen(str)); } diff --git a/src/modules/module-filter-chain/convolver.c b/src/modules/module-filter-chain/convolver.c index 075886583dcde92f676c1199da9f67574fd44e75..dcf54d03398bfd92ba3411660b3ff63169c25f36 100644 --- a/src/modules/module-filter-chain/convolver.c +++ b/src/modules/module-filter-chain/convolver.c @@ -232,9 +232,12 @@ static void convolver1_free(struct convolver1 *conv) fft_cpx_free(&conv->segments[i]); fft_cpx_free(&conv->segmentsIr[i]); } - fft_destroy(conv->fft); - fft_destroy(conv->ifft); - fft_free(conv->fft_buffer); + if (conv->fft) + fft_destroy(conv->fft); + if (conv->ifft) + fft_destroy(conv->ifft); + if (conv->fft_buffer) + fft_free(conv->fft_buffer); free(conv->segments); free(conv->segmentsIr); fft_cpx_free(&conv->pre_mult); diff --git a/src/modules/module-filter-chain/ladspa_plugin.c b/src/modules/module-filter-chain/ladspa_plugin.c index 76d80dc841b11940bee93bfadaad71d75ee6c731..591002bea33da82d339b58661775a807b33a8649 100644 --- a/src/modules/module-filter-chain/ladspa_plugin.c +++ b/src/modules/module-filter-chain/ladspa_plugin.c @@ -167,7 +167,7 @@ static const struct fc_descriptor *ladspa_make_desc(struct fc_plugin *plugin, co desc->desc.free = ladspa_free; desc->desc.name = d->Label; - desc->desc.flags = d->Properties; + desc->desc.flags = 0; desc->desc.n_ports = d->PortCount; desc->desc.ports = calloc(desc->desc.n_ports, sizeof(struct fc_port)); diff --git a/src/modules/module-filter-chain/lv2_plugin.c b/src/modules/module-filter-chain/lv2_plugin.c index 55b59a5c40dd458f1719a91454cd88fdfa4dd6d5..9f655700792e284fac69b94b4b6a1ba0b3827c13 100644 --- a/src/modules/module-filter-chain/lv2_plugin.c +++ b/src/modules/module-filter-chain/lv2_plugin.c @@ -37,9 +37,9 @@ #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" +#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> #include "plugin.h" diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index 543ee3d818b338496d098d5ab09be2a6a690bc8c..88462a61eb6499f21d710b03eb2bb08c147e58a3 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -99,7 +99,8 @@ * playback.props = { * node.name = "playback.CM106_stereo_pair_2" * audio.position = [ RL RR ] - * node.target = "alsa_output.usb-0d8c_USB_Sound_Device-00.analog-surround-71" + * target.object = "alsa_output.usb-0d8c_USB_Sound_Device-00.analog-surround-71" + * node.dont-reconnect = true * stream.dont-remix = true * node.passive = true * } @@ -188,33 +189,35 @@ static void capture_process(void *d) pw_log_debug("out of playback buffers: %m"); if (in != NULL && out != NULL) { + uint32_t outsize = UINT32_MAX; + int32_t stride = 0; + struct spa_data *d; + const void *src[in->buffer->n_datas]; - for (i = 0; i < out->buffer->n_datas; i++) { - struct spa_data *ds, *dd; - uint32_t outsize = 0; - int32_t stride = 0; - - dd = &out->buffer->datas[i]; + for (i = 0; i < in->buffer->n_datas; i++) { + uint32_t offs, size; - if (i < in->buffer->n_datas) { - uint32_t offs, size; + d = &in->buffer->datas[i]; + offs = SPA_MIN(d->chunk->offset, d->maxsize); + size = SPA_MIN(d->chunk->size, d->maxsize - offs); - ds = &in->buffer->datas[i]; + src[i] = SPA_PTROFF(d->data, offs, void); + outsize = SPA_MIN(outsize, size); + stride = SPA_MAX(stride, d->chunk->stride); + } + for (i = 0; i < out->buffer->n_datas; i++) { + d = &out->buffer->datas[i]; - offs = SPA_MIN(ds->chunk->offset, ds->maxsize); - size = SPA_MIN(ds->chunk->size, ds->maxsize - offs); - stride = SPA_MAX(stride, stride); + outsize = SPA_MIN(outsize, d->maxsize); - memcpy(dd->data, - SPA_PTROFF(ds->data, offs, void), size); + if (i < in->buffer->n_datas) + memcpy(d->data, src[i], outsize); + else + memset(d->data, 0, outsize); - outsize = SPA_MAX(outsize, size); - } else { - memset(dd->data, 0, outsize); - } - dd->chunk->offset = 0; - dd->chunk->size = outsize; - dd->chunk->stride = stride; + d->chunk->offset = 0; + d->chunk->size = outsize; + d->chunk->stride = stride; } } @@ -453,6 +456,7 @@ static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_ .format = SPA_AUDIO_FORMAT_F32P); info->rate = pw_properties_get_int32(props, PW_KEY_AUDIO_RATE, 0); info->channels = pw_properties_get_uint32(props, PW_KEY_AUDIO_CHANNELS, 0); + info->channels = SPA_MIN(info->channels, SPA_AUDIO_MAX_CHANNELS); if ((str = pw_properties_get(props, SPA_KEY_AUDIO_POSITION)) != NULL) parse_position(info, str, strlen(str)); } diff --git a/src/modules/module-pipe-tunnel.c b/src/modules/module-pipe-tunnel.c index 02d8d1b55c1ea41c114dc4d0ae535f47a2d53183..ff4a2596867ee45143a7ce217b715d8502379c0f 100644 --- a/src/modules/module-pipe-tunnel.c +++ b/src/modules/module-pipe-tunnel.c @@ -84,6 +84,7 @@ * 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 @@ -93,7 +94,7 @@ * - \ref PW_KEY_NODE_GROUP * - \ref PW_KEY_NODE_VIRTUAL * - \ref PW_KEY_MEDIA_CLASS - * - \ref PW_KEY_NODE_TARGET to specify the remote name or id to link to + * - \ref PW_KEY_TARGET_OBJECT to specify the remote name or serial id to link to * * When not otherwise specified, the pipe will accept or produce a * 16 bits, stereo, 48KHz sample stream. @@ -107,10 +108,11 @@ * tunnel.mode = playback * # Set the pipe name to tunnel to * pipe.filename = "/tmp/fifo_output" + * #audio.format=<sample format> * #audio.rate=<sample rate> * #audio.channels=<number of channels> * #audio.position=<channel map> - * #node.target=<remote target node> + * #target.object=<remote target node> * stream.props = { * # extra sink properties * } @@ -125,6 +127,11 @@ #define DEFAULT_CAPTURE_FILENAME "/tmp/fifo_input" #define DEFAULT_PLAYBACK_FILENAME "/tmp/fifo_output" +#define DEFAULT_FORMAT "S16" +#define DEFAULT_RATE 48000 +#define DEFAULT_CHANNELS 2 +#define DEFAULT_POSITION "[ FL FR ]" + PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic @@ -132,7 +139,8 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); "[ node.latency=<latency as fraction> ] " \ "[ node.name=<name of the nodes> ] " \ "[ node.description=<description of the nodes> ] " \ - "[ node.target=<remote node target name> ] " \ + "[ target.object=<remote node target name> ] " \ + "[ audio.format=<sample format> ] " \ "[ audio.rate=<sample rate> ] " \ "[ audio.channels=<number of channels> ] " \ "[ audio.position=<channel map> ] " \ @@ -377,7 +385,7 @@ static int create_fifo(struct impl *impl) do_unlink_fifo = true; } - if ((fd = open(filename, O_RDWR | O_CLOEXEC | O_NONBLOCK, 0)) <= 0) { + if ((fd = open(filename, O_RDWR | O_CLOEXEC | O_NONBLOCK, 0)) < 0) { res = -errno; pw_log_error("open('%s'): %s", filename, spa_strerror(res)); goto error; @@ -408,7 +416,7 @@ static int create_fifo(struct impl *impl) error: if (do_unlink_fifo) unlink(filename); - if (fd > 0) + if (fd >= 0) close(fd); return res; } @@ -453,7 +461,7 @@ static void impl_destroy(struct impl *impl) unlink(impl->filename); free(impl->filename); } - if (impl->fd > 0) + if (impl->fd >= 0) close(impl->fd); pw_properties_free(impl->stream_props); @@ -511,31 +519,28 @@ static inline uint32_t format_from_name(const char *name, size_t len) return SPA_AUDIO_FORMAT_UNKNOWN; } -static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) +static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { const char *str; - *info = SPA_AUDIO_INFO_RAW_INIT( - .rate = 48000, - .channels = 2, - .format = SPA_AUDIO_FORMAT_S16); - - if ((str = pw_properties_get(props, PW_KEY_AUDIO_FORMAT)) != NULL) { - uint32_t id; - - id = format_from_name(str, strlen(str)); - if (id != SPA_AUDIO_FORMAT_UNKNOWN) - info->format = id; - } + spa_zero(*info); + if ((str = pw_properties_get(props, PW_KEY_AUDIO_FORMAT)) == NULL) + str = DEFAULT_FORMAT; + info->format = format_from_name(str, strlen(str)); info->rate = pw_properties_get_uint32(props, PW_KEY_AUDIO_RATE, info->rate); + if (info->rate == 0) + info->rate = DEFAULT_RATE; + info->channels = pw_properties_get_uint32(props, PW_KEY_AUDIO_CHANNELS, info->channels); + info->channels = SPA_MIN(info->channels, SPA_AUDIO_MAX_CHANNELS); if ((str = pw_properties_get(props, SPA_KEY_AUDIO_POSITION)) != NULL) parse_position(info, str, strlen(str)); - + if (info->channels == 0) + parse_position(info, DEFAULT_POSITION, strlen(DEFAULT_POSITION)); } -static int calc_frame_size(struct spa_audio_info_raw *info) +static int calc_frame_size(const struct spa_audio_info_raw *info) { int res = info->channels; switch (info->format) { @@ -662,13 +667,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) parse_audio_info(impl->stream_props, &impl->info); - if (impl->info.rate != 0 && - pw_properties_get(props, PW_KEY_NODE_RATE) == NULL) - pw_properties_setf(props, PW_KEY_NODE_RATE, - "1/%u", impl->info.rate), - - copy_props(impl, props, PW_KEY_NODE_RATE); - impl->frame_size = calc_frame_size(&impl->info); if (impl->frame_size == 0) { pw_log_error("unsupported audio format:%d channels:%d", @@ -676,6 +674,13 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) res = -EINVAL; goto error; } + if (impl->info.rate != 0 && + pw_properties_get(props, PW_KEY_NODE_RATE) == NULL) + pw_properties_setf(props, PW_KEY_NODE_RATE, + "1/%u", impl->info.rate), + + copy_props(impl, props, PW_KEY_NODE_RATE); + impl->leftover = calloc(1, impl->frame_size); if (impl->leftover == NULL) { res = -errno; diff --git a/src/modules/module-protocol-native.c b/src/modules/module-protocol-native.c index 556ee14c03ea142baac1f091cbe0f774474c0dad..5ec880e80afdfdf84634d4405c74ca16f04cd980 100644 --- a/src/modules/module-protocol-native.c +++ b/src/modules/module-protocol-native.c @@ -38,7 +38,7 @@ #ifdef HAVE_PWD_H #include <pwd.h> #endif -#if defined(__FreeBSD__) +#if defined(__FreeBSD__) || defined(__MidnightBSD__) #include <sys/ucred.h> #endif @@ -103,6 +103,11 @@ PW_LOG_TOPIC(mod_topic_connection, "conn." NAME); * - XDG_RUNTIME_DIR * - USERPROFILE * + * The socket address will be written into the notification file descriptor + * if the following environment variable is set: + * + * - PIPEWIRE_NOTIFICATION_FD + * * When a client connect, the connection will be made to: * * - PIPEWIRE_REMOTE : the environment with the remote name @@ -534,7 +539,7 @@ static struct client_data *client_new(struct server *s, int fd) struct pw_impl_client *client; struct pw_protocol *protocol = s->this.protocol; socklen_t len; -#if defined(__FreeBSD__) +#if defined(__FreeBSD__) || defined(__MidnightBSD__) struct xucred xucred; #else struct ucred ucred; @@ -583,7 +588,7 @@ static struct client_data *client_new(struct server *s, int fd) (int)len, buffer); } } -#elif defined(__FreeBSD__) +#elif defined(__FreeBSD__) || defined(__MidnightBSD__) len = sizeof(xucred); if (getsockopt(fd, 0, LOCAL_PEERCRED, &xucred, &len) < 0) { pw_log_warn("server %p: no peercred: %m", s); @@ -605,12 +610,15 @@ static struct client_data *client_new(struct server *s, int fd) if (client == NULL) goto exit; - this = pw_impl_client_get_user_data(client); spa_list_append(&s->this.client_list, &this->protocol_link); this->server = s; this->client = client; + pw_map_init(&this->compat_v2.types, 0, 32); + + pw_impl_client_add_listener(client, &this->client_listener, &client_events, this); + this->source = pw_loop_add_io(pw_context_get_main_loop(context), fd, SPA_IO_ERR | SPA_IO_HUP, true, connection_data, this); @@ -625,15 +633,11 @@ static struct client_data *client_new(struct server *s, int fd) goto cleanup_client; } - pw_map_init(&this->compat_v2.types, 0, 32); - pw_protocol_native_connection_add_listener(this->connection, &this->conn_listener, &server_conn_events, this); - pw_impl_client_add_listener(client, &this->client_listener, &client_events, this); - if ((res = pw_impl_client_register(client, NULL)) < 0) goto cleanup_client; @@ -761,6 +765,44 @@ socket_data(void *data, int fd, uint32_t mask) } } +static int write_socket_address(struct server *s) +{ + long v; + int fd, res = 0; + char *endptr; + const char *env = getenv("PIPEWIRE_NOTIFICATION_FD"); + + if (env == NULL || env[0] == '\0') + return 0; + + errno = 0; + v = strtol(env, &endptr, 10); + if (endptr[0] != '\0') + errno = EINVAL; + if (errno != 0) { + res = -errno; + pw_log_error("server %p: strtol() failed with error: %m", s); + goto error; + } + fd = (int)v; + if (v != fd) { + res = -ERANGE; + pw_log_error("server %p: invalid fd %ld: %s", s, v, spa_strerror(res)); + goto error; + } + if (dprintf(fd, "%s\n", s->addr.sun_path) < 0) { + res = -errno; + pw_log_error("server %p: dprintf() failed with error: %m", s); + goto error; + } + close(fd); + unsetenv("PIPEWIRE_NOTIFICATION_FD"); + return 0; + +error: + return res; +} + static int add_socket(struct pw_protocol *protocol, struct server *s) { socklen_t size; @@ -815,6 +857,12 @@ static int add_socket(struct pw_protocol *protocol, struct server *s) } } + res = write_socket_address(s); + if (res < 0) { + pw_log_error("server %p: failed to write socket address: %s", s, + spa_strerror(res)); + goto error_close; + } s->activated = activated; s->loop = pw_context_get_main_loop(protocol->context); if (s->loop == NULL) { @@ -990,35 +1038,9 @@ error: goto done; } -static void on_client_connection_destroy(void *data) -{ - struct client *impl = data; - spa_hook_remove(&impl->conn_listener); -} - -static void on_client_need_flush(void *data) -{ - struct client *impl = data; - - pw_log_trace("need flush"); - impl->need_flush = true; - - if (impl->source && !(impl->source->mask & SPA_IO_OUT)) { - pw_loop_update_io(impl->context->main_loop, - impl->source, impl->source->mask | SPA_IO_OUT); - } -} - -static const struct pw_protocol_native_connection_events client_conn_events = { - PW_VERSION_PROTOCOL_NATIVE_CONNECTION_EVENTS, - .destroy = on_client_connection_destroy, - .need_flush = on_client_need_flush, -}; - static int impl_connect_fd(struct pw_protocol_client *client, int fd, bool do_close) { struct client *impl = SPA_CONTAINER_OF(client, struct client, this); - int res; impl->connected = false; impl->disconnecting = false; @@ -1028,23 +1050,10 @@ static int impl_connect_fd(struct pw_protocol_client *client, int fd, bool do_cl fd, SPA_IO_IN | SPA_IO_OUT | SPA_IO_HUP | SPA_IO_ERR, do_close, on_remote_data, impl); - if (impl->source == NULL) { - res = -errno; - goto error_cleanup; - } + if (impl->source == NULL) + return -errno; - pw_protocol_native_connection_add_listener(impl->connection, - &impl->conn_listener, - &client_conn_events, - impl); return 0; - -error_cleanup: - if (impl->connection) { - pw_protocol_native_connection_destroy(impl->connection); - impl->connection = NULL; - } - return res; } static void impl_disconnect(struct pw_protocol_client *client) @@ -1057,9 +1066,7 @@ static void impl_disconnect(struct pw_protocol_client *client) pw_loop_destroy_source(impl->context->main_loop, impl->source); impl->source = NULL; - if (impl->connection) - pw_protocol_native_connection_destroy(impl->connection); - impl->connection = NULL; + pw_protocol_native_connection_set_fd(impl->connection, -1); } static void impl_destroy(struct pw_protocol_client *client) @@ -1068,6 +1075,10 @@ static void impl_destroy(struct pw_protocol_client *client) impl_disconnect(client); + if (impl->connection) + pw_protocol_native_connection_destroy(impl->connection); + impl->connection = NULL; + spa_list_remove(&client->link); client_unref(impl); } @@ -1134,6 +1145,31 @@ error: goto done; } +static void on_client_connection_destroy(void *data) +{ + struct client *impl = data; + spa_hook_remove(&impl->conn_listener); +} + +static void on_client_need_flush(void *data) +{ + struct client *impl = data; + + pw_log_trace("need flush"); + impl->need_flush = true; + + if (impl->source && !(impl->source->mask & SPA_IO_OUT)) { + pw_loop_update_io(impl->context->main_loop, + impl->source, impl->source->mask | SPA_IO_OUT); + } +} + +static const struct pw_protocol_native_connection_events client_conn_events = { + PW_VERSION_PROTOCOL_NATIVE_CONNECTION_EVENTS, + .destroy = on_client_connection_destroy, + .need_flush = on_client_need_flush, +}; + static struct pw_protocol_client * impl_new_client(struct pw_protocol *protocol, struct pw_core *core, @@ -1160,6 +1196,10 @@ impl_new_client(struct pw_protocol *protocol, res = -errno; goto error_free; } + pw_protocol_native_connection_add_listener(impl->connection, + &impl->conn_listener, + &client_conn_events, + impl); if (props) { str = spa_dict_lookup(props, PW_KEY_REMOTE_INTENTION); diff --git a/src/modules/module-protocol-pulse.c b/src/modules/module-protocol-pulse.c index f638880a8a9ea2fb51cb2d4e2959ac2c1e9949e4..29ba0c09241fc0c3f1b322abaec150cdbc00b0ae 100644 --- a/src/modules/module-protocol-pulse.c +++ b/src/modules/module-protocol-pulse.c @@ -214,14 +214,16 @@ * VMs usually can't support the low latency settings that are possible on real * hardware. * - * ## Application settings (Rules) - * - * The pulse protocol module supports generic config rules. It provides a `quirks` - * and an `update-props` action. + * ## Stream settings and rules * * Streams created by module-protocol-pulse will use the stream.properties * section and stream.rules sections as usual. * + * ## Application settings (Rules) + * + * The pulse protocol module supports generic config rules. It supports a pulse.rules + * section with a `quirks` and an `update-props` action. + * *\code{.unparsed} * pulse.rules = [ * { diff --git a/src/modules/module-protocol-pulse/client.c b/src/modules/module-protocol-pulse/client.c index 167955c0e79d7bf01758f782184dc89a1d62d2b1..fe7d7885831557d91de73289c19af44f942c3ef6 100644 --- a/src/modules/module-protocol-pulse/client.c +++ b/src/modules/module-protocol-pulse/client.c @@ -159,10 +159,10 @@ void client_free(struct client *client) pending_sample_free(p); if (client->message) - message_free(impl, client->message, false, false); + message_free(client->message, false, false); spa_list_consume(msg, &client->out_messages, link) - message_free(impl, msg, true, false); + message_free(msg, true, false); spa_list_consume(o, &client->operations, link) operation_free(o); @@ -177,6 +177,9 @@ void client_free(struct client *client) free(client->default_sink); free(client->default_source); + free(client->temporary_default_sink); + free(client->temporary_default_source); + pw_properties_free(client->props); pw_properties_free(client->routes); @@ -220,14 +223,12 @@ int client_queue_message(struct client *client, struct message *msg) return 0; error: - message_free(impl, msg, false, false); + message_free(msg, false, false); return res; } static int client_try_flush_messages(struct client *client) { - struct impl *impl = client->impl; - pw_log_trace("client %p: flushing", client); spa_assert(!client->disconnect); @@ -254,7 +255,7 @@ static int client_try_flush_messages(struct client *client) } else { if (debug_messages && m->channel == SPA_ID_INVALID) message_dump(SPA_LOG_LEVEL_INFO, m); - message_free(impl, m, true, false); + message_free(m, true, false); client->out_index = 0; continue; } @@ -307,7 +308,7 @@ static bool drop_from_out_queue(struct client *client, struct message *m) if (m == first && client->out_index > 0) return false; - message_free(client->impl, m, true, false); + message_free(m, true, false); return true; } diff --git a/src/modules/module-protocol-pulse/client.h b/src/modules/module-protocol-pulse/client.h index d9344584038d9291135a963ffda090c507da12dc..ed0ee813f99cb56caa736443c66eb56188a2daa0 100644 --- a/src/modules/module-protocol-pulse/client.h +++ b/src/modules/module-protocol-pulse/client.h @@ -56,7 +56,7 @@ struct client { struct server *server; int ref; - const char *name; + const char *name; /* owned by `client::props` */ struct spa_source *source; @@ -75,6 +75,8 @@ struct client { struct pw_manager_object *metadata_default; char *default_sink; char *default_source; + char *temporary_default_sink; /**< pending value, for MOVE_* commands */ + char *temporary_default_source; /**< pending value, for MOVE_* commands */ struct pw_manager_object *metadata_routes; struct pw_properties *routes; diff --git a/src/modules/module-protocol-pulse/message.c b/src/modules/module-protocol-pulse/message.c index 43b7ba2a3aefa73455773a769076a1692aa5bd21..e70b03a2bcd87233221925ed2a74abc6106aa011 100644 --- a/src/modules/module-protocol-pulse/message.c +++ b/src/modules/module-protocol-pulse/message.c @@ -390,6 +390,9 @@ static int ensure_size(struct message *m, uint32_t size) uint32_t alloc, diff; void *data; + if (m->length > m->allocated) + return -ENOMEM; + if (m->length + size <= m->allocated) return size; @@ -397,12 +400,13 @@ static int ensure_size(struct message *m, uint32_t size) diff = alloc - m->allocated; if ((data = realloc(m->data, alloc)) == NULL) { free(m->data); - m->stat->allocated -= m->allocated; + m->data = NULL; + m->impl->stat.allocated -= m->allocated; m->allocated = 0; return -errno; } - m->stat->allocated += diff; - m->stat->accumulated += diff; + m->impl->stat.allocated += diff; + m->impl->stat.accumulated += diff; m->data = data; m->allocated = alloc; return size; @@ -826,18 +830,20 @@ struct message *message_alloc(struct impl *impl, uint32_t channel, uint32_t size msg = spa_list_first(&impl->free_messages, struct message, link); spa_list_remove(&msg->link); pw_log_trace("using recycled message %p size:%d", msg, size); + + spa_assert(msg->impl == impl); } else { if ((msg = calloc(1, sizeof(*msg))) == NULL) return NULL; pw_log_trace("new message %p size:%d", msg, size); - msg->stat = &impl->stat; - msg->stat->n_allocated++; - msg->stat->n_accumulated++; + msg->impl = impl; + msg->impl->stat.n_allocated++; + msg->impl->stat.n_accumulated++; } if (ensure_size(msg, size) < 0) { - message_free(impl, msg, false, true); + message_free(msg, false, true); return NULL; } @@ -849,23 +855,23 @@ struct message *message_alloc(struct impl *impl, uint32_t channel, uint32_t size return msg; } -void message_free(struct impl *impl, struct message *msg, bool dequeue, bool destroy) +void message_free(struct message *msg, bool dequeue, bool destroy) { if (dequeue) spa_list_remove(&msg->link); - if (msg->stat->allocated > MAX_ALLOCATED || msg->allocated > MAX_SIZE) + if (msg->impl->stat.allocated > MAX_ALLOCATED || msg->allocated > MAX_SIZE) destroy = true; if (destroy) { pw_log_trace("destroy message %p size:%d", msg, msg->allocated); - msg->stat->n_allocated--; - msg->stat->allocated -= msg->allocated; + msg->impl->stat.n_allocated--; + msg->impl->stat.allocated -= msg->allocated; free(msg->data); free(msg); } else { pw_log_trace("recycle message %p size:%d/%d", msg, msg->length, msg->allocated); - spa_list_append(&impl->free_messages, &msg->link); + spa_list_append(&msg->impl->free_messages, &msg->link); msg->length = 0; } } diff --git a/src/modules/module-protocol-pulse/message.h b/src/modules/module-protocol-pulse/message.h index 022f70e1be667918d503d180f9d6a782d56f2998..ad952929b3d9d061d1be6101c606124562893ac5 100644 --- a/src/modules/module-protocol-pulse/message.h +++ b/src/modules/module-protocol-pulse/message.h @@ -32,12 +32,10 @@ #include <spa/support/log.h> struct impl; -struct client; -struct stats; struct message { struct spa_list link; - struct stats *stat; + struct impl *impl; uint32_t extra[4]; uint32_t channel; uint32_t allocated; @@ -69,7 +67,7 @@ enum { }; struct message *message_alloc(struct impl *impl, uint32_t channel, uint32_t size); -void message_free(struct impl *impl, struct message *msg, bool dequeue, bool destroy); +void message_free(struct message *msg, bool dequeue, bool destroy); int message_get(struct message *m, ...); int message_put(struct message *m, ...); int message_dump(enum spa_log_level level, struct message *m); diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index 7dead6055432e66bff2783fc5a57ac3eb25cf93e..ff7e1e9957075f3c808cde17310cd63a5515dab6 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -87,9 +87,6 @@ #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) @@ -455,6 +452,10 @@ static uint32_t fix_playback_buffer_attr(struct stream *s, struct buffer_attr *a if (frame_size == 0) frame_size = 4; + pw_log_info("[%s] maxlength:%u tlength:%u minreq:%u prebuf:%u", + s->client->name, attr->maxlength, attr->tlength, + attr->minreq, attr->prebuf); + minreq = frac_to_bytes_round_up(s->min_req, &s->ss); max_latency = defs->quantum_limit * frame_size; @@ -651,6 +652,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); + if (attr->maxlength == (uint32_t) -1 || attr->maxlength > MAXLENGTH) attr->maxlength = MAXLENGTH; attr->maxlength -= attr->maxlength % frame_size; @@ -660,22 +664,23 @@ static uint32_t fix_record_buffer_attr(struct stream *s, struct buffer_attr *att if (attr->fragsize == (uint32_t) -1 || attr->fragsize == 0) attr->fragsize = frac_to_bytes_round_up(s->default_frag, &s->ss); - attr->fragsize -= attr->fragsize % frame_size; + attr->fragsize = SPA_MIN(attr->fragsize, attr->maxlength); + attr->fragsize = SPA_ROUND_UP(attr->fragsize, frame_size); attr->fragsize = SPA_MAX(attr->fragsize, minfrag); - attr->fragsize = SPA_MAX(attr->fragsize, frame_size); - if (attr->fragsize > attr->maxlength) - attr->fragsize = attr->maxlength; + /* 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; - attr->tlength = attr->minreq = attr->prebuf = 0; + if (s->adjust_latency) + attr->fragsize = SPA_ROUND_UP(latency, frame_size); - if (s->early_requests) { - latency = attr->fragsize; - } else if (s->adjust_latency) { - latency = attr->fragsize; - } else { - latency = attr->fragsize; - } + attr->tlength = 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", s->client->name, attr->maxlength, attr->fragsize, minfrag, @@ -748,13 +753,12 @@ static int reply_create_record_stream(struct stream *stream, struct pw_manager_o peer = find_linked(manager, peer->id, PW_DIRECTION_OUTPUT); if (peer && pw_manager_object_is_source_or_monitor(peer)) { name = pw_properties_get(peer->props, PW_KEY_NODE_NAME); + peer_index = peer->index; if (!pw_manager_object_is_source(peer)) { size_t len = (name ? strlen(name) : 5) + 10; - peer_index = peer->index; peer_name = tmp = alloca(len); snprintf(tmp, len, "%s.monitor", name ? name : "sink"); } else { - peer_index = peer->index; peer_name = name; } } else { @@ -849,6 +853,13 @@ static void manager_added(void *data, struct pw_manager_object *o) s->peer_index = peer->index; peer_name = pw_properties_get(peer->props, PW_KEY_NODE_NAME); + if (peer_name && s->direction == PW_DIRECTION_INPUT && + pw_manager_object_is_monitor(peer)) { + int len = strlen(peer_name) + 10; + char *tmp = alloca(len); + snprintf(tmp, len, "%s.monitor", peer_name); + peer_name = tmp; + } if (peer_name != NULL) stream_send_moved(s, peer->index, peer_name); } @@ -951,6 +962,8 @@ static void manager_metadata(void *data, struct pw_manager_object *o, free(client->default_sink); client->default_sink = value ? strdup(value) : NULL; } + free(client->temporary_default_sink); + client->temporary_default_sink = NULL; } if (key == NULL || spa_streq(key, "default.audio.source")) { if (value != NULL) { @@ -964,6 +977,8 @@ static void manager_metadata(void *data, struct pw_manager_object *o, free(client->default_source); client->default_source = value ? strdup(value) : NULL; } + free(client->temporary_default_source); + client->temporary_default_source = NULL; } if (changed) send_default_change_subscribe_event(client, true, true); @@ -1024,12 +1039,12 @@ static int do_set_client_name(struct client *client, uint32_t command, uint32_t changed++; } + client_update_quirks(client); + client->name = pw_properties_get(client->props, PW_KEY_APP_NAME); pw_log_info("[%s] %s tag:%d", client->name, commands[command].name, tag); - client_update_quirks(client); - if (client->core == NULL) { client->core = pw_context_connect(impl->context, pw_properties_copy(client->props), 0); @@ -1263,7 +1278,7 @@ do_process_done(struct spa_loop *loop, stream->timestamp = pd->pwt.now; stream->delay = pd->pwt.buffered * SPA_USEC_PER_SEC / stream->ss.rate; if (pd->pwt.rate.denom > 0) - stream->delay = pd->pwt.delay * SPA_USEC_PER_SEC / pd->pwt.rate.denom; + stream->delay += pd->pwt.delay * SPA_USEC_PER_SEC * pd->pwt.rate.num / pd->pwt.rate.denom; if (stream->direction == PW_DIRECTION_OUTPUT) { if (pd->quantum != stream->last_quantum) @@ -1318,10 +1333,10 @@ do_process_done(struct spa_loop *loop, stream->read_index += skip; avail = stream->attr.fragsize; } + pw_log_trace("avail:%d index:%u", avail, index); while ((uint32_t)avail >= stream->attr.fragsize) { - towrite = SPA_MIN(avail, MAX_FRAGSIZE); - towrite = SPA_ROUND_DOWN(towrite, stream->frame_size); + towrite = SPA_MIN((uint32_t)avail, stream->attr.fragsize); msg = message_alloc(impl, stream->channel, towrite); if (msg == NULL) @@ -1792,7 +1807,7 @@ static int do_create_record_stream(struct client *client, uint32_t command, uint struct channel_map map; uint32_t source_index; const char *source_name; - struct buffer_attr attr; + struct buffer_attr attr = { 0 }; bool corked = false, no_remap = false, no_remix = false, @@ -2111,7 +2126,7 @@ static int do_get_playback_latency(struct client *client, uint32_t command, uint if (stream == NULL || stream->type != STREAM_TYPE_PLAYBACK) return -ENOENT; - pw_log_debug("read:%"PRIx64" write:%"PRIx64" queued:%"PRIi64" delay:%"PRIi64 + pw_log_debug("read:0x%"PRIx64" write:0x%"PRIx64" queued:%"PRIi64" delay:%"PRIi64 " playing:%"PRIu64, stream->read_index, stream->write_index, stream->write_index - stream->read_index, stream->delay, @@ -2160,6 +2175,11 @@ static int do_get_record_latency(struct client *client, uint32_t command, uint32 if (stream == NULL || stream->type != STREAM_TYPE_RECORD) return -ENOENT; + pw_log_debug("read:0x%"PRIx64" write:0x%"PRIx64" queued:%"PRIi64" delay:%"PRIi64, + stream->read_index, stream->write_index, + stream->write_index - stream->read_index, stream->delay); + + gettimeofday(&now, NULL); reply = reply_new(client, tag); message_put(reply, @@ -3236,6 +3256,7 @@ static int do_update_proplist(struct client *client, uint32_t command, uint32_t } else { if (pw_properties_update(client->props, &props->dict) > 0) { client_update_quirks(client); + client->name = pw_properties_get(client->props, PW_KEY_APP_NAME); pw_core_update_properties(client->core, &client->props->dict); } } @@ -3675,6 +3696,27 @@ static int fill_card_info(struct client *client, struct message *m, return 0; } +static int fill_sink_info_proplist(struct message *m, const struct spa_dict *sink_props, + const struct pw_manager_object *card) +{ + struct pw_device_info *card_info = card ? card->info : NULL; + struct pw_properties *props = NULL; + + if (card_info && card_info->props) { + props = pw_properties_new_dict(sink_props); + if (props == NULL) + return -ENOMEM; + + pw_properties_add(props, card_info->props); + sink_props = &props->dict; + } + message_put(m, TAG_PROPLIST, sink_props, TAG_INVALID); + + pw_properties_free(props); + + return 0; +} + static int fill_sink_info(struct client *client, struct message *m, struct pw_manager_object *o) { @@ -3767,8 +3809,10 @@ static int fill_sink_info(struct client *client, struct message *m, TAG_INVALID); if (client->version >= 13) { + int res; + if ((res = fill_sink_info_proplist(m, info->props, card)) < 0) + return res; message_put(m, - TAG_PROPLIST, info->props, TAG_USEC, 0LL, /* requested latency */ TAG_INVALID); } @@ -3857,22 +3901,27 @@ static int fill_sink_info(struct client *client, struct message *m, return 0; } -static int fill_source_info_proplist(struct message *m, struct pw_manager_object *o, - struct pw_node_info *info) +static int fill_source_info_proplist(struct message *m, const struct spa_dict *source_props, + const struct pw_manager_object *card, const bool is_monitor) { + struct pw_device_info *card_info = card ? card->info : NULL; struct pw_properties *props = NULL; - struct spa_dict *props_dict = info->props; - if (pw_manager_object_is_monitor(o)) { - props = pw_properties_new_dict(info->props); + if ((card_info && card_info->props) || is_monitor) { + props = pw_properties_new_dict(source_props); if (props == NULL) return -ENOMEM; - pw_properties_set(props, PW_KEY_DEVICE_CLASS, "monitor"); - props_dict = &props->dict; + if (card_info && card_info->props) + pw_properties_add(props, card_info->props); + + if (is_monitor) + pw_properties_set(props, PW_KEY_DEVICE_CLASS, "monitor"); + + source_props = &props->dict; } + message_put(m, TAG_PROPLIST, source_props, TAG_INVALID); - message_put(m, TAG_PROPLIST, props_dict, TAG_INVALID); pw_properties_free(props); return 0; @@ -3974,7 +4023,7 @@ static int fill_source_info(struct client *client, struct message *m, if (client->version >= 13) { int res; - if ((res = fill_source_info_proplist(m, o, info)) < 0) + if ((res = fill_source_info_proplist(m, info->props, card, is_monitor)) < 0) return res; message_put(m, TAG_USEC, 0LL, /* requested latency */ @@ -4313,7 +4362,7 @@ error_invalid: goto error; error: if (reply) - message_free(impl, reply, false, false); + message_free(reply, false, false); return res; } @@ -4389,7 +4438,7 @@ static int do_get_sample_info(struct client *client, uint32_t command, uint32_t error: if (reply) - message_free(impl, reply, false, false); + message_free(reply, false, false); return res; } @@ -4713,6 +4762,20 @@ static int do_set_default(struct client *client, uint32_t command, uint32_t tag, if (res < 0) return res; + /* + * The metadata is not necessarily updated within one server sync. + * Correct functioning of MOVE_* commands requires knowing the current + * default target, so we need to stash temporary values here in case + * the client emits them before metadata gets updated. + */ + if (sink) { + free(client->temporary_default_sink); + client->temporary_default_sink = name ? strdup(name) : NULL; + } else { + free(client->temporary_default_source); + client->temporary_default_source = name ? strdup(name) : NULL; + } + return operation_new(client, tag); } @@ -4754,6 +4817,7 @@ static int do_move_stream(struct client *client, uint32_t command, uint32_t tag, int target_id; int64_t target_serial; const char *name_device; + const char *name; struct pw_node_info *info; struct selector sel; int res; @@ -4790,7 +4854,13 @@ static int do_move_stream(struct client *client, uint32_t command, uint32_t tag, if ((dev = find_device(client, index_device, name_device, sink, NULL)) == NULL) return -ENOENT; - dev_default = find_device(client, SPA_ID_INVALID, NULL, sink, NULL); + /* + * The client metadata is not necessarily yet updated after SET_DEFAULT command, + * so use the temporary values if they are still set. + */ + name = sink ? client->temporary_default_sink : client->temporary_default_source; + dev_default = find_device(client, SPA_ID_INVALID, name, sink, NULL); + if (dev == dev_default) { /* * When moving streams to a node that is equal to the default, @@ -4816,6 +4886,11 @@ static int do_move_stream(struct client *client, uint32_t command, uint32_t tag, SPA_TYPE_INFO_BASE"Id", "%"PRIi64, target_serial)) < 0) return res; + name = spa_dict_lookup(info->props, PW_KEY_NODE_NAME); + pw_log_debug("[%s] %s done tag:%u index:%u name:%s target:%d target-serial:%"PRIi64, client->name, + commands[command].name, tag, index, name ? name : "<null>", + target_id, target_serial); + /* We will temporarily claim the stream was already moved */ set_temporary_move_target(client, o, dev->index); send_object_event(client, o, SUBSCRIPTION_EVENT_CHANGE); @@ -5353,7 +5428,7 @@ static void impl_clear(struct impl *impl) client_free(c); spa_list_consume(msg, &impl->free_messages, link) - message_free(impl, msg, true, true); + message_free(msg, true, true); pw_map_for_each(&impl->samples, impl_free_sample, impl); pw_map_clear(&impl->samples); diff --git a/src/modules/module-protocol-pulse/remap.c b/src/modules/module-protocol-pulse/remap.c index 1ddd9e3a7b58efc4934c0ca7acdd01eda53be3f9..aba66bb0729e801602d6cfbac7f610163f541148 100644 --- a/src/modules/module-protocol-pulse/remap.c +++ b/src/modules/module-protocol-pulse/remap.c @@ -43,6 +43,7 @@ const struct str_map media_role_map[] = { const struct str_map props_key_map[] = { { PW_KEY_DEVICE_BUS_PATH, "device.bus_path" }, + { PW_KEY_DEVICE_SYSFS_PATH, "sysfs.path" }, { PW_KEY_DEVICE_FORM_FACTOR, "device.form_factor" }, { PW_KEY_DEVICE_ICON_NAME, "device.icon_name" }, { PW_KEY_DEVICE_INTENDED_ROLES, "device.intended_roles" }, diff --git a/src/modules/module-protocol-pulse/server.c b/src/modules/module-protocol-pulse/server.c index d6b7797b877683fb5ce134d45b3e8f8c4468360d..b363a66bc846b7acd93a4c5ae5b520c9daf83842 100644 --- a/src/modules/module-protocol-pulse/server.c +++ b/src/modules/module-protocol-pulse/server.c @@ -60,13 +60,13 @@ #include "server.h" #include "stream.h" #include "utils.h" +#include "flatpak-utils.h" #define LISTEN_BACKLOG 32 #define MAX_CLIENTS 64 static int handle_packet(struct client *client, struct message *msg) { - struct impl * const impl = client->impl; uint32_t command, tag; int res = 0; @@ -110,7 +110,7 @@ static int handle_packet(struct client *client, struct message *msg) res = cmd->run(client, command, tag, msg); finish: - message_free(impl, msg, false, false); + message_free(msg, false, false); if (res < 0) reply_error(client, command, tag, res); @@ -119,7 +119,6 @@ finish: static int handle_memblock(struct client *client, struct message *msg) { - struct impl * const impl = client->impl; struct stream *stream; uint32_t channel, flags, index; int64_t offset, diff; @@ -190,7 +189,7 @@ static int handle_memblock(struct client *client, struct message *msg) stream_send_request(stream); finish: - message_free(impl, msg, false, false); + message_free(msg, false, false); return res; } @@ -264,7 +263,7 @@ static int do_read(struct client *client) } if (client->message) - message_free(impl, client->message, false, false); + message_free(client->message, false, false); client->message = message_alloc(impl, channel, length); } else if (client->message && @@ -419,14 +418,47 @@ on_connect(void *data, int fd, uint32_t mask) client_access = server->client_access; if (server->addr.ss_family == AF_UNIX) { + char *app_id = NULL, *devices = NULL; + #ifdef SO_PRIORITY val = 6; if (setsockopt(client_fd, SOL_SOCKET, SO_PRIORITY, &val, sizeof(val)) < 0) pw_log_warn("setsockopt(SO_PRIORITY) failed: %m"); #endif pid = get_client_pid(client, client_fd); - if (pid != 0 && check_flatpak(client, pid) == 1) + if (pid != 0 && pw_check_flatpak(pid, &app_id, &devices) == 1) { + /* + * XXX: we should really use Portal client access here + * + * However, session managers currently support only camera + * permissions, and the XDG Portal doesn't have a "Sound Manager" + * permission defined. So for now, use access=flatpak, and determine + * extra permissions here. + * + * The application has access to the Pulseaudio socket, + * and with real PA it would always then have full sound access. + * We'll restrict the full access here behind devices=all; + * if the application can access all devices it can then + * also sound and camera devices directly, so granting also the + * Manager permissions here is reasonable. + * + * The "Manager" permission in any case is also currently not safe + * as the session manager does not check any permission store + * for it. + */ client_access = "flatpak"; + pw_properties_set(client->props, "pipewire.access.portal.app_id", + app_id); + + if (devices && (spa_streq(devices, "all") || + spa_strstartswith(devices, "all;") || + strstr(devices, ";all;"))) + pw_properties_set(client->props, PW_KEY_MEDIA_CATEGORY, "Manager"); + else + pw_properties_set(client->props, PW_KEY_MEDIA_CATEGORY, NULL); + } + free(devices); + free(app_id); } else if (server->addr.ss_family == AF_INET || server->addr.ss_family == AF_INET6) { @@ -460,7 +492,7 @@ static int parse_unix_address(const char *address, struct sockaddr_storage *addr if (address[0] != '/') { char runtime_dir[PATH_MAX]; - if ((res = get_runtime_dir(runtime_dir, sizeof(runtime_dir), "pulse")) < 0) + if ((res = get_runtime_dir(runtime_dir, sizeof(runtime_dir))) < 0) return res; res = snprintf(addr.sun_path, sizeof(addr.sun_path), diff --git a/src/modules/module-protocol-pulse/utils.c b/src/modules/module-protocol-pulse/utils.c index 52ddf75fc9ac1e3e751118dfb4e7b0a06b82bed0..1c9bc1c5f30cb4d4188e6248976bbbbd31478d51 100644 --- a/src/modules/module-protocol-pulse/utils.c +++ b/src/modules/module-protocol-pulse/utils.c @@ -50,27 +50,30 @@ #include "log.h" #include "utils.h" -int get_runtime_dir(char *buf, size_t buflen, const char *dir) +int get_runtime_dir(char *buf, size_t buflen) { - const char *runtime_dir; + const char *runtime_dir, *dir = NULL; struct stat stat_buf; int res, size; runtime_dir = getenv("PULSE_RUNTIME_PATH"); - if (runtime_dir == NULL) + if (runtime_dir == NULL) { runtime_dir = getenv("XDG_RUNTIME_DIR"); - + dir = "pulse"; + } if (runtime_dir == NULL) { pw_log_error("could not find a suitable runtime directory in" "$PULSE_RUNTIME_PATH and $XDG_RUNTIME_DIR"); return -ENOENT; } - size = snprintf(buf, buflen, "%s/%s", runtime_dir, dir); + size = snprintf(buf, buflen, "%s%s%s", runtime_dir, + dir ? "/" : "", dir ? dir : ""); if (size < 0) return -errno; if ((size_t) size >= buflen) { - pw_log_error("path %s/%s too long", runtime_dir, dir); + pw_log_error("path %s%s%s too long", runtime_dir, + dir ? "/" : "", dir ? dir : ""); return -ENAMETOOLONG; } @@ -149,7 +152,7 @@ pid_t get_client_pid(struct client *client, int client_fd) pw_log_warn("client %p: no peercred: %m", client); } else return ucred.pid; -#elif defined(__FreeBSD__) +#elif defined(__FreeBSD__) || defined(__MidnightBSD__) struct xucred xucred; len = sizeof(xucred); if (getsockopt(client_fd, 0, LOCAL_PEERCRED, &xucred, &len) < 0) { @@ -182,7 +185,7 @@ int create_pid_file(void) { FILE *f; int res; - if ((res = get_runtime_dir(pid_file, sizeof(pid_file), "pulse")) < 0) + if ((res = get_runtime_dir(pid_file, sizeof(pid_file))) < 0) return res; if (strlen(pid_file) > PATH_MAX - sizeof("/pid")) { diff --git a/src/modules/module-protocol-pulse/utils.h b/src/modules/module-protocol-pulse/utils.h index 488951f9fdb40762c330005bf5747e2db5cbd766..fafccf3f802dae6038e6d050f0ea703b62c9ed99 100644 --- a/src/modules/module-protocol-pulse/utils.h +++ b/src/modules/module-protocol-pulse/utils.h @@ -31,7 +31,7 @@ struct client; struct pw_context; -int get_runtime_dir(char *buf, size_t buflen, const char *dir); +int get_runtime_dir(char *buf, size_t buflen); int check_flatpak(struct client *client, pid_t pid); pid_t get_client_pid(struct client *client, int client_fd); const char *get_server_name(struct pw_context *context); diff --git a/src/modules/module-pulse-tunnel.c b/src/modules/module-pulse-tunnel.c index a8a641e9cac428856b79b24439b3acd2b944eb94..f0231b9f3117e830551a64686ce8fec77337e95a 100644 --- a/src/modules/module-pulse-tunnel.c +++ b/src/modules/module-pulse-tunnel.c @@ -80,6 +80,7 @@ * 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 @@ -89,7 +90,7 @@ * - \ref PW_KEY_NODE_GROUP * - \ref PW_KEY_NODE_VIRTUAL * - \ref PW_KEY_MEDIA_CLASS - * - \ref PW_KEY_NODE_TARGET to specify the remote name or id to link to + * - \ref PW_KEY_TARGET_OBJECT to specify the remote node.name or serial.id to link to * * ## Example configuration of a virtual sink * @@ -103,7 +104,7 @@ * #audio.rate=<sample rate> * #audio.channels=<number of channels> * #audio.position=<channel map> - * #node.target=<remote target node> + * #target.object=<remote target name> * stream.props = { * # extra sink properties * } @@ -118,11 +119,17 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic +#define DEFAULT_FORMAT "S16" +#define DEFAULT_RATE 48000 +#define DEFAULT_CHANNELS 2 +#define DEFAULT_POSITION "[ FL FR ]" + #define MODULE_USAGE "[ remote.name=<remote> ] " \ "[ node.latency=<latency as fraction> ] " \ "[ node.name=<name of the nodes> ] " \ "[ node.description=<description of the nodes> ] " \ "[ node.target=<remote node target name> ] " \ + "[ audio.format=<sample format> ] " \ "[ audio.rate=<sample rate> ] " \ "[ audio.channels=<number of channels> ] " \ "[ audio.position=<channel map> ] " \ @@ -271,7 +278,7 @@ static void playback_stream_process(void *d) } else { float error, corr; - error = (float)(impl->current_latency) - (float)impl->target_latency; + error = (float)impl->target_latency - (float)impl->current_latency; error = SPA_CLAMP(error, -impl->max_error, impl->max_error); corr = spa_dll_update(&impl->dll, error); @@ -415,33 +422,47 @@ static int create_stream(struct impl *impl) static void context_state_cb(pa_context *c, void *userdata) { struct impl *impl = userdata; + bool do_destroy = false; switch (pa_context_get_state(c)) { - case PA_CONTEXT_READY: case PA_CONTEXT_TERMINATED: case PA_CONTEXT_FAILED: + do_destroy = true; + SPA_FALLTHROUGH; + case PA_CONTEXT_READY: pa_threaded_mainloop_signal(impl->pa_mainloop, 0); break; case PA_CONTEXT_UNCONNECTED: + do_destroy = true; + break; case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; } + if (do_destroy) + pw_impl_module_schedule_destroy(impl->module); } static void stream_state_cb(pa_stream *s, void * userdata) { struct impl *impl = userdata; + bool do_destroy = false; switch (pa_stream_get_state(s)) { - case PA_STREAM_READY: case PA_STREAM_FAILED: case PA_STREAM_TERMINATED: + do_destroy = true; + SPA_FALLTHROUGH; + case PA_STREAM_READY: pa_threaded_mainloop_signal(impl->pa_mainloop, 0); break; case PA_STREAM_UNCONNECTED: + do_destroy = true; + break; case PA_STREAM_CREATING: break; } + if (do_destroy) + pw_impl_module_schedule_destroy(impl->module); } static void stream_read_request_cb(pa_stream *s, size_t length, void *userdata) @@ -653,7 +674,9 @@ static int create_pulse_stream(struct impl *impl) pa_stream_set_overflow_callback(impl->pa_stream, stream_overflow_cb, impl); pa_stream_set_latency_update_callback(impl->pa_stream, stream_latency_update_cb, impl); - remote_node_target = pw_properties_get(impl->props, PW_KEY_NODE_TARGET); + remote_node_target = pw_properties_get(impl->props, PW_KEY_TARGET_OBJECT); + if (remote_node_target == NULL) + remote_node_target = pw_properties_get(impl->props, PW_KEY_NODE_TARGET); bufferattr.fragsize = (uint32_t) -1; bufferattr.minreq = (uint32_t) -1; @@ -823,28 +846,25 @@ static inline uint32_t format_from_name(const char *name, size_t len) return SPA_AUDIO_FORMAT_UNKNOWN; } -static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) +static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { const char *str; - *info = SPA_AUDIO_INFO_RAW_INIT( - .rate = 48000, - .channels = 2, - .format = SPA_AUDIO_FORMAT_S16); - - if ((str = pw_properties_get(props, PW_KEY_AUDIO_FORMAT)) != NULL) { - uint32_t id; - - id = format_from_name(str, strlen(str)); - if (id != SPA_AUDIO_FORMAT_UNKNOWN) - info->format = id; - } + spa_zero(*info); + if ((str = pw_properties_get(props, PW_KEY_AUDIO_FORMAT)) == NULL) + str = DEFAULT_FORMAT; + info->format = format_from_name(str, strlen(str)); info->rate = pw_properties_get_uint32(props, PW_KEY_AUDIO_RATE, info->rate); + if (info->rate == 0) + info->rate = DEFAULT_RATE; + info->channels = pw_properties_get_uint32(props, PW_KEY_AUDIO_CHANNELS, info->channels); + info->channels = SPA_MIN(info->channels, SPA_AUDIO_MAX_CHANNELS); if ((str = pw_properties_get(props, SPA_KEY_AUDIO_POSITION)) != NULL) parse_position(info, str, strlen(str)); - + if (info->channels == 0) + parse_position(info, DEFAULT_POSITION, strlen(DEFAULT_POSITION)); } static int calc_frame_size(struct spa_audio_info_raw *info) diff --git a/src/modules/module-raop-discover.c b/src/modules/module-raop-discover.c index 88ca2062e6152f11f4b98830715e6de18b2fa96f..dd26bad950630b0c952dce8ffeaf01f7663d0c49 100644 --- a/src/modules/module-raop-discover.c +++ b/src/modules/module-raop-discover.c @@ -223,6 +223,8 @@ static void pw_properties_from_avahi_string(const char *key, const char *value, * 4 = FairPlay SAPv2.5. */ if (str_in_list(value, ",", "1")) value = "RSA"; + else if (str_in_list(value, ",", "4")) + value = "auth_setup"; else value = "none"; pw_properties_set(props, "raop.encryption.type", value); diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index e3bbce5123365d8ab983d40329f74562a45d35b9..83d279f2cc6e35f47596a62bcde9bdf6c39729f7 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -110,7 +110,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); "[ node.name=<name of the nodes> ] " \ "[ node.description=<description of the nodes> ] " \ "[ audio.format=<format, default:"DEFAULT_FORMAT"> ] " \ - "[ audio.rate=<sample rate, default: "SPA_STRINGIFY(DEFAuLT_RATE)"> ] " \ + "[ audio.rate=<sample rate, default: "SPA_STRINGIFY(DEFAULT_RATE)"> ] " \ "[ audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS)"> ] " \ "[ audio.position=<channel map, default:"DEFAULT_POSITION"> ] " \ "[ stream.props=<properties> ] " @@ -130,6 +130,7 @@ enum { enum { CRYPTO_NONE, CRYPTO_RSA, + CRYPTO_AUTH_SETUP, }; enum { CODEC_PCM, @@ -257,7 +258,8 @@ static inline uint64_t ntp_now(int clockid) return timespec_to_ntp(&now); } -static int send_udp_sync_packet(struct impl *impl) +static int send_udp_sync_packet(struct impl *impl, + struct sockaddr *dest_addr, socklen_t addrlen) { uint32_t pkt[5]; uint32_t rtptime = impl->rtptime; @@ -278,10 +280,11 @@ static int send_udp_sync_packet(struct impl *impl) pw_log_debug("sync: delayed:%u now:%"PRIu64" rtptime:%u", rtptime - delay, transmitted, rtptime); - return write(impl->control_fd, pkt, sizeof(pkt)); + return sendto(impl->control_fd, pkt, sizeof(pkt), 0, dest_addr, addrlen); } -static int send_udp_timing_packet(struct impl *impl, uint64_t remote, uint64_t received) +static int send_udp_timing_packet(struct impl *impl, uint64_t remote, uint64_t received, + struct sockaddr *dest_addr, socklen_t addrlen) { uint32_t pkt[8]; uint64_t transmitted; @@ -299,7 +302,7 @@ static int send_udp_timing_packet(struct impl *impl, uint64_t remote, uint64_t r pw_log_debug("sync: remote:%"PRIu64" received:%"PRIu64" transmitted:%"PRIu64, remote, received, transmitted); - return write(impl->timing_fd, pkt, sizeof(pkt)); + return sendto(impl->timing_fd, pkt, sizeof(pkt), 0, dest_addr, addrlen); } static int write_codec_pcm(void *dst, void *frames, uint32_t n_frames) @@ -345,7 +348,7 @@ static int flush_to_udp_packet(struct impl *impl) impl->sync++; if (impl->first || impl->sync == impl->sync_period) { impl->sync = 0; - send_udp_sync_packet(impl); + send_udp_sync_packet(impl, NULL, 0); } pkt[0] = htonl(0x80600000); if (impl->first) @@ -373,7 +376,7 @@ static int flush_to_udp_packet(struct impl *impl) impl->seq = (impl->seq + 1) & 0xffff; pw_log_debug("send %u", len + 12); - res = write(impl->server_fd, pkt, len + 12); + res = send(impl->server_fd, pkt, len + 12, 0); impl->first = false; @@ -417,7 +420,7 @@ static int flush_to_tcp_packet(struct impl *impl) impl->seq = (impl->seq + 1) & 0xffff; pw_log_debug("send %u", len + 16); - res = write(impl->server_fd, pkt, len + 16); + res = send(impl->server_fd, pkt, len + 16, 0); impl->first = false; @@ -593,9 +596,12 @@ on_timing_source_io(void *data, int fd, uint32_t mask) if (mask & SPA_IO_IN) { uint64_t remote, received; + struct sockaddr_storage sender; + socklen_t sender_size = sizeof(sender); received = ntp_now(CLOCK_MONOTONIC); - bytes = read(impl->timing_fd, packet, sizeof(packet)); + bytes = recvfrom(impl->timing_fd, packet, sizeof(packet), 0, + (struct sockaddr*)&sender, &sender_size); if (bytes < 0) { pw_log_debug("error reading timing packet: %m"); return; @@ -609,7 +615,11 @@ on_timing_source_io(void *data, int fd, uint32_t mask) return; remote = ((uint64_t)ntohl(packet[6])) << 32 | ntohl(packet[7]); - send_udp_timing_packet(impl, remote, received); + if (send_udp_timing_packet(impl, remote, received, + (struct sockaddr *)&sender, sender_size) < 0) { + pw_log_warn("error sending timing packet"); + return; + } } } @@ -833,10 +843,8 @@ static void rtsp_setup_reply(void *data, int status, const struct spa_dict *head return; ntp = ntp_now(CLOCK_MONOTONIC); - send_udp_timing_packet(impl, ntp, ntp); + send_udp_timing_packet(impl, ntp, ntp, NULL, 0); - impl->timing_source = pw_loop_add_io(impl->loop, impl->timing_fd, - SPA_IO_IN, false, on_timing_source_io, impl); impl->control_source = pw_loop_add_io(impl->loop, impl->control_fd, SPA_IO_IN, false, on_control_source_io, impl); @@ -868,6 +876,9 @@ static int rtsp_do_setup(struct impl *impl) if (impl->control_fd < 0 || impl->timing_fd < 0) goto error; + impl->timing_source = pw_loop_add_io(impl->loop, impl->timing_fd, + SPA_IO_IN, false, on_timing_source_io, impl); + pw_properties_setf(impl->headers, "Transport", "RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;" "control_port=%u;timing_port=%u", @@ -988,7 +999,7 @@ static int rtsp_do_announce(struct impl *impl) char iv[16*2]; int res, frames, i, ip_version; char *sdp; - char local_ip[256]; + char local_ip[256]; host = pw_properties_get(impl->props, "raop.hostname"); @@ -1048,6 +1059,32 @@ static int rtsp_do_announce(struct impl *impl) return res; } +static void rtsp_auth_setup_reply(void *data, int status, const struct spa_dict *headers) +{ + struct impl *impl = data; + + pw_log_info("reply %d", status); + + impl->encryption = CRYPTO_NONE; + + rtsp_do_announce(impl); +} + +static int rtsp_do_auth_setup(struct impl *impl) +{ + int res; + + char output[] = + "\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; +} + static const char *find_attr(char **tokens, const char *key) { int i; @@ -1143,7 +1180,7 @@ static int rtsp_do_auth(struct impl *impl, const struct spa_dict *headers) DEFAULT_USER_NAME, realm, nonce, resp); } else - return -EINVAL; + goto error; pw_properties_setf(impl->headers, "Authorization", "%s %s", tokens[0], auth); @@ -1168,7 +1205,10 @@ static void rtsp_options_reply(void *data, int status, const struct spa_dict *he rtsp_do_auth(impl, headers); break; case 200: - rtsp_do_announce(impl); + if (impl->encryption == CRYPTO_AUTH_SETUP) + rtsp_do_auth_setup(impl); + else + rtsp_do_announce(impl); break; } } @@ -1484,56 +1524,59 @@ static void parse_position(struct spa_audio_info_raw *info, const char *val, siz } } -static int parse_audio_info(struct impl *impl) +static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - struct pw_properties *props = impl->stream_props; - struct spa_audio_info_raw *info = &impl->info; const char *str; spa_zero(*info); - if ((str = pw_properties_get(props, PW_KEY_AUDIO_FORMAT)) == NULL) str = DEFAULT_FORMAT; info->format = format_from_name(str, strlen(str)); + + info->rate = pw_properties_get_uint32(props, PW_KEY_AUDIO_RATE, info->rate); + if (info->rate == 0) + info->rate = DEFAULT_RATE; + + info->channels = pw_properties_get_uint32(props, PW_KEY_AUDIO_CHANNELS, info->channels); + info->channels = SPA_MIN(info->channels, SPA_AUDIO_MAX_CHANNELS); + if ((str = pw_properties_get(props, SPA_KEY_AUDIO_POSITION)) != NULL) + parse_position(info, str, strlen(str)); + if (info->channels == 0) + parse_position(info, DEFAULT_POSITION, strlen(DEFAULT_POSITION)); +} + +static int calc_frame_size(struct spa_audio_info_raw *info) +{ + int res = info->channels; switch (info->format) { - case SPA_AUDIO_FORMAT_S8: case SPA_AUDIO_FORMAT_U8: - impl->frame_size = 1; - break; + case SPA_AUDIO_FORMAT_S8: + case SPA_AUDIO_FORMAT_ALAW: + case SPA_AUDIO_FORMAT_ULAW: + return res; case SPA_AUDIO_FORMAT_S16: - impl->frame_size = 2; - break; + case SPA_AUDIO_FORMAT_S16_OE: + case SPA_AUDIO_FORMAT_U16: + return res * 2; case SPA_AUDIO_FORMAT_S24: - impl->frame_size = 3; - break; + case SPA_AUDIO_FORMAT_S24_OE: + case SPA_AUDIO_FORMAT_U24: + return res * 3; case SPA_AUDIO_FORMAT_S24_32: + case SPA_AUDIO_FORMAT_S24_32_OE: case SPA_AUDIO_FORMAT_S32: + case SPA_AUDIO_FORMAT_S32_OE: + case SPA_AUDIO_FORMAT_U32: + case SPA_AUDIO_FORMAT_U32_OE: case SPA_AUDIO_FORMAT_F32: - impl->frame_size = 4; - break; + case SPA_AUDIO_FORMAT_F32_OE: + return res * 4; case SPA_AUDIO_FORMAT_F64: - impl->frame_size = 8; - break; + case SPA_AUDIO_FORMAT_F64_OE: + return res * 8; default: - pw_log_error("unsupported format '%s'", str); - return -EINVAL; - } - info->rate = pw_properties_get_uint32(props, PW_KEY_AUDIO_RATE, DEFAULT_RATE); - if (info->rate == 0) { - pw_log_error("invalid rate '%s'", str); - return -EINVAL; - } - info->channels = pw_properties_get_uint32(props, PW_KEY_AUDIO_CHANNELS, DEFAULT_CHANNELS); - if ((str = pw_properties_get(props, SPA_KEY_AUDIO_POSITION)) == NULL) - str = DEFAULT_POSITION; - parse_position(info, str, strlen(str)); - if (info->channels == 0) { - pw_log_error("invalid channels '%s'", str); - return -EINVAL; + return 0; } - impl->frame_size *= info->channels; - - return 0; } static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) @@ -1617,8 +1660,13 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_MEDIA_CLASS); - if ((res = parse_audio_info(impl)) < 0) { - pw_log_error( "can't parse audio format"); + parse_audio_info(impl->stream_props, &impl->info); + + impl->frame_size = calc_frame_size(&impl->info); + if (impl->frame_size == 0) { + pw_log_error("unsupported audio format:%d channels:%d", + impl->info.format, impl->info.channels); + res = -EINVAL; goto error; } @@ -1630,6 +1678,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->protocol = PROTO_TCP; else { pw_log_error( "can't handle transport %s", str); + res = -EINVAL; goto error; } @@ -1639,8 +1688,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->encryption = CRYPTO_NONE; else if (spa_streq(str, "RSA")) impl->encryption = CRYPTO_RSA; + else if (spa_streq(str, "auth_setup")) + impl->encryption = CRYPTO_AUTH_SETUP; else { pw_log_error( "can't handle encryption type %s", str); + res = -EINVAL; goto error; } @@ -1650,6 +1702,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->codec = CODEC_PCM; else { pw_log_error( "can't handle codec type %s", str); + res = -EINVAL; goto error; } str = pw_properties_get(props, "raop.password"); diff --git a/src/modules/module-raop/rtsp-client.c b/src/modules/module-raop/rtsp-client.c index c241bf40d04b4951cc17da983cc6a0c8c7e12a97..792b441908b8adaee955f0ed5783a4801f514af4 100644 --- a/src/modules/module-raop/rtsp-client.c +++ b/src/modules/module-raop/rtsp-client.c @@ -273,12 +273,30 @@ static int process_input(struct pw_rtsp_client *client) 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); @@ -466,7 +484,7 @@ int pw_rtsp_client_disconnect(struct pw_rtsp_client *client) return 0; } -int pw_rtsp_client_send(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, void (*reply) (void *user_data, int status, const struct spa_dict *headers), @@ -485,7 +503,7 @@ int pw_rtsp_client_send(struct pw_rtsp_client *client, cseq = ++client->cseq; - fprintf(f, "%s %s RTSP/1.0\r\n", cmd, client->url); + fprintf(f, "%s %s RTSP/1.0\r\n", cmd, url); fprintf(f, "CSeq: %d\r\n", cseq); if (headers != NULL) { @@ -519,3 +537,12 @@ int pw_rtsp_client_send(struct pw_rtsp_client *client, } return 0; } + +int pw_rtsp_client_send(struct pw_rtsp_client *client, + const char *cmd, const struct spa_dict *headers, + const char *content_type, const char *content, + 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); +} diff --git a/src/modules/module-raop/rtsp-client.h b/src/modules/module-raop/rtsp-client.h index 4588eb8303d7be6a197c2735688587d737703318..1ff13ee59c089d42233f6d4c6111c491a8d95e80 100644 --- a/src/modules/module-raop/rtsp-client.h +++ b/src/modules/module-raop/rtsp-client.h @@ -71,6 +71,12 @@ int pw_rtsp_client_disconnect(struct pw_rtsp_client *client); int pw_rtsp_client_get_local_ip(struct pw_rtsp_client *client, int *version, char *ip, size_t len); +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, + void (*reply) (void *user_data, int status, const struct spa_dict *headers), + void *user_data); + int pw_rtsp_client_send(struct pw_rtsp_client *client, const char *cmd, const struct spa_dict *headers, const char *content_type, const char *content, diff --git a/src/modules/module-roc-sink.c b/src/modules/module-roc-sink.c index e6c8b8e4e7f8b974e180eb81d1e09ecf2691a71f..1ba21ecb372e357940d49ef2b46f7638976d2d09 100644 --- a/src/modules/module-roc-sink.c +++ b/src/modules/module-roc-sink.c @@ -316,7 +316,7 @@ static int roc_sink_setup(struct module_roc_sink_data *data) /* Fixed to be the same as ROC sender config above */ info.rate = 44100; info.channels = 2; - info.format = SPA_AUDIO_FORMAT_F32_LE; + info.format = SPA_AUDIO_FORMAT_F32; info.position[0] = SPA_AUDIO_CHANNEL_FL; info.position[1] = SPA_AUDIO_CHANNEL_FR; diff --git a/src/modules/module-roc-source.c b/src/modules/module-roc-source.c index 2d7a676bbf9adf7a7083321c2ebd970dcaf3c5a0..8dc5761b6e2f487314b42a021771edca7cf1a13e 100644 --- a/src/modules/module-roc-source.c +++ b/src/modules/module-roc-source.c @@ -337,7 +337,7 @@ static int roc_source_setup(struct module_roc_source_data *data) /* Fixed to be the same as ROC receiver config above */ info.rate = 44100; info.channels = 2; - info.format = SPA_AUDIO_FORMAT_F32_LE; + info.format = SPA_AUDIO_FORMAT_F32; info.position[0] = SPA_AUDIO_CHANNEL_FL; info.position[1] = SPA_AUDIO_CHANNEL_FR; data->stride = info.channels * sizeof(float); diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c index cc87c5f678871de8fe60a4d19f28677dde4e6052..0ee3463d29dc4c2701e78d5b04a7dead0858b0c3 100644 --- a/src/modules/module-rt.c +++ b/src/modules/module-rt.c @@ -52,7 +52,7 @@ #include <stdio.h> #include <errno.h> #include <sys/stat.h> -#ifdef __FreeBSD__ +#if defined(__FreeBSD__) || defined(__MidnightBSD__) #include <sys/thr.h> #endif #include <fcntl.h> @@ -153,6 +153,11 @@ static const struct spa_dict_item module_props[] = { #ifdef HAVE_DBUS #define RTKIT_SERVICE_NAME "org.freedesktop.RealtimeKit1" #define RTKIT_OBJECT_PATH "/org/freedesktop/RealtimeKit1" +#define RTKIT_INTERFACE "org.freedesktop.RealtimeKit1" + +#define XDG_PORTAL_SERVICE_NAME "org.freedesktop.portal.Desktop" +#define XDG_PORTAL_OBJECT_PATH "/org/freedesktop/portal/desktop" +#define XDG_PORTAL_INTERFACE "org.freedesktop.portal.Realtime" /** \cond */ struct pw_rtkit_bus { @@ -184,7 +189,11 @@ struct impl { #ifdef HAVE_DBUS bool use_rtkit; - struct pw_rtkit_bus *system_bus; + /* For D-Bus. These are const static. */ + const char* service_name; + const char* object_path; + const char* interface; + struct pw_rtkit_bus *rtkit_bus; /* These are only for the RTKit implementation to fill in the `thread` * struct. Since there's barely any overhead here we'll do this @@ -205,7 +214,7 @@ static pid_t _gettid(void) return (pid_t) gettid(); #elif defined(__linux__) return syscall(SYS_gettid); -#elif defined(__FreeBSD__) +#elif defined(__FreeBSD__) || defined(__MidnightBSD__) long pid; thr_self(&pid); return (pid_t)pid; @@ -215,7 +224,7 @@ static pid_t _gettid(void) } #ifdef HAVE_DBUS -struct pw_rtkit_bus *pw_rtkit_bus_get_system(void) +struct pw_rtkit_bus *pw_rtkit_bus_get(DBusBusType bus_type) { struct pw_rtkit_bus *bus; DBusError error; @@ -231,7 +240,7 @@ struct pw_rtkit_bus *pw_rtkit_bus_get_system(void) if (bus == NULL) return NULL; - bus->bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error); + bus->bus = dbus_bus_get_private(bus_type, &error); if (bus->bus == NULL) goto error; @@ -241,12 +250,41 @@ struct pw_rtkit_bus *pw_rtkit_bus_get_system(void) error: free(bus); - pw_log_error("Failed to connect to system bus: %s", error.message); + pw_log_error("Failed to connect to %s bus: %s", + bus_type == DBUS_BUS_SYSTEM ? "system" : "session", error.message); dbus_error_free(&error); errno = ECONNREFUSED; return NULL; } +struct pw_rtkit_bus *pw_rtkit_bus_get_system(void) +{ + return pw_rtkit_bus_get(DBUS_BUS_SYSTEM); +} + +struct pw_rtkit_bus *pw_rtkit_bus_get_session(void) +{ + return pw_rtkit_bus_get(DBUS_BUS_SESSION); +} + +bool pw_rtkit_check_xdg_portal(struct pw_rtkit_bus *system_bus) +{ + DBusError error; + bool ret = true; + + dbus_error_init(&error); + + if (!dbus_bus_name_has_owner(system_bus->bus, XDG_PORTAL_SERVICE_NAME, &error)) { + pw_log_warn("Can't find xdg-portal: %s", error.name); + ret = false; + goto finish; + } +finish: + dbus_error_free(&error); + + return ret; +} + void pw_rtkit_bus_free(struct pw_rtkit_bus *system_bus) { dbus_connection_close(system_bus->bus); @@ -270,7 +308,7 @@ static int translate_error(const char *name) return -EIO; } -static long long rtkit_get_int_property(struct pw_rtkit_bus *connection, const char *propname, +static long long rtkit_get_int_property(struct impl *impl, const char *propname, long long *propval) { DBusMessage *m = NULL, *r = NULL; @@ -280,19 +318,19 @@ static long long rtkit_get_int_property(struct pw_rtkit_bus *connection, const c DBusError error; int current_type; long long ret; - const char *interfacestr = "org.freedesktop.RealtimeKit1"; + struct pw_rtkit_bus *connection = impl->rtkit_bus; dbus_error_init(&error); - if (!(m = dbus_message_new_method_call(RTKIT_SERVICE_NAME, - RTKIT_OBJECT_PATH, + if (!(m = dbus_message_new_method_call(impl->service_name, + impl->object_path, "org.freedesktop.DBus.Properties", "Get"))) { ret = -ENOMEM; goto finish; } if (!dbus_message_append_args(m, - DBUS_TYPE_STRING, &interfacestr, + DBUS_TYPE_STRING, &impl->interface, DBUS_TYPE_STRING, &propname, DBUS_TYPE_INVALID)) { ret = -ENOMEM; goto finish; @@ -349,60 +387,63 @@ finish: return ret; } -int pw_rtkit_get_max_realtime_priority(struct pw_rtkit_bus *connection) +int pw_rtkit_get_max_realtime_priority(struct impl *impl) { long long retval; int err; - err = rtkit_get_int_property(connection, "MaxRealtimePriority", &retval); + err = rtkit_get_int_property(impl, "MaxRealtimePriority", &retval); return err < 0 ? err : retval; } -int pw_rtkit_get_min_nice_level(struct pw_rtkit_bus *connection, int *min_nice_level) +int pw_rtkit_get_min_nice_level(struct impl *impl, int *min_nice_level) { long long retval; int err; - err = rtkit_get_int_property(connection, "MinNiceLevel", &retval); + err = rtkit_get_int_property(impl, "MinNiceLevel", &retval); if (err >= 0) *min_nice_level = retval; return err; } -long long pw_rtkit_get_rttime_usec_max(struct pw_rtkit_bus *connection) +long long pw_rtkit_get_rttime_usec_max(struct impl *impl) { long long retval; int err; - err = rtkit_get_int_property(connection, "RTTimeUSecMax", &retval); + err = rtkit_get_int_property(impl, "RTTimeUSecMax", &retval); return err < 0 ? err : retval; } -int pw_rtkit_make_realtime(struct pw_rtkit_bus *connection, pid_t thread, int priority) +int pw_rtkit_make_realtime(struct impl *impl, pid_t thread, int priority) { DBusMessage *m = NULL, *r = NULL; + dbus_uint64_t pid; dbus_uint64_t u64; dbus_uint32_t u32; DBusError error; int ret; + struct pw_rtkit_bus *connection = impl->rtkit_bus; dbus_error_init(&error); if (thread == 0) thread = _gettid(); - if (!(m = dbus_message_new_method_call(RTKIT_SERVICE_NAME, - RTKIT_OBJECT_PATH, - "org.freedesktop.RealtimeKit1", - "MakeThreadRealtime"))) { + if (!(m = dbus_message_new_method_call(impl->service_name, + impl->object_path, impl->interface, + "MakeThreadRealtimeWithPID"))) { ret = -ENOMEM; goto finish; } + pid = (dbus_uint64_t) getpid(); u64 = (dbus_uint64_t) thread; u32 = (dbus_uint32_t) priority; if (!dbus_message_append_args(m, + DBUS_TYPE_UINT64, &pid, DBUS_TYPE_UINT64, &u64, DBUS_TYPE_UINT32, &u32, DBUS_TYPE_INVALID)) { ret = -ENOMEM; @@ -435,31 +476,34 @@ finish: return ret; } -int pw_rtkit_make_high_priority(struct pw_rtkit_bus *connection, pid_t thread, int nice_level) +int pw_rtkit_make_high_priority(struct impl *impl, pid_t thread, int nice_level) { DBusMessage *m = NULL, *r = NULL; + dbus_uint64_t pid; dbus_uint64_t u64; dbus_int32_t s32; DBusError error; int ret; + struct pw_rtkit_bus *connection = impl->rtkit_bus; dbus_error_init(&error); if (thread == 0) thread = _gettid(); - if (!(m = dbus_message_new_method_call(RTKIT_SERVICE_NAME, - RTKIT_OBJECT_PATH, - "org.freedesktop.RealtimeKit1", - "MakeThreadHighPriority"))) { + if (!(m = dbus_message_new_method_call(impl->service_name, + impl->object_path, impl->interface, + "MakeThreadHighPriorityWithPID"))) { ret = -ENOMEM; goto finish; } + pid = (dbus_uint64_t) getpid(); u64 = (dbus_uint64_t) thread; s32 = (dbus_int32_t) nice_level; if (!dbus_message_append_args(m, + DBUS_TYPE_UINT64, &pid, DBUS_TYPE_UINT64, &u64, DBUS_TYPE_INT32, &s32, DBUS_TYPE_INVALID)) { ret = -ENOMEM; @@ -502,8 +546,8 @@ static void module_destroy(void *data) spa_hook_remove(&impl->module_listener); #ifdef HAVE_DBUS - if (impl->system_bus) - pw_rtkit_bus_free(impl->system_bus); + if (impl->rtkit_bus) + pw_rtkit_bus_free(impl->rtkit_bus); #endif free(impl); @@ -520,9 +564,8 @@ static const struct pw_impl_module_events module_events = { */ static bool check_realtime_privileges(rlim_t priority) { - int old_policy; + int err, old_policy, new_policy = REALTIME_POLICY; struct sched_param old_sched_params; - int new_policy = REALTIME_POLICY; struct sched_param new_sched_params; /* We could check `RLIMIT_RTPRIO`, but the BSDs generally don't have @@ -530,8 +573,8 @@ static bool check_realtime_privileges(rlim_t priority) * scheduling without that rlimit being set such as `CAP_SYS_NICE` or * running as root. Instead of checking a bunch of preconditions, we * just try if setting realtime scheduling works or not. */ - if (pthread_getschedparam(pthread_self(),&old_policy,&old_sched_params) < 0) { - pw_log_warn("Failed to check RLIMIT_RTPRIO %m"); + if ((err = pthread_getschedparam(pthread_self(),&old_policy,&old_sched_params)) != 0) { + pw_log_warn("Failed to check RLIMIT_RTPRIO: %s", strerror(err)); return false; } @@ -561,13 +604,13 @@ static int sched_set_nice(int nice_level) return -errno; } -static int set_nice(struct impl *impl, int nice_level) +static int set_nice(struct impl *impl, int nice_level, bool warn) { int res = 0; #ifdef HAVE_DBUS if (impl->use_rtkit) - res = pw_rtkit_make_high_priority(impl->system_bus, 0, nice_level); + res = pw_rtkit_make_high_priority(impl, 0, nice_level); else res = sched_set_nice(nice_level); #else @@ -575,13 +618,13 @@ static int set_nice(struct impl *impl, int nice_level) #endif if (res < 0) { - pw_log_warn("could not set nice-level to %d: %s", - nice_level, spa_strerror(res)); + if (warn) + pw_log_warn("could not set nice-level to %d: %s", + nice_level, spa_strerror(res)); } else { pw_log_info("main thread nice level set to %d", nice_level); } - return res; } @@ -597,11 +640,11 @@ static int set_rlimit(struct impl *impl) #ifdef HAVE_DBUS if (impl->use_rtkit) { long long rttime; - rttime = pw_rtkit_get_rttime_usec_max(impl->system_bus); + rttime = pw_rtkit_get_rttime_usec_max(impl); if (rttime >= 0) { if ((rlim_t)rttime < rl.rlim_cur) { - pw_log_debug("clamping rt.time.soft from %ld to %lld because of RTKit", - rl.rlim_cur, rttime); + pw_log_debug("clamping rt.time.soft from %llu to %lld because of RTKit", + (long long)rl.rlim_cur, rttime); } rl.rlim_cur = SPA_MIN(rl.rlim_cur, (rlim_t)rttime); @@ -742,7 +785,7 @@ static int impl_get_rt_range(void *object, const struct spa_dict *props, if (min) *min = 1; if (max) - *max = pw_rtkit_get_max_realtime_priority(impl->system_bus); + *max = pw_rtkit_get_max_realtime_priority(impl); } else { if (min) *min = sched_get_priority_min(REALTIME_POLICY); @@ -783,7 +826,7 @@ static int impl_acquire_rt(void *object, struct spa_thread *thread, int priority if (impl->use_rtkit) { pid = impl_gettid(impl, pt); - rtprio_limit = pw_rtkit_get_max_realtime_priority(impl->system_bus); + rtprio_limit = pw_rtkit_get_max_realtime_priority(impl); if (rtprio_limit >= 0 && rtprio_limit < priority) { pw_log_info("dropping requested priority %d for thread %d down to %d because of RTKit limits", priority, pid, rtprio_limit); priority = rtprio_limit; @@ -796,7 +839,7 @@ static int impl_acquire_rt(void *object, struct spa_thread *thread, int priority pw_log_debug("SCHED_OTHER|SCHED_RESET_ON_FORK worked."); } - if ((err = pw_rtkit_make_realtime(impl->system_bus, pid, priority)) < 0) { + if ((err = pw_rtkit_make_realtime(impl, pid, priority)) < 0) { pw_log_warn("could not make thread %d realtime using RTKit: %s", pid, spa_strerror(err)); return err; } @@ -864,31 +907,17 @@ static const struct spa_thread_utils_methods impl_thread_utils = { #ifdef HAVE_DBUS -static int should_use_rtkit(struct impl *impl, struct pw_context *context, bool *use_rtkit) +static int check_rtkit(struct impl *impl, struct pw_context *context, bool *can_use_rtkit) { const struct pw_properties *context_props; const char *str; - *use_rtkit = true; + *can_use_rtkit = true; if ((context_props = pw_context_get_properties(context)) != NULL && (str = pw_properties_get(context_props, "support.dbus")) != NULL && !pw_properties_parse_bool(str)) - *use_rtkit = false; - - /* If the user has permissions to use regular realtime scheduling, then - * we'll use that instead of RTKit */ - if (check_realtime_privileges(impl->rt_prio)) { - *use_rtkit = false; - } else { - if (!(*use_rtkit)) { - pw_log_warn("neither regular realtime scheduling nor RTKit are available"); - return -ENOTSUP; - } - - /* TODO: Should this be pw_log_warn or pw_log_debug instead? */ - pw_log_info("could not use realtime scheduling, falling back to using RTKit instead"); - } + *can_use_rtkit = false; return 0; } @@ -922,35 +951,68 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->rt_time_soft = pw_properties_get_int32(props, "rt.time.soft", DEFAULT_RT_TIME_SOFT); impl->rt_time_hard = pw_properties_get_int32(props, "rt.time.hard", DEFAULT_RT_TIME_HARD); + bool can_use_rtkit = false, use_rtkit = false; + #ifdef HAVE_DBUS spa_list_init(&impl->threads_list); pthread_mutex_init(&impl->lock, NULL); pthread_cond_init(&impl->cond, NULL); - if ((res = should_use_rtkit(impl, context, &impl->use_rtkit)) < 0) { + if ((res = check_rtkit(impl, context, &can_use_rtkit)) < 0) goto error; +#endif + /* If the user has permissions to use regular realtime scheduling, as well as + * the nice level we want, then we'll use that instead of RTKit */ + if (!check_realtime_privileges(impl->rt_prio)) { + if (!can_use_rtkit) { + res = -ENOTSUP; + pw_log_warn("regular realtime scheduling not available (RTKit fallback disabled)"); + goto error; + } + use_rtkit = true; } + if (IS_VALID_NICE_LEVEL(impl->nice_level)) { + 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; if (impl->use_rtkit) { - impl->system_bus = pw_rtkit_bus_get_system(); - if (impl->system_bus == NULL) { - res = -errno; - pw_log_warn("could not get system bus: %m"); - goto error; + /* Checking xdg-desktop-portal. It works fine in all situations. */ + impl->rtkit_bus = pw_rtkit_bus_get_session(); + if (impl->rtkit_bus != NULL) { + if (pw_rtkit_check_xdg_portal(impl->rtkit_bus)) { + impl->service_name = XDG_PORTAL_SERVICE_NAME; + impl->object_path = XDG_PORTAL_OBJECT_PATH; + impl->interface = XDG_PORTAL_INTERFACE; + } else { + pw_log_warn("found session bus but no portal"); + pw_rtkit_bus_free(impl->rtkit_bus); + impl->rtkit_bus = NULL; + } } - } -#else - if (!check_realtime_privileges(impl->rt_prio)) { - res = -ENOTSUP; - pw_log_warn("regular realtime scheduling not available (RTKit fallback disabled)"); - goto error; + /* Failed to get xdg-desktop-portal, try to use rtkit. */ + if (impl->rtkit_bus == NULL) { + impl->rtkit_bus = pw_rtkit_bus_get_system(); + if (impl->rtkit_bus != NULL) { + impl->service_name = RTKIT_SERVICE_NAME; + impl->object_path = RTKIT_OBJECT_PATH; + impl->interface = RTKIT_INTERFACE; + } else { + res = -errno; + pw_log_warn("could not get system bus: %m"); + goto error; + } + } + /* Retry set_nice with rtkit */ + if (IS_VALID_NICE_LEVEL(impl->nice_level)) + set_nice(impl, impl->nice_level, true); } #endif - if (IS_VALID_NICE_LEVEL(impl->nice_level)) - set_nice(impl, impl->nice_level); - set_rlimit(impl); - impl->thread_utils.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_ThreadUtils, SPA_VERSION_THREAD_UTILS, @@ -978,8 +1040,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) error: #ifdef HAVE_DBUS - if (impl->system_bus) - pw_rtkit_bus_free(impl->system_bus); + if (impl->rtkit_bus) + pw_rtkit_bus_free(impl->rtkit_bus); #endif free(impl); done: diff --git a/src/pipewire/buffers.c b/src/pipewire/buffers.c index e0b74eb253ab29ae4516fa78836ed38d6d0247c4..d27b9ee245378315dc5142b36febb432d35f37f4 100644 --- a/src/pipewire/buffers.c +++ b/src/pipewire/buffers.c @@ -166,8 +166,8 @@ param_filter(struct pw_buffers *this, uint8_t ibuf[4096]; struct spa_pod_builder ib = { 0 }; struct spa_pod *oparam, *iparam; - uint32_t iidx, oidx, num = 0; - int in_res = -EIO, out_res = -EIO; + uint32_t iidx, oidx; + int in_res = -EIO, out_res = -EIO, num = 0; for (iidx = 0;;) { spa_pod_builder_init(&ib, ibuf, sizeof(ibuf)); diff --git a/src/pipewire/conf.c b/src/pipewire/conf.c index 4e6fc2ca0d7441e67cdeb77180b3481eb0143f8f..fd41a4063852dd031903bfa13771d0b60c950814 100644 --- a/src/pipewire/conf.c +++ b/src/pipewire/conf.c @@ -38,9 +38,11 @@ #ifdef HAVE_PWD_H #include <pwd.h> #endif -#ifdef __FreeBSD__ +#if defined(__FreeBSD__) || defined(__MidnightBSD__) +#ifndef O_PATH #define O_PATH 0 #endif +#endif #include <spa/utils/result.h> #include <spa/utils/string.h> @@ -190,6 +192,9 @@ static int get_config_dir(char *path, size_t size, const char *prefix, const cha return -ENOENT; } + if (pw_check_option("no-config", "true")) + goto no_config; + if ((res = get_envconf_path(path, size, prefix, name)) != 0) { if ((*level)++ == 0) return res; @@ -198,20 +203,18 @@ static int get_config_dir(char *path, size_t size, const char *prefix, const cha if (*level == 0) { (*level)++; - if ((res = get_confdata_path(path, size, prefix, name)) != 0) + if ((res = get_homeconf_path(path, size, prefix, name)) != 0) return res; } - if (pw_check_option("no-config", "true")) - return 0; - if (*level == 1) { (*level)++; if ((res = get_configdir_path(path, size, prefix, name)) != 0) return res; } if (*level == 2) { +no_config: (*level)++; - if ((res = get_homeconf_path(path, size, prefix, name)) != 0) + if ((res = get_confdata_path(path, size, prefix, name)) != 0) return res; } return 0; @@ -405,12 +408,17 @@ static int conf_load(const char *path, struct pw_properties *conf) if (fstat(fd, &sbuf) < 0) goto error_close; - if ((data = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) - goto error_close; - close(fd); - count = pw_properties_update_string(conf, data, sbuf.st_size); - munmap(data, sbuf.st_size); + if (sbuf.st_size > 0) { + if ((data = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) + goto error_close; + + count = pw_properties_update_string(conf, data, sbuf.st_size); + munmap(data, sbuf.st_size); + } else { + count = 0; + } + close(fd); pw_log_info("%p: loaded config '%s' with %d items", conf, path, count); @@ -423,13 +431,33 @@ error: return -errno; } +static bool check_override(struct pw_properties *conf, const char *name, int level) +{ + const struct spa_dict_item *it; + + spa_dict_for_each(it, &conf->dict) { + int lev, idx; + + if (!spa_streq(name, it->value)) + continue; + if (sscanf(it->key, "override.%d.%d.config.name", &lev, &idx) != 2) + continue; + if (lev < level) + return false; + } + return true; +} + static void add_override(struct pw_properties *conf, struct pw_properties *override, - const char *path, int level, int index) + const char *path, const char *name, int level, int index) { const struct spa_dict_item *it; char key[1024]; + snprintf(key, sizeof(key), "override.%d.%d.config.path", level, index); pw_properties_set(conf, key, path); + snprintf(key, sizeof(key), "override.%d.%d.config.name", level, index); + pw_properties_set(conf, key, name); spa_dict_for_each(it, &override->dict) { snprintf(key, sizeof(key), "override.%d.%d.%s", level, index, it->key); pw_properties_set(conf, key, it->value); @@ -488,10 +516,16 @@ int pw_conf_load_conf(const char *prefix, const char *name, struct pw_properties return -errno; for (i = 0; i < n; i++) { - snprintf(fname, sizeof(fname), "%s/%s", path, entries[i]->d_name); - if (conf_load(fname, override) >= 0) - add_override(conf, override, fname, level, i); - pw_properties_clear(override); + const char *name = entries[i]->d_name; + + snprintf(fname, sizeof(fname), "%s/%s", path, name); + if (check_override(conf, name, level)) { + if (conf_load(fname, override) >= 0) + add_override(conf, override, fname, name, level, i); + pw_properties_clear(override); + } else { + pw_log_info("skip override %s with lower priority", fname); + } free(entries[i]); } free(entries); @@ -891,6 +925,75 @@ static int update_props(void *user_data, const char *location, const char *key, return 0; } +static int try_load_conf(const char *conf_prefix, const char *conf_name, + struct pw_properties *conf) +{ + int res; + + if (conf_name == NULL) + return -EINVAL; + if (spa_streq(conf_name, "null")) + return 0; + if ((res = pw_conf_load_conf(conf_prefix, conf_name, conf)) < 0) { + bool skip_prefix = conf_prefix == NULL || conf_name[0] == '/'; + pw_log_warn("can't load config %s%s%s: %s", + skip_prefix ? "" : conf_prefix, + skip_prefix ? "" : "/", + conf_name, spa_strerror(res)); + } + return res; +} + +SPA_EXPORT +int pw_conf_load_conf_for_context(struct pw_properties *props, struct pw_properties *conf) +{ + const char *conf_prefix, *conf_name; + int res; + + conf_prefix = getenv("PIPEWIRE_CONFIG_PREFIX"); + if (conf_prefix == NULL) + conf_prefix = pw_properties_get(props, PW_KEY_CONFIG_PREFIX); + + conf_name = getenv("PIPEWIRE_CONFIG_NAME"); + if ((res = try_load_conf(conf_prefix, conf_name, conf)) < 0) { + conf_name = pw_properties_get(props, PW_KEY_CONFIG_NAME); + if ((res = try_load_conf(conf_prefix, conf_name, conf)) < 0) { + conf_name = "client.conf"; + if ((res = try_load_conf(conf_prefix, conf_name, conf)) < 0) { + pw_log_error("can't load default config %s: %s", + conf_name, spa_strerror(res)); + return res; + } + } + } + + conf_name = pw_properties_get(props, PW_KEY_CONFIG_OVERRIDE_NAME); + if (conf_name != NULL) { + struct pw_properties *override; + const char *path, *name; + + override = pw_properties_new(NULL, NULL); + if (override == NULL) { + res = -errno; + return res; + } + + conf_prefix = pw_properties_get(props, PW_KEY_CONFIG_OVERRIDE_PREFIX); + if ((res = try_load_conf(conf_prefix, conf_name, override)) < 0) { + pw_log_error("can't load default override config %s: %s", + conf_name, spa_strerror(res)); + pw_properties_free (override); + return res; + } + path = pw_properties_get(override, "config.path"); + name = pw_properties_get(override, "config.name"); + add_override(conf, override, path, name, 0, 1); + pw_properties_free(override); + } + + return res; +} + SPA_EXPORT int pw_context_conf_update_props(struct pw_context *context, const char *section, struct pw_properties *props) diff --git a/src/pipewire/conf.h b/src/pipewire/conf.h index 459b3b6a8583b2293135078dd0c38153c80ea27a..f5c8ba06b2937e7a26b85bfc46218ff09736bc6c 100644 --- a/src/pipewire/conf.h +++ b/src/pipewire/conf.h @@ -33,6 +33,7 @@ * \{ */ +int pw_conf_load_conf_for_context(struct pw_properties *props, struct pw_properties *conf); int pw_conf_load_conf(const char *prefix, const char *name, struct pw_properties *conf); int pw_conf_load_state(const char *prefix, const char *name, struct pw_properties *conf); int pw_conf_save_state(const char *prefix, const char *name, const struct pw_properties *conf); diff --git a/src/pipewire/context.c b/src/pipewire/context.c index a0915698bcb5ae7ab79cfecb52535c1a4b04ce02..f86179121c640b2572bed839007e21c801b8963d 100644 --- a/src/pipewire/context.c +++ b/src/pipewire/context.c @@ -101,26 +101,6 @@ static void fill_properties(struct pw_context *context) pw_properties_set(properties, PW_KEY_CORE_NAME, context->core->info.name); } -static int try_load_conf(struct pw_context *this, const char *conf_prefix, - const char *conf_name, struct pw_properties *conf) -{ - int res; - - if (conf_name == NULL) - return -EINVAL; - if (spa_streq(conf_name, "null")) - return 0; - if ((res = pw_conf_load_conf(conf_prefix, conf_name, conf)) < 0) { - bool skip_prefix = conf_prefix == NULL || conf_name[0] == '/'; - pw_log_warn("%p: can't load config %s%s%s: %s", - this, - skip_prefix ? "" : conf_prefix, - skip_prefix ? "" : "/", - conf_name, spa_strerror(res)); - } - return res; -} - static int context_set_freewheel(struct pw_context *context, bool freewheel) { struct spa_thread *thr; @@ -211,7 +191,7 @@ struct pw_context *pw_context_new(struct pw_loop *main_loop, { struct impl *impl; struct pw_context *this; - const char *lib, *str, *conf_prefix, *conf_name; + const char *lib, *str; void *dbus_iface = NULL; uint32_t n_support; struct pw_properties *pr, *conf; @@ -270,23 +250,8 @@ struct pw_context *pw_context_new(struct pw_loop *main_loop, goto error_free; } this->conf = conf; - - conf_prefix = getenv("PIPEWIRE_CONFIG_PREFIX"); - if (conf_prefix == NULL) - conf_prefix = pw_properties_get(properties, PW_KEY_CONFIG_PREFIX); - - conf_name = getenv("PIPEWIRE_CONFIG_NAME"); - if (try_load_conf(this, conf_prefix, conf_name, conf) < 0) { - conf_name = pw_properties_get(properties, PW_KEY_CONFIG_NAME); - if (try_load_conf(this, conf_prefix, conf_name, conf) < 0) { - conf_name = "client.conf"; - if ((res = try_load_conf(this, conf_prefix, conf_name, conf)) < 0) { - pw_log_error("%p: can't load config %s: %s", - this, conf_name, spa_strerror(res)); - goto error_free; - } - } - } + if ((res = pw_conf_load_conf_for_context (properties, conf)) < 0) + goto error_free; n_support = pw_get_support(this->support, SPA_N_ELEMENTS(this->support) - 6); cpu = spa_support_find(this->support, n_support, SPA_TYPE_INTERFACE_CPU); @@ -542,6 +507,12 @@ struct pw_loop *pw_context_get_main_loop(struct pw_context *context) return context->main_loop; } +SPA_EXPORT +struct pw_data_loop *pw_context_get_data_loop(struct pw_context *context) +{ + return context->data_loop_impl; +} + SPA_EXPORT struct pw_work_queue *pw_context_get_work_queue(struct pw_context *context) { @@ -621,98 +592,6 @@ struct pw_global *pw_context_find_global(struct pw_context *context, uint32_t id return global; } -/** Find a port to link with - * - * \param context a context - * \param other_port a port to find a link with - * \param id the id of a port or PW_ID_ANY - * \param props extra properties - * \param n_format_filters number of filters - * \param format_filters array of format filters - * \param[out] error an error when something is wrong - * \return a port that can be used to link to \a otherport or NULL on error - */ -struct pw_impl_port *pw_context_find_port(struct pw_context *context, - struct pw_impl_port *other_port, - uint32_t id, - struct pw_properties *props, - uint32_t n_format_filters, - struct spa_pod **format_filters, - char **error) -{ - struct pw_impl_port *best = NULL; - bool have_id; - struct pw_impl_node *n; - - have_id = id != PW_ID_ANY; - - pw_log_debug("%p: id:%u", context, id); - - spa_list_for_each(n, &context->node_list, link) { - if (n->global == NULL) - continue; - - if (other_port->node == n) - continue; - - if (!global_can_read(context, n->global)) - continue; - - pw_log_debug("%p: node id:%d", context, n->global->id); - - if (have_id) { - if (n->global->id == id) { - pw_log_debug("%p: id:%u matches node %p", context, id, n); - - best = - pw_impl_node_find_port(n, - pw_direction_reverse(other_port->direction), - PW_ID_ANY); - if (best) - break; - } - } else { - struct pw_impl_port *p, *pin, *pout; - uint8_t buf[4096]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); - struct spa_pod *dummy; - - p = pw_impl_node_find_port(n, - pw_direction_reverse(other_port->direction), - PW_ID_ANY); - if (p == NULL) - continue; - - if (p->direction == PW_DIRECTION_OUTPUT) { - pin = other_port; - pout = p; - } else { - pin = p; - pout = other_port; - } - - if (pw_context_find_format(context, - pout, - pin, - props, - n_format_filters, - format_filters, - &dummy, - &b, - error) < 0) { - free(*error); - continue; - } - best = p; - break; - } - } - if (best == NULL) { - *error = spa_aprintf("No matching Node found"); - } - return best; -} - SPA_PRINTF_FUNC(7, 8) int pw_context_debug_port_params(struct pw_context *this, struct spa_node *node, enum spa_direction direction, uint32_t port_id, uint32_t id, int err, const char *debug, ...) @@ -1113,7 +992,7 @@ static bool rates_contains(uint32_t *rates, uint32_t n_rates, uint32_t rate) * the desired final value and activate the followers and then the driver. * * A complete graph evaluation is performed for each change that is made to the - * graph, such as making/destroting links, adding/removing nodes, property changes such + * graph, such as making/destroying links, adding/removing nodes, property changes such * as quantum/rate changes or metadata changes. */ int pw_context_recalc_graph(struct pw_context *context, const char *reason) @@ -1376,6 +1255,9 @@ again: * panding change. Apply the change to the position now so * that we have the right values when we change the node * states of the driver and followers to RUNNING below */ + pw_log_debug("%p: apply duration:%"PRIu64" rate:%u/%u", context, + n->current_quantum, n->current_rate.num, + n->current_rate.denom); n->rt.position->clock.duration = n->current_quantum; n->rt.position->clock.rate = n->current_rate; n->current_pending = false; diff --git a/src/pipewire/context.h b/src/pipewire/context.h index 31292c46357b83bcf1b0178c867e277a1a6d24ed..fe658a6ce56c73d160249a16d11e6ea623129a82 100644 --- a/src/pipewire/context.h +++ b/src/pipewire/context.h @@ -136,6 +136,9 @@ const struct spa_support *pw_context_get_support(struct pw_context *context, uin /** get the context main loop */ struct pw_loop *pw_context_get_main_loop(struct pw_context *context); +/** get the context data loop. Since 0.3.56 */ +struct pw_data_loop *pw_context_get_data_loop(struct pw_context *context); + /** Get the work queue from the context: Since 0.3.26 */ struct pw_work_queue *pw_context_get_work_queue(struct pw_context *context); diff --git a/src/pipewire/data-loop.c b/src/pipewire/data-loop.c index 47ea8d3fde14cf0a2847d889ac6b315a98b86bde..4f4a2bc645a8807cc49b434a7235fc39791fb1fb 100644 --- a/src/pipewire/data-loop.c +++ b/src/pipewire/data-loop.c @@ -40,11 +40,11 @@ int pw_data_loop_wait(struct pw_data_loop *this, int timeout) int res; while (true) { - if (!this->running) { + if (SPA_UNLIKELY(!this->running)) { res = -ECANCELED; break; } - if ((res = pw_loop_iterate(this->loop, timeout)) < 0) { + if (SPA_UNLIKELY((res = pw_loop_iterate(this->loop, timeout)) < 0)) { if (res == -EINTR) continue; } @@ -77,8 +77,8 @@ static void *do_loop(void *user_data) pthread_cleanup_push(thread_cleanup, this); - while (this->running) { - if ((res = pw_loop_iterate(this->loop, -1)) < 0) { + while (SPA_LIKELY(this->running)) { + if (SPA_UNLIKELY((res = pw_loop_iterate(this->loop, -1)) < 0)) { if (res == -EINTR) continue; pw_log_error("%p: iterate error %d (%s)", @@ -274,12 +274,7 @@ int pw_data_loop_invoke(struct pw_data_loop *loop, spa_invoke_func_t func, uint32_t seq, const void *data, size_t size, bool block, void *user_data) { - int res; - if (loop->running) - res = pw_loop_invoke(loop->loop, func, seq, data, size, block, user_data); - else - res = func(loop->loop->loop, false, seq, data, size, user_data); - return res; + return pw_loop_invoke(loop->loop, func, seq, data, size, block, user_data); } /** Set a thread utils implementation. diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index 095f1eabcde99540d28cd8433ccfabcce5bd830c..26f817d25908bfba23ea8b553a285e6631d0d514 100644 --- a/src/pipewire/impl-link.c +++ b/src/pipewire/impl-link.c @@ -281,8 +281,10 @@ static int do_negotiate(struct pw_impl_link *this) /* find a common format for the ports */ if ((res = pw_context_find_format(context, output, input, NULL, 0, NULL, - &format, &b, &error)) < 0) + &format, &b, &error)) < 0) { + format = NULL; goto error; + } format = spa_pod_copy(format); spa_pod_fixate(format); @@ -1262,7 +1264,7 @@ struct pw_impl_link *pw_context_create_link(struct pw_context *context, impl->inode = input_node; } - this->rt.target.signal = impl->inode->rt.target.signal; + this->rt.target.signal_func = impl->inode->rt.target.signal_func; this->rt.target.data = impl->inode->rt.target.data; pw_log_debug("%p: constructed out:%p:%d.%d -> in:%p:%d.%d", impl, diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index e654b979d83da7a22fd0f1f787f085c85e6e1cb0..55851a68ef40cd77fda5bd2d55ffd3405a7782a5 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -1047,7 +1047,7 @@ static inline int resume_node(struct pw_impl_node *this, int status) if (pw_node_activation_state_dec(state, 1)) { a->status = PW_NODE_ACTIVATION_TRIGGERED; a->signal_time = nsec; - t->signal(t->data); + t->signal_func(t->data); } } return 0; @@ -1145,7 +1145,7 @@ static void node_on_fd_events(struct spa_source *source) this->name, this->info.id, cmd - 1); pw_log_trace_fp("%p: got process", this); - this->rt.target.signal(this->rt.target.data); + this->rt.target.signal_func(this->rt.target.data); } } @@ -1262,9 +1262,9 @@ struct pw_impl_node *pw_context_create_node(struct pw_context *context, this->rt.activation = this->activation->map->ptr; this->rt.target.activation = this->rt.activation; this->rt.target.node = this; - this->rt.target.signal = process_node; + this->rt.target.signal_func = process_node; this->rt.target.data = this; - this->rt.driver_target.signal = process_node; + this->rt.driver_target.signal_func = process_node; reset_position(this, &this->rt.activation->position); this->rt.activation->sync_timeout = DEFAULT_SYNC_TIMEOUT; @@ -1613,7 +1613,7 @@ static int node_ready(void *data, int status) state->pending, state->required); dump_states(node); } - node->rt.target.signal(node->rt.target.data); + node->rt.target.signal_func(node->rt.target.data); } if (node->current_pending) { diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c index 081651bdd43cd997351a6287c1c452089eb2fa74..8b0cb38b02f0df565faae6005a5b5d530b5ed0d0 100644 --- a/src/pipewire/impl-port.c +++ b/src/pipewire/impl-port.c @@ -209,6 +209,7 @@ SPA_EXPORT int pw_impl_port_init_mix(struct pw_impl_port *port, struct pw_impl_port_mix *mix) { uint32_t port_id; + struct pw_impl_node *node = port->node; int res = 0; port_id = pw_map_insert_new(&port->mix_port_map, mix); @@ -252,6 +253,13 @@ int pw_impl_port_init_mix(struct pw_impl_port *port, struct pw_impl_port_mix *mi port->n_mix, port->port_id, mix->port.port_id, mix->io, spa_strerror(res)); + if (port->n_mix == 1) { + pw_log_debug("%p: setting port io", port); + spa_node_port_set_io(node->node, + port->direction, port->port_id, + SPA_IO_Buffers, + &port->rt.io, sizeof(port->rt.io)); + } return res; error_remove_port: @@ -266,6 +274,7 @@ int pw_impl_port_release_mix(struct pw_impl_port *port, struct pw_impl_port_mix { int res = 0; uint32_t port_id = mix->port.port_id; + struct pw_impl_node *node = port->node; pw_map_remove(&port->mix_port_map, port_id); spa_list_remove(&mix->link); @@ -280,6 +289,13 @@ int pw_impl_port_release_mix(struct pw_impl_port *port, struct pw_impl_port_mix pw_log_debug("%p: release mix %d %d.%d", port, port->n_mix, port->port_id, mix->port.port_id); + if (port->n_mix == 0) { + pw_log_debug("%p: clearing port io", port); + spa_node_port_set_io(node->node, + port->direction, port->port_id, + SPA_IO_Buffers, + NULL, sizeof(port->rt.io)); + } return res; } @@ -1025,12 +1041,7 @@ int pw_impl_port_add(struct pw_impl_port *port, struct pw_impl_node *node) if (control) { pw_log_debug("%p: setting node control", port); } else { - pw_log_debug("%p: setting node io", port); - spa_node_port_set_io(node->node, - port->direction, port->port_id, - SPA_IO_Buffers, - &port->rt.io, sizeof(port->rt.io)); - + pw_log_debug("%p: setting mixer io", port); spa_node_port_set_io(port->mix, pw_direction_reverse(port->direction), 0, SPA_IO_Buffers, diff --git a/src/pipewire/keys.h b/src/pipewire/keys.h index 55c37b0f128d79d868b4a92c97c2f3fbd0a8a297..3d1feded42cd62e35d87c634d746d2ec8dbe3e40 100644 --- a/src/pipewire/keys.h +++ b/src/pipewire/keys.h @@ -76,6 +76,8 @@ extern "C" { /* config */ #define PW_KEY_CONFIG_PREFIX "config.prefix" /**< a config prefix directory */ #define PW_KEY_CONFIG_NAME "config.name" /**< a config file name */ +#define PW_KEY_CONFIG_OVERRIDE_PREFIX "config.override.prefix" /**< a config override prefix directory */ +#define PW_KEY_CONFIG_OVERRIDE_NAME "config.override.name" /**< a config override file name */ /* context */ #define PW_KEY_CONTEXT_PROFILE_MODULES "context.profile.modules" /**< a context profile for modules, deprecated */ @@ -170,7 +172,10 @@ extern "C" { #define PW_KEY_NODE_FORCE_RATE "node.force-rate" /**< force a rate while the node is * active */ -#define PW_KEY_NODE_DONT_RECONNECT "node.dont-reconnect" /**< don't reconnect this node */ +#define PW_KEY_NODE_DONT_RECONNECT "node.dont-reconnect" /**< don't reconnect this node. The node is + * initially linked to node.target or + * target.object or the default node. If the + * targets is removed, the node is destroyed */ #define PW_KEY_NODE_ALWAYS_PROCESS "node.always-process" /**< process even when unlinked */ #define PW_KEY_NODE_WANT_DRIVER "node.want-driver" /**< the node wants to be grouped with a driver * node in order to schedule the graph. */ @@ -249,6 +254,7 @@ extern "C" { * "isa", "pci", "usb", "firewire", * "bluetooth" */ #define PW_KEY_DEVICE_SUBSYSTEM "device.subsystem" /**< device subsystem */ +#define PW_KEY_DEVICE_SYSFS_PATH "device.sysfs.path" /**< device sysfs path */ #define PW_KEY_DEVICE_ICON "device.icon" /**< icon for the device. A base64 blob * containing PNG image data */ #define PW_KEY_DEVICE_ICON_NAME "device.icon-name" /**< an XDG icon name for the device. diff --git a/src/pipewire/mem.c b/src/pipewire/mem.c index 42ac7c104db96d03f4747cacba3a75ca0cc191bc..ae9e1e46ce59c6a12cd03c92266da8e266a857d2 100644 --- a/src/pipewire/mem.c +++ b/src/pipewire/mem.c @@ -44,7 +44,7 @@ PW_LOG_TOPIC_EXTERN(log_mem); #define PW_LOG_TOPIC_DEFAULT log_mem -#if !defined(__FreeBSD__) && !defined(HAVE_MEMFD_CREATE) +#if !defined(__FreeBSD__) && !defined(__MidnightBSD__) && !defined(HAVE_MEMFD_CREATE) /* * No glibc wrappers exist for memfd_create(2), so provide our own. * @@ -61,7 +61,7 @@ static inline int memfd_create(const char *name, unsigned int flags) #define HAVE_MEMFD_CREATE 1 #endif -#ifdef __FreeBSD__ +#if defined(__FreeBSD__) || defined(__MidnightBSD__) #define MAP_LOCKED 0 #endif @@ -485,13 +485,18 @@ struct pw_memblock * pw_mempool_alloc(struct pw_mempool *pool, enum pw_memblock_ spa_list_init(&b->memmaps); #ifdef HAVE_MEMFD_CREATE - b->this.fd = memfd_create("pipewire-memfd", MFD_CLOEXEC | MFD_ALLOW_SEALING); + char name[128]; + snprintf(name, sizeof(name), + "pipewire-memfd:flags=0x%08x,type=%" PRIu32 ",size=%zu", + (unsigned int) flags, type, size); + + b->this.fd = memfd_create(name, MFD_CLOEXEC | MFD_ALLOW_SEALING); if (b->this.fd == -1) { res = -errno; pw_log_error("%p: Failed to create memfd: %m", pool); goto error_free; } -#elif defined(__FreeBSD__) +#elif defined(__FreeBSD__) || defined(__MidnightBSD__) b->this.fd = shm_open(SHM_ANON, O_CREAT | O_RDWR | O_CLOEXEC, 0); if (b->this.fd == -1) { res = -errno; @@ -499,7 +504,11 @@ struct pw_memblock * pw_mempool_alloc(struct pw_mempool *pool, enum pw_memblock_ goto error_free; } #else - char filename[] = "/dev/shm/pipewire-tmpfile.XXXXXX"; + char filename[128]; + snprintf(filename, sizeof(filename), + "/dev/shm/pipewire-tmpfile:flags=0x%08x,type=%" PRIu32 ",size=%zu:XXXXXX", + (unsigned int) flags, type, size); + b->this.fd = mkostemp(filename, O_CLOEXEC); if (b->this.fd == -1) { res = -errno; diff --git a/src/pipewire/meson.build b/src/pipewire/meson.build index f96a022bceb320bd793bf5a32b0c8ebb3282ce2c..b19631a9261d96e2bb6737842d13bf188c71de67 100644 --- a/src/pipewire/meson.build +++ b/src/pipewire/meson.build @@ -94,7 +94,7 @@ libpipewire_c_args = [ '-DOLD_MEDIA_SESSION_WORKAROUND=1' ] -if build_machine.system() != 'freebsd' +if host_machine.system() != 'freebsd' and host_machine.system() != 'midnightbsd' libpipewire_c_args += [ '-D_POSIX_C_SOURCE' ] diff --git a/src/pipewire/pipewire.c b/src/pipewire/pipewire.c index f0edb186781397e9284d30dba27d9dce95271c44..255760c90df8ec5206905def6d341c7379044f57 100644 --- a/src/pipewire/pipewire.c +++ b/src/pipewire/pipewire.c @@ -27,7 +27,7 @@ #include <unistd.h> #include <limits.h> #include <stdio.h> -#ifndef __FreeBSD__ +#if !defined(__FreeBSD__) && !defined(__MidnightBSD__) #include <sys/prctl.h> #endif #include <pwd.h> @@ -746,7 +746,7 @@ static void init_prgname(void) static char name[PATH_MAX]; spa_memzero(name, sizeof(name)); -#if defined(__linux__) || defined(__FreeBSD_kernel__) +#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__MidnightBSD_kernel__) { if (readlink("/proc/self/exe", name, sizeof(name)-1) > 0) { prgname = strrchr(name, '/') + 1; @@ -754,7 +754,7 @@ static void init_prgname(void) } } #endif -#if defined __FreeBSD__ +#if defined __FreeBSD__ || defined(__MidnightBSD__) { ssize_t len; @@ -764,7 +764,7 @@ static void init_prgname(void) } } #endif -#ifndef __FreeBSD__ +#if !defined(__FreeBSD__) && !defined(__MidnightBSD__) { if (prctl(PR_GET_NAME, (unsigned long) name, 0, 0, 0) == 0) { prgname = name; @@ -826,8 +826,7 @@ bool pw_check_option(const char *option, const char *value) return global_support.no_color == spa_atob(value); else if (spa_streq(option, "no-config")) return global_support.no_config == spa_atob(value); - else - return false; + return false; } /** Get the client name @@ -841,15 +840,11 @@ const char *pw_get_client_name(void) const char *cc; static char cname[256]; - if ((cc = pw_get_application_name())) + if ((cc = pw_get_application_name()) || (cc = pw_get_prgname())) return cc; - else if ((cc = pw_get_prgname())) - return cc; - else { - if (snprintf(cname, sizeof(cname), "pipewire-pid-%zd", (size_t) getpid()) < 0) - return NULL; - return cname; - } + else if (snprintf(cname, sizeof(cname), "pipewire-pid-%zd", (size_t) getpid()) < 0) + return NULL; + return cname; } /** Reverse the direction */ diff --git a/src/pipewire/private.h b/src/pipewire/private.h index 52629f4a33a7d221e507c29fc90aa3d2f897801c..144e21bf1a84d7a0c3a4e9e0a44173ba8b74e1fa 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -40,7 +40,7 @@ extern "C" { #include <spa/utils/result.h> #include <spa/utils/type-info.h> -#ifdef __FreeBSD__ +#if defined(__FreeBSD__) || defined(__MidnightBSD__) struct ucred { }; #endif @@ -573,7 +573,7 @@ struct pw_node_target { struct spa_list link; struct pw_impl_node *node; struct pw_node_activation *activation; - int (*signal) (void *data); + int (*signal_func) (void *data); void *data; unsigned int active:1; }; @@ -1153,16 +1153,6 @@ int pw_context_find_format(struct pw_context *context, struct spa_pod_builder *builder, char **error); -/** Find a ports compatible with \a other_port and the format filters */ -struct pw_impl_port * -pw_context_find_port(struct pw_context *context, - struct pw_impl_port *other_port, - uint32_t id, - struct pw_properties *props, - uint32_t n_format_filters, - struct spa_pod **format_filters, - char **error); - int pw_context_debug_port_params(struct pw_context *context, struct spa_node *node, enum spa_direction direction, uint32_t port_id, uint32_t id, int err, const char *debug, ...); diff --git a/src/pipewire/settings.c b/src/pipewire/settings.c index 92d297ca336433652822bff7f8498b1d1d537cd6..c512e96585c943787457a203b6256f1025f8293f 100644 --- a/src/pipewire/settings.c +++ b/src/pipewire/settings.c @@ -41,7 +41,7 @@ #define NAME "settings" #define DEFAULT_CLOCK_RATE 48000u -#define DEFAULT_CLOCK_RATES "[ 44100 48000 ]" +#define DEFAULT_CLOCK_RATES "[ 48000 ]" #define DEFAULT_CLOCK_QUANTUM 1024u #define DEFAULT_CLOCK_MIN_QUANTUM 32u #define DEFAULT_CLOCK_MAX_QUANTUM 2048u diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 58dc80ab74c648b936bb2f813347f5a2b238f4d1..89c2794e29748bb264a7530b65282c10c0d2083e 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -313,7 +313,7 @@ static int update_params(struct stream *impl, uint32_t id, } -static inline int push_queue(struct stream *stream, struct queue *queue, struct buffer *buffer) +static inline int queue_push(struct stream *stream, struct queue *queue, struct buffer *buffer) { uint32_t index; @@ -330,7 +330,13 @@ static inline int push_queue(struct stream *stream, struct queue *queue, struct return 0; } -static inline struct buffer *pop_queue(struct stream *stream, struct queue *queue) +static inline bool queue_is_empty(struct stream *stream, struct queue *queue) +{ + uint32_t index; + return spa_ringbuffer_get_read_index(&queue->ring, &index) < 1; +} + +static inline struct buffer *queue_pop(struct stream *stream, struct queue *queue) { uint32_t index, id; struct buffer *buffer; @@ -568,7 +574,7 @@ static inline uint32_t update_requested(struct stream *impl) buffer->this.requested = impl->quantum; res = 1; } - pw_log_trace_fp("%p: update buffer:%u size:%u", impl, id, r->size); + pw_log_trace_fp("%p: update buffer:%u size:%"PRIu64, impl, id, buffer->this.requested); return res; } @@ -788,7 +794,7 @@ static void clear_buffers(struct pw_stream *stream) if (impl->direction == SPA_DIRECTION_INPUT) { struct buffer *b; - while ((b = pop_queue(impl, &impl->dequeued))) { + while ((b = queue_pop(impl, &impl->dequeued))) { if (b->busy) ATOMIC_DEC(b->busy->count); } @@ -927,7 +933,7 @@ static int impl_port_use_buffers(void *object, if (impl->direction == SPA_DIRECTION_OUTPUT) { pw_log_trace("%p: recycle buffer %d", stream, b->id); - push_queue(impl, &impl->dequeued, b); + queue_push(impl, &impl->dequeued, b); } SPA_FLAG_SET(b->flags, BUFFER_FLAG_ADDED); @@ -945,7 +951,7 @@ static int impl_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffe struct stream *d = object; pw_log_trace("%p: recycle buffer %d", d, buffer_id); if (buffer_id < d->n_buffers) - push_queue(d, &d->queued, &d->buffers[buffer_id]); + queue_push(d, &d->queued, &d->buffers[buffer_id]); return 0; } @@ -984,7 +990,7 @@ static int impl_node_process_input(void *object) if (io->status == SPA_STATUS_HAVE_DATA && (b = get_buffer(stream, io->buffer_id)) != NULL) { /* push new buffer */ - if (push_queue(impl, &impl->dequeued, b) == 0) { + if (queue_push(impl, &impl->dequeued, b) == 0) { copy_position(impl, impl->dequeued.incount); if (b->busy) ATOMIC_INC(b->busy->count); @@ -993,7 +999,7 @@ static int impl_node_process_input(void *object) } if (io->status != SPA_STATUS_NEED_DATA) { /* pop buffer to recycle */ - if ((b = pop_queue(impl, &impl->queued))) { + if ((b = queue_pop(impl, &impl->queued))) { pw_log_trace_fp("%p: recycle buffer %d", stream, b->id); } else if (io->status == -EPIPE) return io->status; @@ -1013,28 +1019,36 @@ static int impl_node_process_output(void *object) struct spa_io_buffers *io = impl->io; struct buffer *b; int res; - uint32_t index; - bool recycled; + bool ask_more; again: pw_log_trace_fp("%p: process out status:%d id:%d", stream, io->status, io->buffer_id); - recycled = false; + ask_more = false; if ((res = io->status) != SPA_STATUS_HAVE_DATA) { /* recycle old buffer */ if ((b = get_buffer(stream, io->buffer_id)) != NULL) { pw_log_trace_fp("%p: recycle buffer %d", stream, b->id); - push_queue(impl, &impl->dequeued, b); - recycled = true; + queue_push(impl, &impl->dequeued, b); } /* pop new buffer */ - if ((b = pop_queue(impl, &impl->queued)) != NULL) { + if ((b = queue_pop(impl, &impl->queued)) != NULL) { impl->drained = false; io->buffer_id = b->id; res = io->status = SPA_STATUS_HAVE_DATA; pw_log_trace_fp("%p: pop %d %p", stream, b->id, io); + /* we have a buffer, if we are not rt and don't follow + * any rate matching and there are no more + * buffers queued and there is a buffer to dequeue, ask for + * more buffers so that we have one in the next round. + * If we are using rate matching we need to wait until the + * rate matching node (audioconvert) has been scheduled to + * update the values. */ + ask_more = !impl->process_rt && impl->rate_match == NULL && + queue_is_empty(impl, &impl->queued) && + !queue_is_empty(impl, &impl->dequeued); } else if (impl->draining || impl->drained) { impl->draining = true; impl->drained = true; @@ -1045,7 +1059,12 @@ again: io->buffer_id = SPA_ID_INVALID; res = io->status = SPA_STATUS_NEED_DATA; pw_log_trace_fp("%p: no more buffers %p", stream, io); + ask_more = true; } + } else { + ask_more = !impl->process_rt && + queue_is_empty(impl, &impl->queued) && + !queue_is_empty(impl, &impl->dequeued); } copy_position(impl, impl->queued.outcount); @@ -1053,18 +1072,13 @@ again: if (!impl->draining && !impl->driving) { /* we're not draining, not a driver check if we need to get * more buffers */ - if (!impl->process_rt && (recycled || res == SPA_STATUS_NEED_DATA)) { - /* not realtime and we have a free buffer, trigger process so that we have - * data in the next round. */ - if (update_requested(impl) > 0) - call_process(impl); - } else if (res == SPA_STATUS_NEED_DATA) { - /* realtime and we don't have a buffer, trigger process and try - * again when there is something in the queue now */ + if (ask_more) { if (update_requested(impl) > 0) call_process(impl); - if (impl->draining || - spa_ringbuffer_get_read_index(&impl->queued.ring, &index) > 0) + /* realtime, we can try again now if there is something. + * non-realtime, we will have to try in the next round */ + if (impl->process_rt && + (impl->draining || !queue_is_empty(impl, &impl->queued))) goto again; } } @@ -1155,7 +1169,7 @@ static int node_event_param(void *object, int seq, struct control *c; const struct spa_pod *type, *pod; uint32_t iid, choice, n_vals, container = SPA_ID_INVALID; - float *vals, bool_range[3] = { 1.0, 0.0, 1.0 }, dbl[3]; + float *vals, bool_range[3] = { 1.0f, 0.0f, 1.0f }, dbl[3]; if (spa_pod_parse_object(param, SPA_TYPE_OBJECT_PropInfo, NULL, @@ -1182,8 +1196,6 @@ static int node_event_param(void *object, int seq, return -EINVAL; } - spa_list_append(&stream->controls, &c->link); - pod = spa_pod_get_values(type, &n_vals, &choice); c->type = SPA_POD_TYPE(pod); @@ -1204,22 +1216,28 @@ static int node_event_param(void *object, int seq, vals[0] = SPA_POD_VALUE(struct spa_pod_bool, pod); n_vals = 3; } - else + else { + free(c); return -ENOTSUP; + } c->container = container != SPA_ID_INVALID ? container : c->type; switch (choice) { case SPA_CHOICE_None: - if (n_vals < 1) + if (n_vals < 1) { + free(c); return -EINVAL; + } c->control.n_values = 1; c->control.max_values = 1; c->control.values[0] = c->control.def = c->control.min = c->control.max = vals[0]; break; case SPA_CHOICE_Range: - if (n_vals < 3) + if (n_vals < 3) { + free(c); return -EINVAL; + } c->control.n_values = 1; c->control.max_values = 1; c->control.values[0] = vals[0]; @@ -1228,10 +1246,12 @@ static int node_event_param(void *object, int seq, c->control.max = vals[2]; break; default: + free(c); return -ENOTSUP; } c->id = iid; + spa_list_append(&stream->controls, &c->link); pw_log_debug("%p: add control %d (%s) container:%d (def:%f min:%f max:%f)", stream, c->id, c->control.name, c->container, c->control.def, c->control.min, c->control.max); @@ -1241,11 +1261,9 @@ static int node_event_param(void *object, int seq, { struct spa_pod_prop *prop; struct spa_pod_object *obj = (struct spa_pod_object *) param; - union { - float f; - double d; - bool b; - } value; + float value_f; + double value_d; + bool value_b; float *values; uint32_t i, n_values; @@ -1258,24 +1276,24 @@ static int node_event_param(void *object, int seq, switch (c->container) { case SPA_TYPE_Float: - if (spa_pod_get_float(&prop->value, &value.f) < 0) + if (spa_pod_get_float(&prop->value, &value_f) < 0) continue; n_values = 1; - values = &value.f; + values = &value_f; break; case SPA_TYPE_Double: - if (spa_pod_get_double(&prop->value, &value.d) < 0) + if (spa_pod_get_double(&prop->value, &value_d) < 0) continue; n_values = 1; - value.f = value.d; - values = &value.f; + value_f = value_d; + values = &value_f; break; case SPA_TYPE_Bool: - if (spa_pod_get_bool(&prop->value, &value.b) < 0) + if (spa_pod_get_bool(&prop->value, &value_b) < 0) continue; - value.f = value.b ? 1.0 : 0.0; + value_f = value_b ? 1.0f : 0.0f; n_values = 1; - values = &value.f; + values = &value_f; break; case SPA_TYPE_Array: if ((values = spa_pod_get_array(&prop->value, &n_values)) == NULL || @@ -2223,7 +2241,7 @@ struct pw_buffer *pw_stream_dequeue_buffer(struct pw_stream *stream) struct buffer *b; int res; - if ((b = pop_queue(impl, &impl->dequeued)) == NULL) { + if ((b = queue_pop(impl, &impl->dequeued)) == NULL) { res = -errno; pw_log_trace_fp("%p: no more buffers: %m", stream); errno = -res; @@ -2234,7 +2252,7 @@ struct pw_buffer *pw_stream_dequeue_buffer(struct pw_stream *stream) if (b->busy && impl->direction == SPA_DIRECTION_OUTPUT) { if (ATOMIC_INC(b->busy->count) > 1) { ATOMIC_DEC(b->busy->count); - push_queue(impl, &impl->dequeued, b); + queue_push(impl, &impl->dequeued, b); pw_log_trace_fp("%p: buffer busy", stream); errno = EBUSY; return NULL; @@ -2254,7 +2272,7 @@ int pw_stream_queue_buffer(struct pw_stream *stream, struct pw_buffer *buffer) ATOMIC_DEC(b->busy->count); pw_log_trace_fp("%p: queue buffer %d", stream, b->id); - if ((res = push_queue(impl, &impl->queued, b)) < 0) + if ((res = queue_push(impl, &impl->queued, b)) < 0) return res; if (impl->direction == SPA_DIRECTION_OUTPUT && @@ -2275,9 +2293,9 @@ do_flush(struct spa_loop *loop, pw_log_trace_fp("%p: flush", impl); do { - b = pop_queue(impl, &impl->queued); + b = queue_pop(impl, &impl->queued); if (b != NULL) - push_queue(impl, &impl->dequeued, b); + queue_push(impl, &impl->dequeued, b); } while (b); diff --git a/src/pipewire/stream.h b/src/pipewire/stream.h index 1470404311f8e09981a5de75a79835992638a6c0..ca3aac86cdd6ca154b6d4e6b39d321f318154f18 100644 --- a/src/pipewire/stream.h +++ b/src/pipewire/stream.h @@ -161,6 +161,15 @@ extern "C" { * \section sec_stream_disconnect Disconnect * * Use \ref pw_stream_disconnect() to disconnect a stream after use. + * + * \section sec_stream_configuration Configuration + * + * \subsection ssec_config_properties Stream Properties + * + * \subsection ssec_config_rules Stream Rules + * + * \section sec_stream_environment Environment Variables + * */ /** \defgroup pw_stream Stream * @@ -427,9 +436,16 @@ int pw_stream_update_properties(struct pw_stream *stream, const struct spa_dict int pw_stream_connect(struct pw_stream *stream, /**< a \ref pw_stream */ enum pw_direction direction, /**< the stream direction */ - uint32_t target_id, /**< the target object id to connect to or - * PW_ID_ANY to let the manager - * select a target. */ + uint32_t target_id, /**< should have the value PW_ID_ANY. + * To select a specific target + * node, specify the + * PW_KEY_OBJECT_SERIAL or the + * PW_KEY_NODE_NAME value of the target + * node in the PW_KEY_TARGET_OBJECT + * property of the stream. + * Specifying target nodes by + * their id is deprecated. + */ enum pw_stream_flags flags, /**< stream flags */ const struct spa_pod **params, /**< an array with params. The params * should ideally contain supported diff --git a/src/pipewire/thread.c b/src/pipewire/thread.c index f699ebf5bbb43b26ecc573bf72074a4486a049c0..72be387bd82f96826346d45df51615d1ef183bad 100644 --- a/src/pipewire/thread.c +++ b/src/pipewire/thread.c @@ -62,9 +62,9 @@ error: return NULL; } -#ifdef __FreeBSD__ +#if defined(__FreeBSD__) || defined(__MidnightBSD__) #include <sys/param.h> -#if __FreeBSD_version < 1202000 +#if __FreeBSD_version < 1202000 || defined(__MidnightBSD__) int pthread_setname_np(pthread_t thread, const char *name) { pthread_set_name_np(thread, name); diff --git a/src/pipewire/utils.c b/src/pipewire/utils.c index a644049fd2afb2e20c1a3182f357d3531f109522..072f472777842b3e25aaee44887b763093766fcb 100644 --- a/src/pipewire/utils.c +++ b/src/pipewire/utils.c @@ -154,6 +154,7 @@ SPA_EXPORT ssize_t pw_getrandom(void *buf, size_t buflen, unsigned int flags) { ssize_t bytes; + int read_errno; #ifdef HAVE_GETRANDOM bytes = getrandom(buf, buflen, flags); @@ -165,7 +166,9 @@ ssize_t pw_getrandom(void *buf, size_t buflen, unsigned int flags) if (fd < 0) return -1; bytes = read(fd, buf, buflen); + read_errno = errno; close(fd); + errno = read_errno; return bytes; } diff --git a/src/pipewire/utils.h b/src/pipewire/utils.h index b320db22f97fafd0ae17d02737e155c06b811c44..c04d6ea136fa851edeedef68e2226ede375173dd 100644 --- a/src/pipewire/utils.h +++ b/src/pipewire/utils.h @@ -86,6 +86,7 @@ pw_strip(char *str, const char *whitespace); }) #endif +SPA_WARN_UNUSED_RESULT ssize_t pw_getrandom(void *buf, size_t buflen, unsigned int flags); void* pw_reallocarray(void *ptr, size_t nmemb, size_t size); diff --git a/src/tests/meson.build b/src/tests/meson.build index 3e03a4c7561413e3b96fbe2fdf79cfe23c52fd87..f7c54f2b76a4f836c8a388d700eb2247f0fe280f 100644 --- a/src/tests/meson.build +++ b/src/tests/meson.build @@ -3,6 +3,7 @@ test_apps = [ 'test-interfaces', # 'test-remote', 'test-stream', + 'test-filter', ] foreach a : test_apps diff --git a/src/tests/test-filter.c b/src/tests/test-filter.c new file mode 100644 index 0000000000000000000000000000000000000000..93f00377e78cde05aac0ae7cfe2ec17f2cd88f7b --- /dev/null +++ b/src/tests/test-filter.c @@ -0,0 +1,375 @@ +/* PipeWire + * + * Copyright © 2022 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <pipewire/pipewire.h> +#include <pipewire/main-loop.h> +#include <pipewire/filter.h> + +#include <spa/utils/string.h> + +#define TEST_FUNC(a,b,func) \ +do { \ + a.func = b.func; \ + spa_assert_se(SPA_PTRDIFF(&a.func, &a) == SPA_PTRDIFF(&b.func, &b)); \ +} while(0) + +static void test_abi(void) +{ + static const struct { + uint32_t version; + void (*destroy) (void *data); + void (*state_changed) (void *data, enum pw_filter_state old, + enum pw_filter_state state, const char *error); + void (*io_changed) (void *data, void *port_data, uint32_t id, void *area, uint32_t size); + void (*param_changed) (void *data, void *port_data, uint32_t id, const struct spa_pod *param); + void (*add_buffer) (void *data, void *port_data, struct pw_buffer *buffer); + void (*remove_buffer) (void *data, void *port_data, struct pw_buffer *buffer); + void (*process) (void *data, struct spa_io_position *position); + void (*drained) (void *data); + void (*command) (void *data, const struct spa_command *command); + } test = { PW_VERSION_FILTER_EVENTS, NULL }; + + struct pw_filter_events ev; + + TEST_FUNC(ev, test, destroy); + TEST_FUNC(ev, test, state_changed); + TEST_FUNC(ev, test, io_changed); + TEST_FUNC(ev, test, param_changed); + TEST_FUNC(ev, test, add_buffer); + TEST_FUNC(ev, test, remove_buffer); + TEST_FUNC(ev, test, process); + TEST_FUNC(ev, test, drained); + TEST_FUNC(ev, test, command); + + spa_assert_se(PW_VERSION_FILTER_EVENTS == 1); + spa_assert_se(sizeof(ev) == sizeof(test)); + + spa_assert_se(PW_FILTER_STATE_ERROR == -1); + spa_assert_se(PW_FILTER_STATE_UNCONNECTED == 0); + spa_assert_se(PW_FILTER_STATE_CONNECTING == 1); + spa_assert_se(PW_FILTER_STATE_PAUSED == 2); + spa_assert_se(PW_FILTER_STATE_STREAMING == 3); + + spa_assert_se(pw_filter_state_as_string(PW_FILTER_STATE_ERROR) != NULL); + spa_assert_se(pw_filter_state_as_string(PW_FILTER_STATE_UNCONNECTED) != NULL); + spa_assert_se(pw_filter_state_as_string(PW_FILTER_STATE_CONNECTING) != NULL); + spa_assert_se(pw_filter_state_as_string(PW_FILTER_STATE_PAUSED) != NULL); + spa_assert_se(pw_filter_state_as_string(PW_FILTER_STATE_STREAMING) != NULL); +} + +static void filter_destroy_error(void *data) +{ + spa_assert_not_reached(); +} +static void filter_state_changed_error(void *data, enum pw_filter_state old, + enum pw_filter_state state, const char *error) +{ + spa_assert_not_reached(); +} +static void filter_io_changed_error(void *data, void *port_data, uint32_t id, void *area, uint32_t size) +{ + spa_assert_not_reached(); +} +static void filter_param_changed_error(void *data, void *port_data, uint32_t id, const struct spa_pod *format) +{ + spa_assert_not_reached(); +} +static void filter_add_buffer_error(void *data, void *port_data, struct pw_buffer *buffer) +{ + spa_assert_not_reached(); +} +static void filter_remove_buffer_error(void *data, void *port_data, struct pw_buffer *buffer) +{ + spa_assert_not_reached(); +} +static void filter_process_error(void *data, struct spa_io_position *position) +{ + spa_assert_not_reached(); +} +static void filter_drained_error(void *data) +{ + spa_assert_not_reached(); +} + +static const struct pw_filter_events filter_events_error = +{ + PW_VERSION_FILTER_EVENTS, + .destroy = filter_destroy_error, + .state_changed = filter_state_changed_error, + .io_changed = filter_io_changed_error, + .param_changed = filter_param_changed_error, + .add_buffer = filter_add_buffer_error, + .remove_buffer = filter_remove_buffer_error, + .process = filter_process_error, + .drained = filter_drained_error +}; + +static int destroy_count = 0; +static void filter_destroy_count(void *data) +{ + destroy_count++; +} +static void test_create(void) +{ + struct pw_main_loop *loop; + struct pw_context *context; + struct pw_core *core; + struct pw_filter *filter; + struct pw_filter_events filter_events = filter_events_error; + struct spa_hook listener = { 0, }; + const char *error = NULL; + + loop = pw_main_loop_new(NULL); + context = pw_context_new(pw_main_loop_get_loop(loop), NULL, 12); + spa_assert_se(context != NULL); + core = pw_context_connect_self(context, NULL, 0); + spa_assert_se(core != NULL); + filter = pw_filter_new(core, "test", NULL); + spa_assert_se(filter != NULL); + pw_filter_add_listener(filter, &listener, &filter_events, filter); + + /* check state */ + spa_assert_se(pw_filter_get_state(filter, &error) == PW_FILTER_STATE_UNCONNECTED); + spa_assert_se(error == NULL); + /* check name */ + spa_assert_se(spa_streq(pw_filter_get_name(filter), "test")); + + /* check id, only when connected */ + spa_assert_se(pw_filter_get_node_id(filter) == SPA_ID_INVALID); + + /* check destroy */ + destroy_count = 0; + filter_events.destroy = filter_destroy_count; + pw_filter_destroy(filter); + spa_assert_se(destroy_count == 1); + + pw_context_destroy(context); + pw_main_loop_destroy(loop); +} + +static void test_properties(void) +{ + struct pw_main_loop *loop; + struct pw_context *context; + struct pw_core *core; + const struct pw_properties *props; + struct pw_filter *filter; + struct pw_filter_events filter_events = filter_events_error; + struct spa_hook listener = { { NULL }, }; + struct spa_dict_item items[3]; + + loop = pw_main_loop_new(NULL); + context = pw_context_new(pw_main_loop_get_loop(loop), NULL, 12); + spa_assert_se(context != NULL); + core = pw_context_connect_self(context, NULL, 0); + spa_assert_se(core != NULL); + filter = pw_filter_new(core, "test", + pw_properties_new("foo", "bar", + "biz", "fuzz", + NULL)); + spa_assert_se(filter != NULL); + pw_filter_add_listener(filter, &listener, &filter_events, filter); + + props = pw_filter_get_properties(filter, NULL); + spa_assert_se(props != NULL); + spa_assert_se(spa_streq(pw_properties_get(props, "foo"), "bar")); + spa_assert_se(spa_streq(pw_properties_get(props, "biz"), "fuzz")); + spa_assert_se(pw_properties_get(props, "buzz") == NULL); + + /* remove foo */ + items[0] = SPA_DICT_ITEM_INIT("foo", NULL); + /* change biz */ + items[1] = SPA_DICT_ITEM_INIT("biz", "buzz"); + /* add buzz */ + items[2] = SPA_DICT_ITEM_INIT("buzz", "frizz"); + pw_filter_update_properties(filter, NULL, &SPA_DICT_INIT(items, 3)); + + spa_assert_se(props == pw_filter_get_properties(filter, NULL)); + spa_assert_se(pw_properties_get(props, "foo") == NULL); + spa_assert_se(spa_streq(pw_properties_get(props, "biz"), "buzz")); + spa_assert_se(spa_streq(pw_properties_get(props, "buzz"), "frizz")); + + /* check destroy */ + destroy_count = 0; + filter_events.destroy = filter_destroy_count; + pw_context_destroy(context); + spa_assert_se(destroy_count == 1); + + pw_main_loop_destroy(loop); +} + +struct roundtrip_data +{ + struct pw_main_loop *loop; + int pending; + int done; +}; + +static void core_event_done(void *object, uint32_t id, int seq) +{ + struct roundtrip_data *data = object; + if (id == PW_ID_CORE && seq == data->pending) { + data->done = 1; + printf("done %d\n", seq); + pw_main_loop_quit(data->loop); + } +} + +static int roundtrip(struct pw_core *core, struct pw_main_loop *loop) +{ + struct spa_hook core_listener; + struct roundtrip_data data = { .loop = loop }; + const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .done = core_event_done, + }; + spa_zero(core_listener); + pw_core_add_listener(core, &core_listener, + &core_events, &data); + + data.pending = pw_core_sync(core, PW_ID_CORE, 0); + printf("sync %d\n", data.pending); + + while (!data.done) { + pw_main_loop_run(loop); + } + spa_hook_remove(&core_listener); + return 0; +} + +static int node_count = 0; +static int port_count = 0; +static void registry_event_global(void *data, uint32_t id, + uint32_t permissions, const char *type, uint32_t version, + const struct spa_dict *props) +{ + printf("object: id:%u type:%s/%d\n", id, type, version); + if (spa_streq(type, PW_TYPE_INTERFACE_Port)) + port_count++; + else if (spa_streq(type, PW_TYPE_INTERFACE_Node)) + node_count++; + +} +static void registry_event_global_remove(void *data, uint32_t id) +{ + printf("object: id:%u\n", id); +} + +struct port { + struct pw_filter *filter; +}; + +static void test_create_port(void) +{ + struct pw_main_loop *loop; + struct pw_context *context; + struct pw_core *core; + struct pw_registry *registry; + struct pw_filter *filter; + struct spa_hook registry_listener = { 0, }; + static const struct pw_registry_events registry_events = { + PW_VERSION_REGISTRY_EVENTS, + .global = registry_event_global, + .global_remove = registry_event_global_remove, + }; + int res; + struct port *port; + enum pw_filter_state state; + + loop = pw_main_loop_new(NULL); + context = pw_context_new(pw_main_loop_get_loop(loop), NULL, 12); + spa_assert_se(context != NULL); + core = pw_context_connect_self(context, NULL, 0); + spa_assert_se(core != NULL); + filter = pw_filter_new(core, "test", NULL); + spa_assert_se(filter != NULL); + + registry = pw_core_get_registry(core, PW_VERSION_REGISTRY, 0); + spa_assert_se(registry != NULL); + pw_registry_add_listener(registry, ®istry_listener, + ®istry_events, NULL); + + state = pw_filter_get_state(filter, NULL); + printf("state %s\n", pw_filter_state_as_string(state)); + res = pw_filter_connect(filter, PW_FILTER_FLAG_RT_PROCESS, NULL, 0); + spa_assert_se(res >= 0); + + printf("wait connect\n"); + while (true) { + state = pw_filter_get_state(filter, NULL); + printf("state %s\n", pw_filter_state_as_string(state)); + spa_assert_se(state != PW_FILTER_STATE_ERROR); + + if (state == PW_FILTER_STATE_PAUSED) + break; + + roundtrip(core, loop); + } + spa_assert_se(node_count == 1); + + printf("add port\n"); + /* make an audio DSP output port */ + port = pw_filter_add_port(filter, + PW_DIRECTION_OUTPUT, + PW_FILTER_PORT_FLAG_MAP_BUFFERS, + sizeof(struct port), + pw_properties_new( + PW_KEY_FORMAT_DSP, "32 bit float mono audio", + PW_KEY_PORT_NAME, "output", + NULL), + NULL, 0); + + printf("wait port\n"); + roundtrip(core, loop); + + spa_assert_se(port_count == 1); + printf("port added\n"); + + printf("remove port\n"); + pw_filter_remove_port(port); + roundtrip(core, loop); + + printf("destroy\n"); + /* check destroy */ + pw_filter_destroy(filter); + + pw_proxy_destroy((struct pw_proxy*)registry); + + pw_context_destroy(context); + pw_main_loop_destroy(loop); +} + +int main(int argc, char *argv[]) +{ + pw_init(&argc, &argv); + + test_abi(); + test_create(); + test_properties(); + test_create_port(); + + pw_deinit(); + + return 0; +} diff --git a/src/tests/test-stream.c b/src/tests/test-stream.c index 3c4b722684718275458fc747bf5f3bd039b6a1fb..1e54fed9045d630eab03f1ab588b52b3736f1f9b 100644 --- a/src/tests/test-stream.c +++ b/src/tests/test-stream.c @@ -251,5 +251,7 @@ int main(int argc, char *argv[]) test_create(); test_properties(); + pw_deinit(); + return 0; } diff --git a/src/tools/dsffile.c b/src/tools/dsffile.c index a16f38447443c5d8956da200603419011b421da3..9c6ce0c3a670f7aa9f8942464d96b0c965d4d977 100644 --- a/src/tools/dsffile.c +++ b/src/tools/dsffile.c @@ -216,13 +216,15 @@ dsf_file_read(struct dsf_file *f, void *data, size_t samples, const struct dsf_l uint8_t *d = data; int step = SPA_ABS(layout->interleave); bool rev = layout->lsb != f->info.lsb; - size_t total, block, offset, pos; + size_t total, block, offset, pos, scale; block = f->offset / f->info.blocksize; offset = block * f->info.blocksize * f->info.channels; pos = f->offset % f->info.blocksize; + scale = SPA_CLAMP(f->info.rate / (44100u * 64u), 1u, 4u); samples *= step; + samples *= scale; for (total = 0; total < samples && offset + pos < f->info.length; total++) { const uint8_t *s = f->p + offset + pos; diff --git a/src/tools/meson.build b/src/tools/meson.build index 02514c253168946afa983a978d3f64deefd4a1df..9f058da9762573060ca83043808211e6c7f78366 100644 --- a/src/tools/meson.build +++ b/src/tools/meson.build @@ -17,13 +17,11 @@ foreach t : tools_sources ) endforeach -if readline_dep.found() - executable('pw-cli', - 'pw-cli.c', - install: true, - dependencies: [pipewire_dep, readline_dep] - ) -endif +executable('pw-cli', + 'pw-cli.c', + install: true, + dependencies: [pipewire_dep, readline_dep] +) if ncurses_dep.found() executable('pw-top', diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index e9082e5aab460460806d698de131272352af8ad3..ea502d6473069ec02241c154c557ec1fc02d5186 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -126,7 +126,6 @@ struct data { unsigned int rate; int channels; struct channelmap channelmap; - unsigned int samplesize; unsigned int stride; enum unit latency_unit; unsigned int latency_value; @@ -154,125 +153,42 @@ struct data { } dsf; }; -static inline int -sf_str_to_fmt(const char *str) -{ - if (!str) - return -1; - - if (spa_streq(str, "s8")) - return SF_FORMAT_PCM_S8; - if (spa_streq(str, "u8")) - return SF_FORMAT_PCM_U8; - if (spa_streq(str, "s16")) - return SF_FORMAT_PCM_16; - if (spa_streq(str, "s24")) - return SF_FORMAT_PCM_24; - if (spa_streq(str, "s32")) - return SF_FORMAT_PCM_32; - if (spa_streq(str, "f32")) - return SF_FORMAT_FLOAT; - if (spa_streq(str, "f64")) - return SF_FORMAT_DOUBLE; - - return -1; -} - -static inline const char * -sf_fmt_to_str(int format) -{ - int sub_type = (format & SF_FORMAT_SUBMASK); - - if (sub_type == SF_FORMAT_PCM_U8) - return "u8"; - if (sub_type == SF_FORMAT_PCM_S8) - return "s8"; - if (sub_type == SF_FORMAT_PCM_16) - return "s16"; - if (sub_type == SF_FORMAT_PCM_24) - return "s24"; - if (sub_type == SF_FORMAT_PCM_32) - return "s32"; - if (sub_type == SF_FORMAT_FLOAT) - return "f32"; - if (sub_type == SF_FORMAT_DOUBLE) - return "f64"; - return "(invalid)"; -} +#define STR_FMTS "(ulaw|alaw|u8|s8|s16|s32|f32|f64)" -#define STR_FMTS "(u8|s8|s16|s32|f32|f64)" - -/* 0 = native, 1 = le, 2 = be */ -static inline int -sf_format_endianess(int format) -{ - return 0; /* native */ -} +static const struct format_info { + const char *name; + int sf_format; + uint32_t spa_format; + uint32_t width; +} format_info[] = { + { "ulaw", SF_FORMAT_ULAW, SPA_AUDIO_FORMAT_ULAW, 1 }, + { "alaw", SF_FORMAT_ULAW, SPA_AUDIO_FORMAT_ALAW, 1 }, + { "s8", SF_FORMAT_PCM_S8, SPA_AUDIO_FORMAT_S8, 1 }, + { "u8", SF_FORMAT_PCM_U8, SPA_AUDIO_FORMAT_U8, 1 }, + { "s16", SF_FORMAT_PCM_16, SPA_AUDIO_FORMAT_S16, 2 }, + { "s24", SF_FORMAT_PCM_24, SPA_AUDIO_FORMAT_S24, 3 }, + { "s32", SF_FORMAT_PCM_32, SPA_AUDIO_FORMAT_S32, 4 }, + { "f32", SF_FORMAT_FLOAT, SPA_AUDIO_FORMAT_F32, 4 }, + { "f64", SF_FORMAT_DOUBLE, SPA_AUDIO_FORMAT_F32, 8 }, +}; -static inline enum spa_audio_format -sf_format_to_pw(int format) +static const struct format_info *format_info_by_name(const char *str) { - int endianness; - - endianness = sf_format_endianess(format); - if (endianness < 0) - return SPA_AUDIO_FORMAT_UNKNOWN; - - switch (format & SF_FORMAT_SUBMASK) { - case SF_FORMAT_PCM_U8: - return SPA_AUDIO_FORMAT_U8; - case SF_FORMAT_PCM_S8: - return SPA_AUDIO_FORMAT_S8; - case SF_FORMAT_ULAW: - return SPA_AUDIO_FORMAT_ULAW; - case SF_FORMAT_ALAW: - return SPA_AUDIO_FORMAT_ALAW; - case SF_FORMAT_PCM_16: - return endianness == 1 ? SPA_AUDIO_FORMAT_S16_LE : - endianness == 2 ? SPA_AUDIO_FORMAT_S16_BE : - SPA_AUDIO_FORMAT_S16; - case SF_FORMAT_PCM_24: - case SF_FORMAT_PCM_32: - return endianness == 1 ? SPA_AUDIO_FORMAT_S32_LE : - endianness == 2 ? SPA_AUDIO_FORMAT_S32_BE : - SPA_AUDIO_FORMAT_S32; - case SF_FORMAT_DOUBLE: - return endianness == 1 ? SPA_AUDIO_FORMAT_F64_LE : - endianness == 2 ? SPA_AUDIO_FORMAT_F64_BE : - SPA_AUDIO_FORMAT_F64; - case SF_FORMAT_FLOAT: - default: - return endianness == 1 ? SPA_AUDIO_FORMAT_F32_LE : - endianness == 2 ? SPA_AUDIO_FORMAT_F32_BE : - SPA_AUDIO_FORMAT_F32; - break; - } - - return SPA_AUDIO_FORMAT_UNKNOWN; + uint32_t i; + for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) + if (spa_streq(str, format_info[i].name)) + return &format_info[i]; + return NULL; } -static inline int -sf_format_samplesize(int format) +static const struct format_info *format_info_by_sf_format(int format) { + uint32_t i; int sub_type = (format & SF_FORMAT_SUBMASK); - - switch (sub_type) { - case SF_FORMAT_PCM_S8: - case SF_FORMAT_PCM_U8: - case SF_FORMAT_ULAW: - case SF_FORMAT_ALAW: - return 1; - case SF_FORMAT_PCM_16: - return 2; - case SF_FORMAT_PCM_32: - return 4; - case SF_FORMAT_DOUBLE: - return 8; - case SF_FORMAT_FLOAT: - default: - return 4; - } - return -1; + for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) + if (format_info[i].sf_format == sub_type) + return &format_info[i]; + return NULL; } static int sf_playback_fill_x8(struct data *d, void *dest, unsigned int n_frames) @@ -280,7 +196,7 @@ static int sf_playback_fill_x8(struct data *d, void *dest, unsigned int n_frames sf_count_t rn; rn = sf_read_raw(d->file, dest, n_frames * d->stride); - return (int)rn; + return (int)rn / d->stride; } static int sf_playback_fill_s16(struct data *d, void *dest, unsigned int n_frames) @@ -320,10 +236,8 @@ static int sf_playback_fill_f64(struct data *d, void *dest, unsigned int n_frame } static inline fill_fn -sf_fmt_playback_fill_fn(int format) +playback_fill_fn(uint32_t fmt) { - enum spa_audio_format fmt = sf_format_to_pw(format); - switch (fmt) { case SPA_AUDIO_FORMAT_S8: case SPA_AUDIO_FORMAT_U8: @@ -364,7 +278,7 @@ static int sf_record_fill_x8(struct data *d, void *src, unsigned int n_frames) sf_count_t rn; rn = sf_write_raw(d->file, src, n_frames * d->stride); - return (int)rn; + return (int)rn / d->stride; } static int sf_record_fill_s16(struct data *d, void *src, unsigned int n_frames) @@ -404,10 +318,8 @@ static int sf_record_fill_f64(struct data *d, void *src, unsigned int n_frames) } static inline fill_fn -sf_fmt_record_fill_fn(int format) +record_fill_fn(uint32_t fmt) { - enum spa_audio_format fmt = sf_format_to_pw(format); - switch (fmt) { case SPA_AUDIO_FORMAT_S8: case SPA_AUDIO_FORMAT_U8: @@ -1076,29 +988,6 @@ static int setup_dsffile(struct data *data) return 0; } -struct format_info { - const char *name; - uint32_t spa_format; - uint32_t width; -} format_info[] = { - { "s8", SPA_AUDIO_FORMAT_S8, 1 }, - { "u8", SPA_AUDIO_FORMAT_U8, 1 }, - { "s16", SPA_AUDIO_FORMAT_S16, 2 }, - { "s24", SPA_AUDIO_FORMAT_S24, 3 }, - { "s32", SPA_AUDIO_FORMAT_S32, 4 }, - { "f32", SPA_AUDIO_FORMAT_F32, 4 }, - { "f64", SPA_AUDIO_FORMAT_F32, 8 }, -}; - -static struct format_info *format_info_by_name(const char *str) -{ - uint32_t i; - for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) - if (spa_streq(str, format_info[i].name)) - return &format_info[i]; - return NULL; -} - static int stdout_record(struct data *d, void *src, unsigned int n_frames) { return fwrite(src, d->stride, n_frames, stdout); @@ -1111,7 +1000,7 @@ static int stdin_play(struct data *d, void *src, unsigned int n_frames) static int setup_pipe(struct data *data) { - struct format_info *info; + const struct format_info *info; if (data->format == NULL) data->format = DEFAULT_FORMAT; @@ -1129,6 +1018,12 @@ static int setup_pipe(struct data *data) data->spa_format = info->spa_format; data->stride = info->width * data->channels; data->fill = data->mode == mode_playback ? stdin_play : stdout_record; + + if (data->verbose) + printf("PIPE: rate=%u channels=%u fmt=%s samplesize=%u stride=%u\n", + data->rate, data->channels, + info->name, info->width, data->stride); + return 0; } @@ -1221,9 +1116,8 @@ static void format_from_filename(SF_INFO *info, const char *filename) static int setup_sndfile(struct data *data) { + const struct format_info *fi = NULL; SF_INFO info; - const char *s; - unsigned int nom = 0; spa_zero(info); /* for record, you fill in the info first */ @@ -1237,14 +1131,14 @@ static int setup_sndfile(struct data *data) if (data->channelmap.n_channels == 0) channelmap_default(&data->channelmap, data->channels); - memset(&info, 0, sizeof(info)); - info.samplerate = data->rate; - info.channels = data->channels; - info.format = sf_str_to_fmt(data->format); - if (info.format == -1) { + if ((fi = format_info_by_name(data->format)) == NULL) { fprintf(stderr, "error: unknown format \"%s\"\n", data->format); return -EINVAL; } + memset(&info, 0, sizeof(info)); + info.samplerate = data->rate; + info.channels = data->channels; + info.format = fi->sf_format; format_from_filename(&info, data->filename); } @@ -1291,13 +1185,46 @@ static int setup_sndfile(struct data *data) } } fill_properties(data); + + /* try native format first, else decode to float */ + if ((fi = format_info_by_sf_format(info.format)) == NULL) + fi = format_info_by_sf_format(SF_FORMAT_FLOAT); + } - data->samplesize = sf_format_samplesize(info.format); - data->stride = data->samplesize * data->channels; - data->spa_format = sf_format_to_pw(info.format); + if (fi == NULL) + return -EIO; + + if (data->verbose) + printf("PCM: fmt:%s rate:%u channels:%u width:%u\n", + fi->name, data->rate, data->channels, fi->width); + + /* we read and write S24 as S32 with sndfile */ + if (fi->spa_format == SPA_AUDIO_FORMAT_S24) + fi = format_info_by_sf_format(SF_FORMAT_PCM_32); + + data->spa_format = fi->spa_format; + data->stride = fi->width * data->channels; data->fill = data->mode == mode_playback ? - sf_fmt_playback_fill_fn(info.format) : - sf_fmt_record_fill_fn(info.format); + playback_fill_fn(data->spa_format) : + record_fill_fn(data->spa_format); + + if (data->fill == NULL) { + fprintf(stderr, "PCM: unhandled format %d\n", data->spa_format); + return -EINVAL; + } + return 0; +} + +static int setup_properties(struct data *data) +{ + const char *s; + unsigned int nom = 0; + + if (data->quality >= 0) + pw_properties_setf(data->props, "resample.quality", "%d", data->quality); + + if (data->rate) + pw_properties_setf(data->props, PW_KEY_NODE_RATE, "1/%u", data->rate); data->latency_unit = unit_none; @@ -1348,20 +1275,11 @@ static int setup_sndfile(struct data *data) } if (data->verbose) - printf("PCM: rate=%u channels=%u fmt=%s samplesize=%u stride=%u latency=%u (%.3fs)\n", - data->rate, data->channels, - sf_fmt_to_str(info.format), - data->samplesize, - data->stride, nom, (double)nom/data->rate); - + printf("rate:%d latency:%u (%.3fs)\n", + data->rate, nom, data->rate ? (double)nom/data->rate : 0.0f); if (nom) pw_properties_setf(data->props, PW_KEY_NODE_LATENCY, "%u/%u", nom, data->rate); - pw_properties_setf(data->props, PW_KEY_NODE_RATE, "1/%u", data->rate); - - if (data->quality >= 0) - pw_properties_setf(data->props, "resample.quality", "%d", data->quality); - return 0; } @@ -1521,9 +1439,7 @@ int main(int argc, char *argv[]) case OPT_VOLUME: data.volume = atof(optarg); break; - default: - fprintf(stderr, "error: unknown option '%c'\n", c); goto error_usage; } } @@ -1634,7 +1550,6 @@ int main(int argc, char *argv[]) break; } } - if (ret < 0) { fprintf(stderr, "error: open failed: %s\n", spa_strerror(ret)); switch (ret) { @@ -1645,6 +1560,8 @@ int main(int argc, char *argv[]) goto error_usage; } } + ret = setup_properties(&data); + switch (data.data_type) { case TYPE_PCM: { @@ -1746,6 +1663,7 @@ error_connect_fail: pw_stream_destroy(data.stream); } error_no_stream: +error_bad_file: spa_hook_remove(&data.core_listener); pw_core_disconnect(data.core); error_ctx_connect_failed: @@ -1754,7 +1672,6 @@ error_no_context: pw_main_loop_destroy(data.loop); error_no_props: error_no_main_loop: -error_bad_file: pw_properties_free(data.props); if (data.file) sf_close(data.file); diff --git a/src/tools/pw-cli.c b/src/tools/pw-cli.c index 9ac59e282390425da3e079cfe91cb8dd891a4f1e..4d6655b062b37249d9951aed04db8a27973db6d8 100644 --- a/src/tools/pw-cli.c +++ b/src/tools/pw-cli.c @@ -22,19 +22,23 @@ * DEALINGS IN THE SOFTWARE. */ +#include "config.h" + #include <unistd.h> #include <errno.h> #include <stdio.h> #include <signal.h> #include <string.h> #include <ctype.h> -#ifndef __FreeBSD__ +#if !defined(__FreeBSD__) && !defined(__MidnightBSD__) #include <alloca.h> #endif #include <getopt.h> #include <fnmatch.h> +#ifdef HAVE_READLINE #include <readline/readline.h> #include <readline/history.h> +#endif #include <locale.h> #if !defined(FNM_EXTMATCH) @@ -222,7 +226,6 @@ static bool do_set_param(struct data *data, const char *cmd, char *args, char ** static bool do_permissions(struct data *data, const char *cmd, char *args, char **error); static bool do_get_permissions(struct data *data, const char *cmd, char *args, char **error); static bool do_send_command(struct data *data, const char *cmd, char *args, char **error); -static bool do_dump(struct data *data, const char *cmd, char *args, char **error); static bool do_quit(struct data *data, const char *cmd, char *args, char **error); #define DUMP_NAMES "Core|Module|Device|Node|Port|Factory|Client|Link|Session|Endpoint|EndpointStream" @@ -247,8 +250,6 @@ static const struct command command_list[] = { { "permissions", "sp", "Set permissions for a client <client-id> <object> <permission>", do_permissions }, { "get-permissions", "gp", "Get permissions of a client <client-id>", do_get_permissions }, { "send-command", "c", "Send a command <object-id>", do_send_command }, - { "dump", "D", "Dump objects in ways that are cleaner for humans to understand " - "[short|deep|resolve|notype] [-sdrt] [all|"DUMP_NAMES"|<id>]", do_dump }, { "quit", "q", "Quit", do_quit }, }; @@ -265,7 +266,10 @@ static bool do_help(struct data *data, const char *cmd, char *args, char **error printf("Available commands:\n"); for (i = 0; i < SPA_N_ELEMENTS(command_list); i++) { - printf("\t%-20.20s\t%s\n", command_list[i].name, command_list[i].description); + char cmd[256]; + snprintf(cmd, sizeof(cmd), "%s | %s", + command_list[i].name, command_list[i].alias); + printf("\t%-20.20s\t%s\n", cmd, command_list[i].description); } return true; } @@ -307,7 +311,12 @@ static void on_core_info(void *_data, const struct pw_core_info *info) static void set_prompt(struct remote_data *rd) { snprintf(prompt, sizeof(prompt), "%s>> ", rd->name); +#ifdef HAVE_READLINE rl_set_prompt(prompt); +#else + printf("%s", prompt); + fflush(stdout); +#endif } static void on_core_done(void *_data, uint32_t id, int seq) @@ -1920,20 +1929,6 @@ static bool do_send_command(struct data *data, const char *cmd, char *args, char return true; } -static const char * -pw_interface_short(const char *type) -{ - size_t ilen; - - ilen = strlen(PW_TYPE_INFO_INTERFACE_BASE); - - if (!type || strlen(type) <= ilen || - memcmp(type, PW_TYPE_INFO_INTERFACE_BASE, ilen)) - return NULL; - - return type + ilen; -} - static struct global * obj_global(struct remote_data *rd, uint32_t id) { @@ -1992,20 +1987,6 @@ global_props(struct global *global) return NULL; } -static struct spa_dict * -obj_props(struct remote_data *rd, uint32_t id) -{ - struct global *global; - - if (!rd) - return NULL; - - global = obj_global(rd, id); - if (!global) - return NULL; - return global_props(global); -} - static const char * global_lookup(struct global *global, const char *key) { @@ -2017,16 +1998,6 @@ global_lookup(struct global *global, const char *key) return spa_dict_lookup(dict, key); } -static const char * -obj_lookup(struct remote_data *rd, uint32_t id, const char *key) -{ - struct spa_dict *dict; - - dict = obj_props(rd, id); - if (!dict) - return NULL; - return spa_dict_lookup(dict, key); -} static int children_of(struct remote_data *rd, uint32_t parent_id, @@ -2126,67 +2097,6 @@ children_of(struct remote_data *rd, uint32_t parent_id, return count; } -#ifndef BIT -#define BIT(x) (1U << (x)) -#endif - -enum dump_flags { - is_default = 0, - is_short = BIT(0), - is_deep = BIT(1), - is_resolve = BIT(2), - is_notype = BIT(3) -}; - -static const char * const dump_types[] = { - PW_TYPE_INTERFACE_Core, - PW_TYPE_INTERFACE_Module, - PW_TYPE_INTERFACE_Device, - PW_TYPE_INTERFACE_Node, - PW_TYPE_INTERFACE_Port, - PW_TYPE_INTERFACE_Factory, - PW_TYPE_INTERFACE_Client, - PW_TYPE_INTERFACE_Link, - PW_TYPE_INTERFACE_Session, - PW_TYPE_INTERFACE_Endpoint, - PW_TYPE_INTERFACE_EndpointStream, -}; - -int dump_type_index(const char *type) -{ - unsigned int i; - - if (!type) - return -1; - - for (i = 0; i < SPA_N_ELEMENTS(dump_types); i++) { - if (spa_streq(dump_types[i], type)) - return (int)i; - } - - return -1; -} - -static inline unsigned int dump_type_count(void) -{ - return SPA_N_ELEMENTS(dump_types); -} - -static const char *name_to_dump_type(const char *name) -{ - unsigned int i; - - if (!name) - return NULL; - - for (i = 0; i < SPA_N_ELEMENTS(dump_types); i++) { - if (!strcasecmp(name, pw_interface_short(dump_types[i]))) - return dump_types[i]; - } - - return NULL; -} - #define INDENT(_level) \ ({ \ int __level = (_level); \ @@ -2196,817 +2106,6 @@ static const char *name_to_dump_type(const char *name) (const char *)_indent; \ }) -static void -dump(struct data *data, struct global *global, - enum dump_flags flags, int level); - -static void -dump_properties(struct data *data, struct global *global, - enum dump_flags flags, int level) -{ - struct remote_data *rd = data->current; - struct spa_dict *props; - const struct spa_dict_item *item; - const char *ind; - int id; - const char *extra; - - if (!global) - return; - - props = global_props(global); - if (!props || !props->n_items) - return; - - ind = INDENT(level + 2); - spa_dict_for_each(item, props) { - printf("%s%s = \"%s\"", - ind, item->key, item->value); - - extra = NULL; - if (spa_streq(global->type, PW_TYPE_INTERFACE_Port) && spa_streq(item->key, PW_KEY_NODE_ID)) { - id = atoi(item->value); - if (id >= 0) - extra = obj_lookup(rd, id, PW_KEY_NODE_NAME); - } else if (spa_streq(global->type, PW_TYPE_INTERFACE_Factory) && spa_streq(item->key, PW_KEY_MODULE_ID)) { - id = atoi(item->value); - if (id >= 0) - extra = obj_lookup(rd, id, PW_KEY_MODULE_NAME); - } else if (spa_streq(global->type, PW_TYPE_INTERFACE_Device) && spa_streq(item->key, PW_KEY_FACTORY_ID)) { - id = atoi(item->value); - if (id >= 0) - extra = obj_lookup(rd, id, PW_KEY_FACTORY_NAME); - } else if (spa_streq(global->type, PW_TYPE_INTERFACE_Device) && spa_streq(item->key, PW_KEY_CLIENT_ID)) { - id = atoi(item->value); - if (id >= 0) - extra = obj_lookup(rd, id, PW_KEY_CLIENT_NAME); - } - - if (extra) - printf(" (\"%s\")", extra); - - printf("\n"); - } -} - -static void -dump_params(struct data *data, struct global *global, - struct spa_param_info *params, uint32_t n_params, - enum dump_flags flags, int level) -{ - uint32_t i; - const char *ind; - - if (params == NULL || n_params == 0) - return; - - ind = INDENT(level + 1); - for (i = 0; i < n_params; i++) { - const struct spa_type_info *type_info = spa_type_param; - - printf("%s %d (%s) %c%c\n", ind, - params[i].id, - spa_debug_type_find_name(type_info, params[i].id), - params[i].flags & SPA_PARAM_INFO_READ ? 'r' : '-', - params[i].flags & SPA_PARAM_INFO_WRITE ? 'w' : '-'); - } -} - - -static void -dump_global_common(struct data *data, struct global *global, - enum dump_flags flags, int level) -{ - const char *ind; - - if (!(flags & is_short)) { - ind = INDENT(level + 1); - printf("%sid: %"PRIu32"\n", ind, global->id); - printf("%spermissions: "PW_PERMISSION_FORMAT"\n", ind, - PW_PERMISSION_ARGS(global->permissions)); - printf("%stype: %s/%d\n", ind, - global->type, global->version); - } else { - ind = INDENT(level); - printf("%s%"PRIu32":", ind, global->id); - if (!(flags & is_notype)) - printf(" %s", pw_interface_short(global->type)); - } -} - -static bool -dump_core(struct data *data, struct global *global, - enum dump_flags flags, int level) -{ - struct proxy_data *pd = pw_proxy_get_user_data(global->proxy); - struct pw_core_info *info; - const char *ind; - - if (!pd->info) - return false; - - dump_global_common(data, global, flags, level); - - info = pd->info; - if (!(flags & is_short)) { - ind = INDENT(level + 1); - printf("%scookie: %u\n", ind, info->cookie); - printf("%suser-name: \"%s\"\n", ind, info->user_name); - printf("%shost-name: \"%s\"\n", ind, info->host_name); - printf("%sversion: \"%s\"\n", ind, info->version); - printf("%sname: \"%s\"\n", ind, info->name); - printf("%sproperties:\n", ind); - dump_properties(data, global, flags, level); - } else { - printf(" u=\"%s\" h=\"%s\" v=\"%s\" n=\"%s\"", - info->user_name, info->host_name, info->version, info->name); - printf("\n"); - } - - return true; -} - -static bool -dump_module(struct data *data, struct global *global, - enum dump_flags flags, int level) -{ - struct remote_data *rd = global->rd; - struct proxy_data *pd = pw_proxy_get_user_data(global->proxy); - struct pw_module_info *info; - const char *args, *desc; - const char *ind; - uint32_t *factories = NULL; - int i, factory_count; - struct global *global_factory; - - if (!pd->info) - return false; - - info = pd->info; - - dump_global_common(data, global, flags, level); - - if (!(flags & is_short)) { - ind = INDENT(level + 1); - printf("%sname: \"%s\"\n", ind, info->name); - printf("%sfilename: \"%s\"\n", ind, info->filename); - printf("%sargs: \"%s\"\n", ind, info->args); - printf("%sproperties:\n", ind); - dump_properties(data, global, flags, level); - } else { - desc = spa_dict_lookup(info->props, PW_KEY_MODULE_DESCRIPTION); - args = info->args && strcmp(info->args, "(null)") ? info->args : NULL; - printf(" n=\"%s\" f=\"%s\"" "%s%s%s" "%s%s%s", - info->name, info->filename, - args ? " a=\"" : "", - args ? args : "", - args ? "\"" : "", - desc ? " d=\"" : "", - desc ? desc : "", - desc ? "\"" : ""); - printf("\n"); - } - - if (!(flags & is_deep)) - return true; - - factory_count = children_of(rd, global->id, PW_TYPE_INTERFACE_Factory, &factories); - if (factory_count >= 0) { - ind = INDENT(level + 1); - printf("%sfactories:\n", ind); - for (i = 0; i < factory_count; i++) { - global_factory = obj_global(rd, factories[i]); - if (!global_factory) - continue; - dump(data, global_factory, flags | is_notype, level + 1); - } - free(factories); - } - - return true; -} - -static bool -dump_device(struct data *data, struct global *global, - enum dump_flags flags, int level) -{ - struct remote_data *rd = data->current; - struct proxy_data *pd = pw_proxy_get_user_data(global->proxy); - struct pw_device_info *info; - const char *media_class, *api, *desc, *name; - const char *alsa_path, *alsa_card_id; - const char *ind; - uint32_t *nodes = NULL; - int i, node_count; - struct global *global_node; - - if (!pd->info) - return false; - - info = pd->info; - - dump_global_common(data, global, flags, level); - - if (!(flags & is_short)) { - ind = INDENT(level + 1); - printf("%sproperties:\n", ind); - dump_properties(data, global, flags, level); - printf("%sparams:\n", ind); - dump_params(data, global, info->params, info->n_params, flags, level); - } else { - media_class = spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS); - name = spa_dict_lookup(info->props, PW_KEY_DEVICE_NAME); - desc = spa_dict_lookup(info->props, PW_KEY_DEVICE_DESCRIPTION); - api = spa_dict_lookup(info->props, PW_KEY_DEVICE_API); - - printf("%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s", - media_class ? " c=\"" : "", - media_class ? media_class : "", - media_class ? "\"" : "", - name ? " n=\"" : "", - name ? name : "", - name ? "\"" : "", - desc ? " d=\"" : "", - desc ? desc : "", - desc ? "\"" : "", - api ? " a=\"" : "", - api ? api : "", - api ? "\"" : ""); - - if (media_class && spa_streq(media_class, "Audio/Device") && - api && spa_streq(api, "alsa:pcm")) { - - alsa_path = spa_dict_lookup(info->props, SPA_KEY_API_ALSA_PATH); - alsa_card_id = spa_dict_lookup(info->props, SPA_KEY_API_ALSA_CARD_ID); - - printf("%s%s%s" "%s%s%s", - alsa_path ? " p=\"" : "", - alsa_path ? alsa_path : "", - alsa_path ? "\"" : "", - alsa_card_id ? " id=\"" : "", - alsa_card_id ? alsa_card_id : "", - alsa_card_id ? "\"" : ""); - } - - printf("\n"); - } - - if (!(flags & is_deep)) - return true; - - node_count = children_of(rd, global->id, PW_TYPE_INTERFACE_Node, &nodes); - if (node_count >= 0) { - ind = INDENT(level + 1); - printf("%snodes:\n", ind); - for (i = 0; i < node_count; i++) { - global_node = obj_global(rd, nodes[i]); - if (!global_node) - continue; - dump(data, global_node, flags | is_notype, level + 1); - } - free(nodes); - } - - return true; -} - -static bool -dump_node(struct data *data, struct global *global, - enum dump_flags flags, int level) -{ - struct remote_data *rd = data->current; - struct proxy_data *pd = pw_proxy_get_user_data(global->proxy); - struct pw_node_info *info; - const char *name, *path; - const char *ind; - uint32_t *ports = NULL; - int i, port_count; - struct global *global_port; - - if (!pd->info) - return false; - - dump_global_common(data, global, flags, level); - - info = pd->info; - - if (!(flags & is_short)) { - ind = INDENT(level + 1); - printf("%sinput ports: %u/%u\n", ind, info->n_input_ports, info->max_input_ports); - printf("%soutput ports: %u/%u\n", ind, info->n_output_ports, info->max_output_ports); - printf("%sstate: \"%s\"", ind, pw_node_state_as_string(info->state)); - if (info->state == PW_NODE_STATE_ERROR && info->error) - printf(" \"%s\"\n", info->error); - else - printf("\n"); - printf("%sproperties:\n", ind); - dump_properties(data, global, flags, level); - printf("%sparams:\n", ind); - dump_params(data, global, info->params, info->n_params, flags, level); - } else { - name = spa_dict_lookup(info->props, PW_KEY_NODE_NAME); - path = spa_dict_lookup(info->props, SPA_KEY_OBJECT_PATH); - - printf(" s=\"%s\"", pw_node_state_as_string(info->state)); - - if (info->max_input_ports) { - printf(" i=%u/%u", info->n_input_ports, info->max_input_ports); - } - if (info->max_output_ports) { - printf(" o=%u/%u", info->n_output_ports, info->max_output_ports); - } - - printf("%s%s%s" "%s%s%s", - name ? " n=\"" : "", - name ? name : "", - name ? "\"" : "", - path ? " p=\"" : "", - path ? path : "", - path ? "\"" : ""); - - printf("\n"); - } - - if (!(flags & is_deep)) - return true; - - port_count = children_of(rd, global->id, PW_TYPE_INTERFACE_Port, &ports); - if (port_count >= 0) { - ind = INDENT(level + 1); - printf("%sports:\n", ind); - for (i = 0; i < port_count; i++) { - global_port = obj_global(rd, ports[i]); - if (!global_port) - continue; - dump(data, global_port, flags | is_notype, level + 1); - } - free(ports); - } - return true; -} - -static bool -dump_port(struct data *data, struct global *global, - enum dump_flags flags, int level) -{ - struct remote_data *rd = data->current; - struct proxy_data *pd = pw_proxy_get_user_data(global->proxy); - struct pw_port_info *info; - const char *ind; - const char *name, *format; - - if (!pd->info) - return false; - - dump_global_common(data, global, flags, level); - - info = pd->info; - - if (!(flags & is_short)) { - ind = INDENT(level + 1); - printf("%sdirection: \"%s\"\n", ind, - pw_direction_as_string(info->direction)); - printf("%sproperties:\n", ind); - dump_properties(data, global, flags, level); - printf("%sparams:\n", ind); - dump_params(data, global, info->params, info->n_params, flags, level); - } else { - printf(" d=\"%s\"", pw_direction_as_string(info->direction)); - - name = spa_dict_lookup(info->props, PW_KEY_PORT_NAME); - format = spa_dict_lookup(info->props, PW_KEY_FORMAT_DSP); - - printf("%s%s%s" "%s%s%s", - name ? " n=\"" : "", - name ? name : "", - name ? "\"" : "", - format ? " f=\"" : "", - format ? format : "", - format ? "\"" : ""); - - printf("\n"); - } - - (void)rd; - - return true; -} - -static bool -dump_factory(struct data *data, struct global *global, - enum dump_flags flags, int level) -{ - struct remote_data *rd = data->current; - struct proxy_data *pd = pw_proxy_get_user_data(global->proxy); - struct pw_factory_info *info; - const char *ind; - const char *module_id, *module_name; - - if (!pd->info) - return false; - - dump_global_common(data, global, flags, level); - - info = pd->info; - - if (!(flags & is_short)) { - ind = INDENT(level + 1); - printf("%sname: \"%s\"\n", ind, info->name); - printf("%sproperties:\n", ind); - dump_properties(data, global, flags, level); - } else { - printf(" n=\"%s\"", info->name); - - module_id = spa_dict_lookup(info->props, PW_KEY_MODULE_ID); - module_name = module_id ? obj_lookup(rd, atoi(module_id), PW_KEY_MODULE_NAME) : NULL; - - printf("%s%s%s", - module_name ? " m=\"" : "", - module_name ? module_name : "", - module_name ? "\"" : ""); - - printf("\n"); - } - - return true; -} - -static bool -dump_client(struct data *data, struct global *global, - enum dump_flags flags, int level) -{ - struct remote_data *rd = data->current; - struct proxy_data *pd = pw_proxy_get_user_data(global->proxy); - struct pw_client_info *info; - const char *ind; - const char *app_name, *app_pid; - - if (!pd->info) - return false; - - dump_global_common(data, global, flags, level); - - info = pd->info; - - if (!(flags & is_short)) { - ind = INDENT(level + 1); - printf("%sproperties:\n", ind); - dump_properties(data, global, flags, level); - } else { - app_name = spa_dict_lookup(info->props, PW_KEY_APP_NAME); - app_pid = spa_dict_lookup(info->props, PW_KEY_APP_PROCESS_ID); - - printf("%s%s%s" "%s%s%s", - app_name ? " ap=\"" : "", - app_name ? app_name : "", - app_name ? "\"" : "", - app_pid ? " ai=\"" : "", - app_pid ? app_pid : "", - app_pid ? "\"" : ""); - - printf("\n"); - } - - (void)rd; - - return true; -} - -static bool -dump_link(struct data *data, struct global *global, - enum dump_flags flags, int level) -{ - struct remote_data *rd = data->current; - struct proxy_data *pd = pw_proxy_get_user_data(global->proxy); - struct pw_link_info *info; - const char *ind; - const char *in_node_name, *in_port_name; - const char *out_node_name, *out_port_name; - - if (!pd->info) - return false; - - dump_global_common(data, global, flags, level); - - info = pd->info; - - if (!(flags & is_short)) { - ind = INDENT(level + 1); - printf("%soutput-node-id: %u\n", ind, info->output_node_id); - printf("%soutput-port-id: %u\n", ind, info->output_port_id); - printf("%sinput-node-id: %u\n", ind, info->input_node_id); - printf("%sinput-port-id: %u\n", ind, info->input_port_id); - - printf("%sstate: \"%s\"", ind, - pw_link_state_as_string(info->state)); - if (info->state == PW_LINK_STATE_ERROR && info->error) - printf(" \"%s\"\n", info->error); - else - printf("\n"); - printf("%sformat:\n", ind); - if (info->format) - spa_debug_pod(8 * (level + 1) + 2, NULL, info->format); - else - printf("%s\tnone\n", ind); - - printf("%sproperties:\n", ind); - dump_properties(data, global, flags, level); - } else { - out_node_name = obj_lookup(rd, info->output_node_id, PW_KEY_NODE_NAME); - in_node_name = obj_lookup(rd, info->input_node_id, PW_KEY_NODE_NAME); - out_port_name = obj_lookup(rd, info->output_port_id, PW_KEY_PORT_NAME); - in_port_name = obj_lookup(rd, info->input_port_id, PW_KEY_PORT_NAME); - - printf(" s=\"%s\"", pw_link_state_as_string(info->state)); - - if (out_node_name && out_port_name) - printf(" on=\"%s\"" " op=\"%s\"", - out_node_name, out_port_name); - if (in_node_name && in_port_name) - printf(" in=\"%s\"" " ip=\"%s\"", - in_node_name, in_port_name); - - printf("\n"); - } - - (void)rd; - - return true; -} - -static bool -dump_session(struct data *data, struct global *global, - enum dump_flags flags, int level) -{ - struct remote_data *rd = data->current; - struct proxy_data *pd = pw_proxy_get_user_data(global->proxy); - struct pw_session_info *info; - const char *ind; - - if (!pd->info) - return false; - - dump_global_common(data, global, flags, level); - - info = pd->info; - - if (!(flags & is_short)) { - ind = INDENT(level + 1); - printf("%sproperties:\n", ind); - dump_properties(data, global, flags, level); - printf("%sparams:\n", ind); - dump_params(data, global, info->params, info->n_params, flags, level); - } else { - printf("\n"); - } - - (void)rd; - - return true; -} - -static bool -dump_endpoint(struct data *data, struct global *global, - enum dump_flags flags, int level) -{ - struct remote_data *rd = data->current; - struct proxy_data *pd = pw_proxy_get_user_data(global->proxy); - struct pw_endpoint_info *info; - const char *ind; - const char *direction; - - if (!pd->info) - return false; - - dump_global_common(data, global, flags, level); - - info = pd->info; - - switch(info->direction) { - case PW_DIRECTION_OUTPUT: - direction = "source"; - break; - case PW_DIRECTION_INPUT: - direction = "sink"; - break; - default: - direction = "invalid"; - break; - } - - if (!(flags & is_short)) { - ind = INDENT(level + 1); - printf("%sname: %s\n", ind, info->name); - printf("%smedia-class: %s\n", ind, info->media_class); - printf("%sdirection: %s\n", ind, direction); - printf("%sflags: 0x%x\n", ind, info->flags); - printf("%sstreams: %u\n", ind, info->n_streams); - printf("%ssession: %u\n", ind, info->session_id); - printf("%sproperties:\n", ind); - dump_properties(data, global, flags, level); - printf("%sparams:\n", ind); - dump_params(data, global, info->params, info->n_params, flags, level); - } else { - printf(" n=\"%s\" c=\"%s\" d=\"%s\" s=%u si=%"PRIu32"", - info->name, info->media_class, direction, - info->n_streams, info->session_id); - printf("\n"); - } - - (void)rd; - - return true; -} - -static bool -dump_endpoint_stream(struct data *data, struct global *global, - enum dump_flags flags, int level) -{ - struct remote_data *rd = data->current; - struct proxy_data *pd = pw_proxy_get_user_data(global->proxy); - struct pw_endpoint_stream_info *info; - const char *ind; - - if (!pd->info) - return false; - - dump_global_common(data, global, flags, level); - - info = pd->info; - - if (!(flags & is_short)) { - ind = INDENT(level + 1); - printf("%sid: %u\n", ind, info->id); - printf("%sendpoint-id: %u\n", ind, info->endpoint_id); - printf("%sname: %s\n", ind, info->name); - printf("%sproperties:\n", ind); - dump_properties(data, global, flags, level); - printf("%sparams:\n", ind); - dump_params(data, global, info->params, info->n_params, flags, level); - } else { - printf(" n=\"%s\" i=%"PRIu32" ei=%"PRIu32"", - info->name, info->id, info->endpoint_id); - printf("\n"); - } - - (void)rd; - - return true; -} - -static void -dump(struct data *data, struct global *global, - enum dump_flags flags, int level) -{ - if (!global) - return; - - if (spa_streq(global->type, PW_TYPE_INTERFACE_Core)) - dump_core(data, global, flags, level); - - if (spa_streq(global->type, PW_TYPE_INTERFACE_Module)) - dump_module(data, global, flags, level); - - if (spa_streq(global->type, PW_TYPE_INTERFACE_Device)) - dump_device(data, global, flags, level); - - if (spa_streq(global->type, PW_TYPE_INTERFACE_Node)) - dump_node(data, global, flags, level); - - if (spa_streq(global->type, PW_TYPE_INTERFACE_Port)) - dump_port(data, global, flags, level); - - if (spa_streq(global->type, PW_TYPE_INTERFACE_Factory)) - dump_factory(data, global, flags, level); - - if (spa_streq(global->type, PW_TYPE_INTERFACE_Client)) - dump_client(data, global, flags, level); - - if (spa_streq(global->type, PW_TYPE_INTERFACE_Link)) - dump_link(data, global, flags, level); - - if (spa_streq(global->type, PW_TYPE_INTERFACE_Session)) - dump_session(data, global, flags, level); - - if (spa_streq(global->type, PW_TYPE_INTERFACE_Endpoint)) - dump_endpoint(data, global, flags, level); - - if (spa_streq(global->type, PW_TYPE_INTERFACE_EndpointStream)) - dump_endpoint_stream(data, global, flags, level); -} - -static bool do_dump(struct data *data, const char *cmd, char *args, char **error) -{ - struct remote_data *rd = data->current; - union pw_map_item *item; - struct global *global; - char *aa[32], **a; - char c; - int i, n, idx; - enum dump_flags flags = is_default; - bool match; - unsigned int type_mask; - - n = pw_split_ip(args, WHITESPACE, SPA_N_ELEMENTS(aa), aa); - if (n < 0) - goto usage; - - a = aa; - while (n > 0 && - (spa_streq(a[0], "short") || - spa_streq(a[0], "deep") || - spa_streq(a[0], "resolve") || - spa_streq(a[0], "notype"))) { - if (spa_streq(a[0], "short")) - flags |= is_short; - else if (spa_streq(a[0], "deep")) - flags |= is_deep; - else if (spa_streq(a[0], "resolve")) - flags |= is_resolve; - else if (spa_streq(a[0], "notype")) - flags |= is_notype; - n--; - a++; - } - - while (n > 0 && a[0][0] == '-') { - for (i = 1; (c = a[0][i]) != '\0'; i++) { - if (c == 's') - flags |= is_short; - else if (c == 'd') - flags |= is_deep; - else if (c == 'r') - flags |= is_resolve; - else if (c == 't') - flags |= is_notype; - else - goto usage; - } - n--; - a++; - } - - if (n == 0 || spa_streq(a[0], "all")) { - type_mask = (1U << dump_type_count()) - 1; - flags &= ~is_notype; - } else { - type_mask = 0; - for (i = 0; i < n; i++) { - /* skip direct IDs */ - if (isdigit(a[i][0])) - continue; - idx = dump_type_index(name_to_dump_type(a[i])); - if (idx < 0) - goto usage; - type_mask |= 1U << idx; - } - - /* single bit set? disable type */ - if ((type_mask & (type_mask - 1)) == 0) - flags |= is_notype; - } - - pw_array_for_each(item, &rd->globals.items) { - if (pw_map_item_is_free(item) || item->data == NULL) - continue; - - global = item->data; - - /* unknown type, ignore completely */ - idx = dump_type_index(global->type); - if (idx < 0) - continue; - - match = false; - - /* first check direct ids */ - for (i = 0; i < n; i++) { - /* skip non direct IDs */ - if (!isdigit(a[i][0])) - continue; - if (atoi(a[i]) == (int)global->id) { - match = true; - break; - } - } - - /* if type match */ - if (!match && (type_mask & (1U << idx))) - match = true; - - if (!match) - continue; - - dump(data, global, flags, 0); - } - - return true; -usage: - *error = spa_aprintf("%s [short|deep|resolve|notype] [-sdrt] [all|%s|<id>]", - cmd, DUMP_NAMES); - return false; -} - static bool parse(struct data *data, char *buf, char **error) { char *a[2]; @@ -3040,18 +2139,20 @@ static bool parse(struct data *data, char *buf, char **error) } /* We need a global variable, readline doesn't have a closure arg */ -static struct data *readline_dataptr; +static struct data *input_dataptr; -static void readline_process_line(char *line) +static void input_process_line(char *line) { - struct data *d = readline_dataptr; + struct data *d = input_dataptr; char *error; if (!line) line = strdup("quit"); if (line[0] != '\0') { +#ifdef HAVE_READLINE add_history(line); +#endif if (!parse(d, line, &error)) { fprintf(stderr, "Error: \"%s\"\n", error); free(error); @@ -3065,8 +2166,21 @@ static void do_input(void *data, int fd, uint32_t mask) struct data *d = data; if (mask & SPA_IO_IN) { - readline_dataptr = d; + input_dataptr = d; +#ifdef HAVE_READLINE rl_callback_read_char(); +#else + { + char *line = NULL; + size_t s = 0; + + if (getline(&line, &s, stdin) < 0) { + free(line); + line = NULL; + } + input_process_line(line); + } +#endif if (d->current == NULL) pw_main_loop_quit(d->loop); @@ -3078,6 +2192,7 @@ static void do_input(void *data, int fd, uint32_t mask) } } +#ifdef HAVE_READLINE static char * readline_match_command(const char *text, int state) { @@ -3119,13 +2234,14 @@ readline_command_completion(const char *text, int start, int end) static void readline_init() { rl_attempted_completion_function = readline_command_completion; - rl_callback_handler_install(">> ", readline_process_line); + rl_callback_handler_install(">> ", input_process_line); } static void readline_cleanup() { rl_callback_handler_remove(); } +#endif static void do_quit_on_signal(void *data, int signal_number) { @@ -3152,13 +2268,14 @@ int main(int argc, char *argv[]) struct pw_loop *l; char *opt_remote = NULL; char *error; - bool daemon = false; + bool daemon = false, monitor = false; struct remote_data *rd; static const struct option long_options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "daemon", no_argument, NULL, 'd' }, - { "remote", required_argument, NULL, 'r' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "monitor", no_argument, NULL, 'm' }, + { "daemon", no_argument, NULL, 'd' }, + { "remote", required_argument, NULL, 'r' }, { NULL, 0, NULL, 0} }; int c, i; @@ -3168,7 +2285,7 @@ int main(int argc, char *argv[]) setlocale(LC_ALL, ""); pw_init(&argc, &argv); - while ((c = getopt_long(argc, argv, "hVdr:", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hVmdr:", long_options, NULL)) != -1) { switch (c) { case 'h': show_help(&data, argv[0], false); @@ -3184,6 +2301,9 @@ int main(int argc, char *argv[]) case 'd': daemon = true; break; + case 'm': + monitor = true; + break; case 'r': opt_remote = optarg; break; @@ -3228,13 +2348,17 @@ int main(int argc, char *argv[]) printf("Welcome to PipeWire version %s. Type 'help' for usage.\n", pw_get_library_version()); +#ifdef HAVE_READLINE readline_init(); +#endif pw_loop_add_io(l, STDIN_FILENO, SPA_IO_IN|SPA_IO_HUP, false, do_input, &data); pw_main_loop_run(data.loop); +#ifdef HAVE_READLINE readline_cleanup(); +#endif } else { char buf[4096], *p, *error; @@ -3250,9 +2374,11 @@ int main(int argc, char *argv[]) fprintf(stderr, "Error: \"%s\"\n", error); free(error); } - if (!data.quit && data.current) { + while (!data.quit && data.current) { data.current->prompt_pending = pw_core_sync(data.current->core, 0, 0); pw_main_loop_run(data.loop); + if (!monitor) + break; } } spa_list_consume(rd, &data.remotes, link) diff --git a/test/test-spa-json.c b/test/test-spa-json.c index 8187787fd11fa9adb6f1ab76595732c4bc14f086..2a57039734cc5293af18791f0683a804e355078e 100644 --- a/test/test-spa-json.c +++ b/test/test-spa-json.c @@ -86,7 +86,7 @@ static void expect_float(struct spa_json *it, float val) { const char *value; int len; - float f; + float f = 0.0f; pwtest_int_gt((len = spa_json_next(it, &value)), 0); check_type(TYPE_FLOAT, value, len); pwtest_int_gt(spa_json_parse_float(value, len, &f), 0); @@ -281,6 +281,36 @@ PWTEST(json_float) return PWTEST_PASS; } +PWTEST(json_float_check) +{ + struct { + const char *str; + int res; + } val[] = { + { "0.0", 1 }, + { ".0", 1 }, + { "+.0E0", 1 }, + { "-.0e0", 1 }, + + { "0,0", 0 }, + { "0.0.5", 0 }, + { "0x0", 0 }, + { "0x0.0", 0 }, + { "E10", 0 }, + { "e20", 0 }, + { " 0.0", 0 }, + { "0.0 ", 0 }, + { " 0.0 ", 0 }, + }; + unsigned i; + float v; + + for (i = 0; i < SPA_N_ELEMENTS(val); i++) { + pwtest_int_eq(spa_json_parse_float(val[i].str, strlen(val[i].str), &v), val[i].res); + } + return PWTEST_PASS; +} + PWTEST(json_int) { int v; @@ -296,6 +326,7 @@ PWTEST_SUITE(spa_json) pwtest_add(json_array, PWTEST_NOARG); pwtest_add(json_overflow, PWTEST_NOARG); pwtest_add(json_float, PWTEST_NOARG); + pwtest_add(json_float_check, PWTEST_NOARG); pwtest_add(json_int, PWTEST_NOARG); return PWTEST_PASS; diff --git a/test/test-spa-utils.c b/test/test-spa-utils.c index 0a750391d1f379c9425b5163b8746e42d0219b1f..2f198f90a7c2b898c1df8ad95ff5a4c2f0c37cec 100644 --- a/test/test-spa-utils.c +++ b/test/test-spa-utils.c @@ -419,6 +419,12 @@ PWTEST(utils_hook) } pwtest_int_eq(count, 4); pwtest_int_eq(hook_free_count, 4); + + /* remove a zeroed hook */ + struct spa_hook hook; + spa_zero(hook); + spa_hook_remove(&hook); + return PWTEST_PASS; }