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/.gitlab-ci.yml b/.gitlab-ci.yml index 61e276c4d35cbae9930cb5dfd68ec98ff78b416d..50eb1d1e1a874ef820a1f2558a07e1fe863800d4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -268,9 +268,9 @@ build_all: extends: - .build_on_fedora variables: - # Fedora doesn't have libfreeaptx, lc3plus, or roc + # Fedora doesn't have libfreeaptx, lc3plus, lc3, or roc # libcamera has no stable API, so let's not chase that target - MESON_OPTIONS: "-Dauto_features=enabled -Dbluez5-codec-aptx=disabled -Dbluez5-codec-lc3plus=disabled -Droc=disabled -Dlibcamera=disabled" + MESON_OPTIONS: "-Dauto_features=enabled -Dbluez5-codec-aptx=disabled -Dbluez5-codec-lc3plus=disabled -Dbluez5-codec-lc3=disabled -Droc=disabled -Dlibcamera=disabled" parallel: matrix: - CC: [gcc, clang] 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..159eb1f52f0eebec5d309f5a51d43cccfa6a5ef5 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,479 @@ +# PipeWire 0.3.59 (2022-09-30) + +This is a bugfix release that is API and ABI compatible with previous +0.3.x releases. + +## Highlights + - Fix possible wrong samplerate in loopback streams after suspend and + rate switch. + - module-filter-chain can now adapt to the graph samplerate. + - Fix some potential stuttering and crackling in pulse-server. + - Add Bluetooth LE support. This requires experimental kernel and bluez + support. + - The ALSA plugin has more options to control the buffer size. This can + be used to work around high latency in davinci resolve. + - Many bugfixes and improvements. + + +## PipeWire + - Add audio capture example with volume meter. + - Fix a case where a rate switch would not suspend all the nodes of the + driver first. This could cause wrong samplerates in streams. + - Fix a case where a node would be Paused while still added to the + graph, causing potential crashes. (#2701) + +## Modules + - module-filter-chain and module-loopback now use the resample.prefill + option to avoid buffering extra samples and causing unwanted latency + when resampling is activated. + - module-filter-chain can now adapt to the graph samplerate. + - Improve module-raop to support the ALAC codec as raw PCM. + - Improve RTSP parsing to improve compatibility. + +## Tools + - Fix 100% CPU in pw-cli monitor mode. (#2709) + - spa-acp-tool can now be exited with ctrl-D. + +## SPA + - Various libcamera fixes and improvements. + - Set stride on audioconvert output buffers. + - Make sure we always place the last requested size from the resampler + on the buffers in pw-stream. + - Add resample.prefill option in the resampler to fill the history with + 0 so that we don't have smaller buffers at the start. + - Make sure that when an overflow corrupts a POD, that it will always + stay corrupted. + - Rate limit some ALSA warnings and reduce some unwanted warnings. + - Don't recalculate the audioconverter state for each pause/play. (#2701) + - Fix some POD parsing inconsistencies and potential overflows. + - Add support for Asus Xonar SE. + - Fix Flush command handling. It should not stop playback. (#2726) + - Refactor the peaks function and add some unit tests and optimizations. + - The channelmix has an optimized nXm converter and new unit tests. + - Normalization in the channelmixer was fixed. + +## pulse-server + - The requested latency of record streams was reduced to fix some + stuttering in Teamspeak. (#2702) + - Tweak the max amount of bytes sent to a client. (#2711) (#2715) + - Improve maxlength calculations, this fixes some crackling noise with + high samplerate and channel counts in some players (audacious). + +## Bluetooth + - Merge Bluetooth LE support. + - Make sure we are backward compatible with WirePlumber. + - Fix some HFP and HSP AT command parsing. (#2463) + - Use HFP by default over HSP. + +## ALSA + - Increase max number of periods. + - The parameters handling was improved. There is now an option to set the + buffer-bytes of the ALSA plugin. + - PIPEWIRE_ALSA can now be used as an environment variable to restrict the + plugin formats and buffer size. + +Older versions: + + +# PipeWire 0.3.58 (2022-09-15) + +This is a bugfix release that is API and ABI compatible with previous +0.3.x releases. + +## Highlights + - Fix a regression that could cause audio crackling. + - Fix a regression in RTKit because rlimit was not set correctly. + - JAVA sound applications will now alsa work with the pulseaudio-alsa plugin. + - pw-top will now show the negotiated formats of devices and streams. + - Fix some potential crashes when starting streams. + - The ALSA plugin has had improved timing reporting and poll descriptor + handling that should improve compatibility. + - Many more improvements and bugfixes. + + +## PipeWire + - Avoid scheduling nodes before they are added to the graph. This could + avoid some crashes when scheduling nodes that were not completely + started yet. (#2677) + +## Tools + - pw-top now also shows the negotiated formats of streams and devices. + (#2566) + - pw-top prints microseconds as "us" now to avoid unicode problems. + +## Modules + - Fix compilation with newer lv2. + - Fix setting realtime priority with RTKit, it was not setting rlimit + correctly and RTKit would refuse to change the priority. + - Fix some playback problems with RAOP sink. (#2673) + - Filter chain will now warn when a non-existing control property is + used in the config file. (#2685) + - Filter chain can now handle control port names with ":" in the name. + (#2685) + - The echo-cancel module and interface now has activate/deactivate + functions to make it possible for plugins to reset their state. + +## SPA + - Make sure audioconvert uses the given channelmap and channels for the + volumes, even when not negotiated yet. This makes it possible to change + the volume before the node has been negotiated. + - Refactor the peaks resampler. Fix an error in the SSE code. + - Fix DSD min/max rates, avoid exposing impossible rates. + - Set monitor port buffer size correctly. This could cause some crackling + and hickups. (#2677) + - Make ALSA sequencer port names unique. + +## Pulse-server + - Rework the capture buffer attributes to better match pulseaudio. This + fixes a regression where opening pavucontrol could cause crackling. + (#2671) + - Implement TRIGGER and PREBUF methods. + - Handle clients that send more than the requested amount of data. + PipeWire will now also keep this as extra buffered data like PulseAudio. + This fixes JAVA sound applications when they are running on top of the + PulseAudio ALSA plugin. (#2626,#2674) + - Update the requested amount of bytes more like PulseAudio. Fixes + stuttering after resume with the GStreamer pulseaudio sink. (#2680) + +## ALSA Plugin + - More debug info was added. The time reporting was improved. + - The poll descriptor handling was improved, avoiding some spurious + wakeups. (#1697) + + +# PipeWire 0.3.57 (2022-09-02) + +This is a bugfix release that is API and ABI compatible with previous +0.3.x releases. + +## Highlights + - Support masking of conf.d/ files. (#2629) + - Use org.freedesktop.portal.Realtime when available. This does the + correct PID/TID mappings to make realtime also work from flatpaks. + - Fix rate adjustement logic in pulse-tunnel. This would cause + increasing delays and hickups when using tunnels. (#2548) + - Add OPUS as a new vendor codec. Add OPUS-A2DP spec. PipeWire can now + send and reveive OPUS data over bluetooth. + - An AAC decoder was added so that PipeWire can now also function as + an A2DP AAC receiver. + - Fix some issues where the wrong samplerate was used. (#2614) + - Fix rate match for sources. This fixes an error where follower sources + would generate many resync warnings. + - Many more bugfixes and improvements. + + +## PipeWire + - Support masking of conf.d/ files. (#2629) + - 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. + +# 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 +588,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..fd691e024ef9d77b396d74f78688139e1940c2eb 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,119 @@ +pipewire (0.3.59-1) unstable; urgency=medium + + * New upstream release + * Remove conflict between pipewire-pulse and pulseaudio to allow users + to switch from one to the other just by disabling/enabling services. + (Closes: #1020330, #1020903) + + -- Dylan Aïssi <daissi@debian.org> Fri, 30 Sep 2022 15:04:20 +0200 + +pipewire (0.3.58-2) unstable; urgency=medium + + * Mention to install pipewire-alsa and pipewire-jack + in README.Debian (Closes: #1019971) + * Add debian/pipewire-alsa.TODO + * Patch pipewire-pulse.service to be sure it is started + after a session manager (Closes: #1019944) + Because of a bug in the way systemd handles aliases, they have been removed + in wireplumber and pipewire-media-session services to avoid a conflict. + This change needs to be reflected in the pipewire-pulse service to be sure + it is started after a session manager, otherwise pipewire-pulse doesn't + see any devices. + + -- Dylan Aïssi <daissi@debian.org> Fri, 23 Sep 2022 11:01:16 +0200 + +pipewire (0.3.58-1) unstable; urgency=medium + + [ Dylan Aïssi ] + * New upstream release + - Fix crackling sound if pavucontrol is open (Closes: #1019888) + * Create a pipewire group and define real-time priority limits + (Closes: #1011399) + * Add suggestion to install wireplumber in pipewire.README.Debian + * Clarify relation between pipewire and libspa-0.2-bluetooth in + pipewire.README.Debian (Closes: #998220, #1011035) + * Remove reference to experimental status of pipewire for audio + + [ Sebastien Bacher ] + * Let pipewire-pulse conflicts on pulseaudio + (Closes: #1013276, LP: #1975823) + + -- Dylan Aïssi <daissi@debian.org> Fri, 16 Sep 2022 15:41:31 +0200 + +pipewire (0.3.57-1) 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..98e1cc8838da47009ae0a51d18a384d401278e51 100644 --- a/debian/control +++ b/debian/control @@ -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 @@ -307,9 +348,7 @@ Description: libraries for the PipeWire multimedia server - bluetooth plugins - Generating graphs for audio and video processing. . This package contains a plugin to make Bluetooth audio devices such as - speakers and headsets available to the PipeWire server. It is considered - to be experimental, and is disabled by default (even if installed) to - avoid conflicts with equivalent functionality in PulseAudio. + speakers and headsets available to the PipeWire server. Package: libspa-0.2-jack Architecture: linux-any @@ -328,4 +367,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/Fix_services.patch b/debian/patches/Fix_services.patch new file mode 100644 index 0000000000000000000000000000000000000000..fe19732919546ca84c8d4be1bddb55ef23483e5c --- /dev/null +++ b/debian/patches/Fix_services.patch @@ -0,0 +1,23 @@ +Description: Update Wants and After fields with real services (NOT alias) + Due to a systemd bug in the way it manages alias, they have been removed in + wireplumber and pipewire-media-session services. Reflecting this change in + the pipewire-pulse service to be sure it is started AFTER a session manager. +Bug: https://github.com/systemd/systemd/issues/23694 +Bug-Debian: https://bugs.debian.org/997818 + https://bugs.debian.org/1019944 +Author: Dylan Aïssi <daissi@debian.org> +Forwarded: not-needed + +--- a/src/daemon/systemd/user/pipewire-pulse.service.in ++++ b/src/daemon/systemd/user/pipewire-pulse.service.in +@@ -15,8 +15,8 @@ + # socket-service relationship, see systemd.socket(5). + Requires=pipewire-pulse.socket + ConditionUser=!root +-Wants=pipewire.service pipewire-session-manager.service +-After=pipewire.service pipewire-session-manager.service ++Wants=pipewire.service wireplumber.service pipewire-media-session.service ++After=pipewire.service wireplumber.service pipewire-media-session.service + Conflicts=pulseaudio.service + + [Service] diff --git a/debian/patches/series b/debian/patches/series index adf4f7f600dc2b456a1398b6fb6751066ab774c4..2eb644dcb91d7d58e65d2cb8ab0f14e08ca8d81e 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,2 +1,4 @@ Don-t-automatically-start-pipewire-for-root-logins.patch Don-t-build_same_binary_twice.patch +Fix_services.patch +# Recommended patch for 0.3.5X diff --git a/debian/pipewire-alsa.TODO b/debian/pipewire-alsa.TODO new file mode 100644 index 0000000000000000000000000000000000000000..e74161c603be857565561a2ce5cc099a699d584f --- /dev/null +++ b/debian/pipewire-alsa.TODO @@ -0,0 +1,13 @@ +# https://bugs.debian.org/1019971 + +Since pipewire-alsa has been split from the previous pipewire-audio-client-libraries +package, it would be useful to directly install the 99-pipewire-default.conf +file in the right location: Install 99-pipewire-default.conf in +usr/share/alsa/alsa.conf.d/ and create a symlink to etc/alsa/conf.d/. +Similarly to what is done for 50-pipewire.conf. + +But, people upgrading their pipewire-audio-client-libraries package will +have pipewire-alsa installed and enabled by default on their system +even if it was not enabled before the upgrade. + +Thus, this should be done at least after the release of Bookworm. diff --git a/debian/pipewire-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.README.Debian b/debian/pipewire-audio-client-libraries.README.Debian deleted file mode 100644 index 7b07c97a7a95fd909b16497c4cea1a4437550186..0000000000000000000000000000000000000000 --- a/debian/pipewire-audio-client-libraries.README.Debian +++ /dev/null @@ -1,40 +0,0 @@ -Using pipewire for audio -======================== - -pipewire in Debian is primarily intended to be used for video -(screen sharing and remote desktop, particularly in GNOME and KDE Wayland -sessions). However, it can also be used for audio. - -This is not a supported scenario for Debian 11, and is considered -experimental. - -Using pipewire as a substitute for PulseAudio ---------------------------------------------- - -Install the pipewire-pulse package and log back in - -Using pipewire as the default ALSA output device ------------------------------------------------- - -ALSA clients can be configured to output via pipewire instead of -PulseAudio or directly to ALSA. - -To enable this: - -* create an empty file /etc/pipewire/media-session.d/with-alsa - -* copy /usr/share/doc/pipewire/examples/alsa.conf.d/99-pipewire-default.conf - into /etc/alsa/conf.d/ - -Using pipewire as a substitute for JACK ---------------------------------------- - -JACK clients can be configured to output via pipewire instead of JACK. - -To enable this: - -* create an empty file /etc/pipewire/media-session.d/with-jack - -* either run JACK clients using the pw-jack(1) wrapper, or copy - /usr/share/doc/pipewire/examples/ld.so.conf.d/pipewire-jack-*.conf - into /etc/ld.so.conf.d/ and run ldconfig as root. 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..0b7a86247bb4425d44745e95b19bf17651acb75a 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.* @@ -36,3 +37,4 @@ usr/share/man/man1/pw-mon.* usr/share/man/man1/pw-profiler.* usr/share/man/man1/pw-top.* usr/share/man/man5 +debian/rlimits/95-pipewire.conf /etc/security/limits.d/ diff --git a/debian/pipewire-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.README.Debian b/debian/pipewire.README.Debian new file mode 100644 index 0000000000000000000000000000000000000000..37c50db7c05e25f99555772e1176ee84717fda2c --- /dev/null +++ b/debian/pipewire.README.Debian @@ -0,0 +1,98 @@ +Using pipewire for audio +======================== + +pipewire in Debian was initially used for video (screen sharing and remote +desktop, particularly in GNOME and KDE Wayland sessions). However, it has +matured enough to also be used for audio. + + +Using pipewire as a substitute for PulseAudio +--------------------------------------------- + +Install the pipewire-pulse package and log back in. +And potentially install the new recommended session manager +wireplumber instead of the deprecated pipewire-media-session. + +PipeWire project recommends [1] to remove the pulseaudio package to prevent +conflicts in some cases [2] even if both pulseaudio and pipewire-pulse +services are not running at the same time. +pipewire-pulse doesn't conflict with pulseaudio at the package level to allow +users to switch from one to the other just by disabling/enabling services. + +[1] https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/FAQ#should-i-uninstall-everything-pulseaudio +[2] https://bugs.debian.org/1013276 + + +Using pipewire with Bluetooth +--------------------------------------------- + +Install the libspa-0.2-bluetooth package to add Bluetooth support to pipewire. + +Pipewire (nor any other package) does not depend or recommend libspa-0.2-bluetooth +as not all users are interested in Bluetooth support. +The same apply to pulseaudio and pulseaudio-module-bluetooth: +pulseaudio does not depend or recommend pulseaudio-module-bluetooth. + +However, pulseaudio-module-bluetooth and libspa-0.2-bluetooth are installed on +a system through the dependency of a Desktop Environment. For example, +with GNOME, the bluetooth plugin is pulled by the meta package gnome-core. + +Moreover, both pipewire-pulse and wireplumber packages already suggest +to install the libspa-0.2-bluetooth package. + + +Using pipewire as the default ALSA output device +------------------------------------------------ + +Install the pipewire-alsa package. + +ALSA clients can be configured to output via pipewire instead of +PulseAudio or directly to ALSA. + +To enable this: + +* create an empty file /etc/pipewire/media-session.d/with-alsa + +* copy /usr/share/doc/pipewire/examples/alsa.conf.d/99-pipewire-default.conf + into /etc/alsa/conf.d/ + + +Using pipewire as a substitute for JACK +--------------------------------------- + +Install the pipewire-jack package. + +JACK clients can be configured to output via pipewire instead of JACK. + +To enable this: + +* create an empty file /etc/pipewire/media-session.d/with-jack + +* either run JACK clients using the pw-jack(1) wrapper, or copy + /usr/share/doc/pipewire/examples/ld.so.conf.d/pipewire-jack-*.conf + into /etc/ld.so.conf.d/ and run ldconfig as root. + + +Setting pipewire real-time priority limits +--------------------------------------- + +!!! WARNING !!! +Your system has real-time priority limits for a good reason. This allows it +to remain stable in case a process goes crazy. RTKit allows limited use of +real-time priority without the risk of locking up the system if a real-time +task starts spinning. +Some upstream recommendations for real-time are to increase these limits to +bypass RTKit or to disable most of its safeguards. By following them, your +system could be blocked if a process goes wrong. +These performance tweaks are not needed for a normal use of pipewire, instead +modifying pipewire configuration is enough. +!!! WARNING !!! + +The "pipewire" package creates a system group called "pipewire". +The upstream recommended priority limits for this group are defined in: + +* /etc/security/limits.d/95-pipewire.conf + +To enable these limits for your user, add it to the "pipewire" group. + +* sudo adduser yourusername pipewire diff --git a/debian/pipewire.postinst b/debian/pipewire.postinst new file mode 100644 index 0000000000000000000000000000000000000000..0c706702189f9d3673234428942d2a2d93473f5c --- /dev/null +++ b/debian/pipewire.postinst @@ -0,0 +1,11 @@ +#!/bin/sh +set -e + +if [ "$1" = "configure" ] ; then + # Create the pipewire system group for setting real-time priority limits + if ! getent group pipewire > /dev/null; then + addgroup --quiet --system pipewire + fi +fi + +#DEBHELPER# diff --git a/debian/pipewire.postrm b/debian/pipewire.postrm new file mode 100644 index 0000000000000000000000000000000000000000..ccde1e254f0fb7c7a2cd7c3a945a848eb7d171a4 --- /dev/null +++ b/debian/pipewire.postrm @@ -0,0 +1,8 @@ +#!/bin/sh +set -e + +if [ "$1" = "purge" ] ; then + delgroup --quiet --system pipewire > /dev/null || true +fi + +#DEBHELPER# diff --git a/debian/rlimits/95-pipewire.conf b/debian/rlimits/95-pipewire.conf new file mode 100644 index 0000000000000000000000000000000000000000..47f9ab46c093a39b72e8d50cd92582530ea97e05 --- /dev/null +++ b/debian/rlimits/95-pipewire.conf @@ -0,0 +1,4 @@ +# Default limits for users of pipewire +@pipewire - rtprio 95 +@pipewire - nice -19 +@pipewire - memlock 4194304 diff --git a/debian/rlimits/README b/debian/rlimits/README new file mode 100644 index 0000000000000000000000000000000000000000..58d4ad24ecf20dc7c1d81b6c9d23f8b1affd4118 --- /dev/null +++ b/debian/rlimits/README @@ -0,0 +1,8 @@ +Setting pipewire real-time priority limits +--------------------------------------- + +The debian/rlimits/95-pipewire.conf defines the real-time priority limits +for the system group "pipewire" as recommended by upstream. +This file is installed in /etc/security/limits.d/. + +See https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Performance-tuning#rlimits diff --git a/debian/rules b/debian/rules index d3910a5e5c9c552e2e3661b4dd61c421652118e4..150d4a05f98c49aef301e9b5d3c5e94d0dc8a65f 100755 --- a/debian/rules +++ b/debian/rules @@ -77,10 +77,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/index.dox b/doc/index.dox index e61642cc1fd570d236fdcac745a88b17f6c3b2af..1602b7b7f80d2c19c06066db408a85ae808f6796 100644 --- a/doc/index.dox +++ b/doc/index.dox @@ -40,5 +40,6 @@ More information on how to configure and use PipeWire. - [PipeWire: The Linux audio/video bus (LWN)](https://lwn.net/Articles/847412) - [PipeWire Wikipedia](https://en.wikipedia.org/wiki/PipeWire) - [Bluetooth, PipeWire and Whatsapp calls](https://gjhenrique.com/pipewire.html) +- [Intoduction to PipeWire](https://bootlin.com/blog/an-introduction-to-pipewire/) */ diff --git a/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/pipewire.1.rst.in b/man/pipewire.1.rst.in index fd07017059bba520c9c224caf2022547c8c0cb08..8b63839696510960f4128cae1d980027d33bfb95 100644 --- a/man/pipewire.1.rst.in +++ b/man/pipewire.1.rst.in @@ -47,6 +47,8 @@ PipeWire is available from @PACKAGE_URL@ SEE ALSO ======== +``pw-top(1)``, +``pw-dump(1)``, ``pw-mon(1)``, ``pw-cat(1)``, ``pw-cli(1)``, diff --git a/man/pw-cat.1.rst.in b/man/pw-cat.1.rst.in index bc386bb33af3fc7409514a8bf2652d997bc50b8a..1e7687f82d75a6a788be0357f4d6a660a7db5cf4 100644 --- a/man/pw-cat.1.rst.in +++ b/man/pw-cat.1.rst.in @@ -26,9 +26,14 @@ capturing raw or encoded media files on a PipeWire server. It understands all audio file formats supported by ``libsndfile`` for PCM capture and playback. -It understands standard MIDI files for playback and recording, +It understands standard MIDI files for playback and recording. This tool +will not render MIDI files, it will simply make the MIDI events available +to the graph. You need a MIDI renderer such as qsynth, timidity or a hardware +MIDI rendered to hear the MIDI. -DSD playback is supported with the DSF file format. +DSD playback is supported with the DSF file format. This tool will only work +with native DSD capable hardware and will produce an error when no such +hardware was found. When the *FILE* is - input and output will be from STDIN and STDOUT respectively. @@ -60,10 +65,15 @@ OPTIONS -m | --midi MIDI mode. *FILE* is a MIDI file. If the tool is called under the name **pw-midiplay** or **pw-midirecord** this is the default. + Note that this program will *not* render the MIDI events into audible samples, + it will simply provide the MIDI events in the graph. You need a separate + MIDI renderer such as qsynth, timidity or a hardware renderer to hear the MIDI. -d | --dsd DSD mode. *FILE* is a DSF file. If the tool is called under the name **pw-dsdplay** this is the default. + Note that this program will *not* render the DSD audio. You need a DSD capable + device to play DSD content or this program will exit with an error. --media-type=VALUE Set the media type property (default Audio/Midi depending on mode). diff --git a/man/pw-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/man/pw-profiler.1.rst.in b/man/pw-profiler.1.rst.in index 119d8ceea4ca8004881be87ebe296a1bd8a9d005..6fb57c8eb29856b6c56366592b43099f4290acf1 100644 --- a/man/pw-profiler.1.rst.in +++ b/man/pw-profiler.1.rst.in @@ -27,6 +27,8 @@ When this program is stopped, a set of **gnuplot** files and a script to generat SVG files from the .plot files is generated, along with a .html file to visualize the profiling results in a browser. +This function uses the same data used by *pw-top*. + OPTIONS ======= @@ -52,3 +54,4 @@ SEE ALSO ======== ``pipewire(1)``, +``pw-top(1)``, diff --git a/man/pw-top.1.rst.in b/man/pw-top.1.rst.in index 36625e308f3e2b16f49c0e457217b9cfcb384ca9..0e3a4854e7eb5e66e41c1294f431830e84ccd61f 100644 --- a/man/pw-top.1.rst.in +++ b/man/pw-top.1.rst.in @@ -61,7 +61,7 @@ RATE The current rate (for drivers) and the suggested rate for follower nodes. - This is the rate at which the graph processes data and needs to be combined with + This is the rate at which the *graph* processes data and needs to be combined with the QUANT value to derive the duration of a processing cycle in the graph. Some nodes can have a 0 RATE, which means that they don't have any rate @@ -72,7 +72,8 @@ RATE The RATE on (audio) driver nodes usually also translates directly to the samplerate used by the device. Although some devices might not be able to operate at the given samplerate, in which case resampling will need to be - done. + done. The negotiated samplerate with the device and stream can be found in + the FORMAT column. WAIT The waiting time of a node is the elapsed time between when the node @@ -125,6 +126,19 @@ ERR Xruns for followers are incremented when the node started processing but did not complete before the end of the graph cycle deadline. +FORMAT + The format used by the driver node or the stream. This is the hardware format + negotiated with the device or stream. + + If the stream of driver has a different rate than the graph, resampling will + be done. + + For raw audio formats, the layout is <sampleformat> <channels> <samplerate>. + + For DSD formats, the layout is <dsd-rate> <channels>. + + For Video formats, the layout is <pixelformat> <width>x<height>. + NAME Name assigned to the device/node, as found in *pw-dump* node.name @@ -154,4 +168,7 @@ SEE ALSO ======== ``pipewire(1)``, +``pw-dump(1)``, +``pw-cli(1)``, +``pw-profiler(1)``, diff --git a/meson.build b/meson.build index 972a55771478f794c3299c85e3a746429736bc2a..276d21456c285038f9fcc86b28baaeb4788e2ce6 100644 --- a/meson.build +++ b/meson.build @@ -1,9 +1,9 @@ project('pipewire', ['c' ], - version : '0.3.52', + version : '0.3.59', license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ], meson_version : '>= 0.59.0', default_options : [ 'warning_level=3', - 'c_std=gnu99', + 'c_std=gnu11', 'cpp_std=c++17', 'b_pie=true', #'b_sanitize=address,undefined', @@ -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..63b5e21af3ce5891e7a0c50a24270ba3dbde9fa6 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -116,6 +116,14 @@ 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('bluez5-codec-lc3', + description: 'Enable LC3 open source codec implementation', + type: 'feature', + value: 'disabled') option('control', description: 'Enable control spa plugin integration', type: 'feature', @@ -245,3 +253,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..d2ef0fb6260224387d0832f52c1a8d177e9418eb 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> @@ -44,6 +44,15 @@ #include <pipewire/pipewire.h> +#define ATOMIC_INC(s) __atomic_add_fetch(&(s), 1, __ATOMIC_SEQ_CST) +#define ATOMIC_LOAD(s) __atomic_load_n(&(s), __ATOMIC_SEQ_CST) + +#define SEQ_WRITE(s) ATOMIC_INC(s) +#define SEQ_WRITE_SUCCESS(s1,s2) ((s1) + 1 == (s2) && ((s2) & 1) == 0) + +#define SEQ_READ(s) ATOMIC_LOAD(s) +#define SEQ_READ_SUCCESS(s1,s2) ((s1) == (s2) && ((s2) & 1) == 0) + PW_LOG_TOPIC_STATIC(alsa_log_topic, "alsa.pcm"); #define PW_LOG_TOPIC_DEFAULT alsa_log_topic @@ -55,9 +64,32 @@ PW_LOG_TOPIC_STATIC(alsa_log_topic, "alsa.pcm"); #define MIN_PERIOD 64 +#define MIN_PERIOD_BYTES (128) +#define MAX_PERIOD_BYTES (2*1024*1024) + +#define MIN_BUFFER_BYTES (2*MIN_PERIOD_BYTES) +#define MAX_BUFFER_BYTES (2*MAX_PERIOD_BYTES) + +struct params { + const char *node_name; + const char *server_name; + const char *playback_node; + const char *capture_node; + const char *role; + snd_pcm_format_t format; + int rate; + int channels; + int period_bytes; + int buffer_bytes; + uint32_t flags; +}; + typedef struct { snd_pcm_ioplug_t io; + snd_output_t *output; + FILE *log_file; + char *node_name; char *target; char *role; @@ -69,6 +101,7 @@ typedef struct { unsigned int draining:1; unsigned int xrun_detected:1; unsigned int hw_params_changed:1; + unsigned int active:1; snd_pcm_uframes_t hw_ptr; snd_pcm_uframes_t boundary; @@ -89,53 +122,62 @@ typedef struct { struct pw_stream *stream; struct spa_hook stream_listener; - struct pw_time time; + int64_t delay; + uint64_t now; + uintptr_t seq; struct spa_audio_info_raw format; } snd_pcm_pipewire_t; static int snd_pcm_pipewire_stop(snd_pcm_ioplug_t *io); -static int block_check(snd_pcm_ioplug_t *io) +static int check_active(snd_pcm_ioplug_t *io) { snd_pcm_pipewire_t *pw = io->private_data; snd_pcm_sframes_t avail; - uint64_t val; + bool active; avail = snd_pcm_ioplug_avail(io, pw->hw_ptr, io->appl_ptr); - if (avail >= 0 && avail < (snd_pcm_sframes_t)pw->min_avail) { - spa_system_eventfd_read(pw->system, io->poll_fd, &val); - return 1; + + if (io->state == SND_PCM_STATE_DRAINING) { + active = pw->drained; } - return 0; + else if (avail >= 0 && avail < (snd_pcm_sframes_t)pw->min_avail) { + active = false; + } + else if (avail >= (snd_pcm_sframes_t)pw->min_avail) { + active = true; + } else { + active = false; + } + if (pw->active != active) { + pw_log_trace("%p: avail:%lu min-avail:%lu state:%s hw:%lu appl:%lu active:%d->%d state:%s", + pw, avail, pw->min_avail, snd_pcm_state_name(io->state), + pw->hw_ptr, io->appl_ptr, pw->active, active, + snd_pcm_state_name(io->state)); + } + return active; } -static int pcm_poll_block_check(snd_pcm_ioplug_t *io) + +static int update_active(snd_pcm_ioplug_t *io) { snd_pcm_pipewire_t *pw = io->private_data; + bool active; - if (io->state == SND_PCM_STATE_DRAINING) { + active = check_active(io); + + if (pw->active != active) { uint64_t val; - spa_system_eventfd_read(pw->system, io->poll_fd, &val); - return 0; - } else if (io->state == SND_PCM_STATE_RUNNING || - (io->state == SND_PCM_STATE_PREPARED && io->stream == SND_PCM_STREAM_CAPTURE)) { - return block_check(io); - } - return 0; -} -static inline int pcm_poll_unblock_check(snd_pcm_ioplug_t *io) -{ - snd_pcm_pipewire_t *pw = io->private_data; - snd_pcm_uframes_t avail; + pw->active = active; - avail = snd_pcm_ioplug_avail(io, pw->hw_ptr, io->appl_ptr); - if (avail >= pw->min_avail || io->state == SND_PCM_STATE_DRAINING) { - spa_system_eventfd_write(pw->system, pw->fd, 1); - return 1; + if (active) + spa_system_eventfd_write(pw->system, io->poll_fd, 1); + else + spa_system_eventfd_read(pw->system, io->poll_fd, &val); } - return 0; + return active; } static void snd_pcm_pipewire_free(snd_pcm_pipewire_t *pw) @@ -143,7 +185,7 @@ static void snd_pcm_pipewire_free(snd_pcm_pipewire_t *pw) if (pw == NULL) return; - pw_log_debug("%p:", pw); + pw_log_debug("%p: free", pw); if (pw->main_loop) pw_thread_loop_stop(pw->main_loop); if (pw->stream) @@ -156,13 +198,16 @@ static void snd_pcm_pipewire_free(snd_pcm_pipewire_t *pw) pw_thread_loop_destroy(pw->main_loop); free(pw->node_name); free(pw->target); + free(pw->role); + snd_output_close(pw->output); + fclose(pw->log_file); free(pw); } static int snd_pcm_pipewire_close(snd_pcm_ioplug_t *io) { snd_pcm_pipewire_t *pw = io->private_data; - pw_log_debug("%p:", pw); + pw_log_debug("%p: close", pw); snd_pcm_pipewire_free(pw); return 0; } @@ -170,7 +215,7 @@ static int snd_pcm_pipewire_close(snd_pcm_ioplug_t *io) static int snd_pcm_pipewire_poll_descriptors(snd_pcm_ioplug_t *io, struct pollfd *pfds, unsigned int space) { snd_pcm_pipewire_t *pw = io->private_data; - pcm_poll_unblock_check(io); /* unblock socket for polling if needed */ + update_active(io); pfds->fd = pw->fd; pfds->events = POLLIN | POLLERR | POLLNVAL; return 1; @@ -188,7 +233,7 @@ static int snd_pcm_pipewire_poll_revents(snd_pcm_ioplug_t *io, return pw->error; *revents = pfds[0].revents & ~(POLLIN | POLLOUT); - if (pfds[0].revents & POLLIN && !pcm_poll_block_check(io)) + if (pfds[0].revents & POLLIN && check_active(io)) *revents |= (io->stream == SND_PCM_STREAM_PLAYBACK) ? POLLOUT : POLLIN; return 0; @@ -213,29 +258,40 @@ static snd_pcm_sframes_t snd_pcm_pipewire_pointer(snd_pcm_ioplug_t *io) static int snd_pcm_pipewire_delay(snd_pcm_ioplug_t *io, snd_pcm_sframes_t *delayp) { snd_pcm_pipewire_t *pw = io->private_data; - int64_t elapsed = 0, filled, avail; + uintptr_t seq1, seq2; + int64_t elapsed = 0, delay, now, avail; + struct timespec ts; + int64_t diff; + + do { + seq1 = SEQ_READ(pw->seq); + + delay = pw->delay; + now = pw->now; + if (io->stream == SND_PCM_STREAM_PLAYBACK) + avail = snd_pcm_ioplug_hw_avail(io, pw->hw_ptr, io->appl_ptr); + else + avail = snd_pcm_ioplug_avail(io, pw->hw_ptr, io->appl_ptr); - if (pw->time.rate.num != 0) { - struct timespec ts; - int64_t diff; + seq2 = SEQ_READ(pw->seq); + } while (!SEQ_READ_SUCCESS(seq1, seq2)); + + if (now != 0 && (io->state == SND_PCM_STATE_RUNNING || + io->state == SND_PCM_STATE_DRAINING)) { clock_gettime(CLOCK_MONOTONIC, &ts); - diff = SPA_TIMESPEC_TO_NSEC(&ts) - pw->time.now; - elapsed = (pw->time.rate.denom * diff) / (pw->time.rate.num * SPA_NSEC_PER_SEC); - } - if (io->stream == SND_PCM_STREAM_PLAYBACK) - avail = snd_pcm_ioplug_hw_avail(io, pw->hw_ptr, io->appl_ptr); - else - avail = snd_pcm_ioplug_avail(io, pw->hw_ptr, io->appl_ptr); + diff = SPA_TIMESPEC_TO_NSEC(&ts) - now; + elapsed = (io->rate * diff) / SPA_NSEC_PER_SEC; - filled = pw->time.delay + avail; + if (io->stream == SND_PCM_STREAM_PLAYBACK) + delay -= SPA_MIN(elapsed, delay); + else + delay += SPA_MIN(elapsed, (int64_t)io->buffer_size); + } - if (io->stream == SND_PCM_STREAM_PLAYBACK) - *delayp = filled - SPA_MIN(elapsed, filled); - else - *delayp = filled + elapsed; + *delayp = delay + avail; - pw_log_trace("avail:%"PRIi64" filled %"PRIi64" elapsed:%"PRIi64" delay:%ld %lu %lu", - avail, filled, elapsed, *delayp, pw->hw_ptr, io->appl_ptr); + pw_log_trace("avail:%"PRIi64" filled %"PRIi64" elapsed:%"PRIi64" delay:%ld hw:%lu appl:%lu", + avail, delay, elapsed, *delayp, pw->hw_ptr, io->appl_ptr); return 0; } @@ -388,21 +444,20 @@ static void on_stream_process(void *data) snd_pcm_ioplug_t *io = &pw->io; struct pw_buffer *b; snd_pcm_uframes_t hw_avail, before, want, xfer; + struct pw_time pwt; + int64_t delay; - pw_stream_get_time_n(pw->stream, &pw->time, sizeof(pw->time)); + pw_stream_get_time_n(pw->stream, &pwt, sizeof(pwt)); - if (pw->time.rate.num != 0) { - pw->time.delay = pw->time.delay * io->rate * pw->time.rate.num / pw->time.rate.denom; - pw->time.rate.denom = io->rate; - pw->time.rate.num = 1; + delay = pwt.delay; + if (pwt.rate.num != 0) { + delay = delay * io->rate * pwt.rate.num / pwt.rate.denom; } before = hw_avail = snd_pcm_ioplug_hw_avail(io, pw->hw_ptr, io->appl_ptr); - if (pw->drained) { - pcm_poll_unblock_check(io); /* unblock socket for polling if needed */ - return; - } + if (pw->drained) + goto done; b = pw_stream_dequeue_buffer(pw->stream); if (b == NULL) @@ -410,15 +465,20 @@ static void on_stream_process(void *data) want = b->requested ? b->requested : hw_avail; - xfer = snd_pcm_pipewire_process(pw, b, &hw_avail, want); + SEQ_WRITE(pw->seq); - pw_log_trace("%p: avail-before:%lu avail:%lu want:%lu xfer:%lu", - pw, before, hw_avail, want, xfer); + xfer = snd_pcm_pipewire_process(pw, b, &hw_avail, want); + pw->delay = delay; + /* the buffer is now queued in the stream and consumed */ if (io->stream == SND_PCM_STREAM_PLAYBACK) - pw->time.delay += xfer; - else - pw->time.delay -= SPA_MIN(pw->time.delay, (int64_t)xfer); + pw->delay += xfer; + + pw->now = pwt.now; + SEQ_WRITE(pw->seq); + + pw_log_trace("%p: avail-before:%lu avail:%lu want:%lu xfer:%lu hw:%lu appl:%lu", + pw, before, hw_avail, want, xfer, pw->hw_ptr, io->appl_ptr); pw_stream_queue_buffer(pw->stream, b); @@ -431,7 +491,8 @@ static void on_stream_process(void *data) pw->drained = false; } } - pcm_poll_unblock_check(io); /* unblock socket for polling if needed */ +done: + update_active(io); } static const struct pw_stream_events stream_events = { @@ -484,6 +545,8 @@ static int snd_pcm_pipewire_prepare(snd_pcm_ioplug_t *io) if (snd_pcm_sw_params_current(io->pcm, swparams) == 0) { snd_pcm_sw_params_get_avail_min(swparams, &pw->min_avail); snd_pcm_sw_params_get_boundary(swparams, &pw->boundary); + snd_pcm_sw_params_dump(swparams, pw->output); + fflush(pw->log_file); } else { pw->min_avail = io->period_size; pw->boundary = io->buffer_size; @@ -492,8 +555,10 @@ static int snd_pcm_pipewire_prepare(snd_pcm_ioplug_t *io) min_period = (MIN_PERIOD * io->rate / 48000); pw->min_avail = SPA_MAX(pw->min_avail, min_period); - pw_log_debug("%p: prepare %d %p %lu %ld", pw, - pw->error, pw->stream, io->period_size, pw->min_avail); + pw_log_debug("%p: prepare error:%d stream:%p buffer-size:%lu " + "period-size:%lu min-avail:%ld", pw, pw->error, + pw->stream, io->buffer_size, io->period_size, pw->min_avail); + if (pw->error >= 0 && pw->stream != NULL && !pw->hw_params_changed) goto done; pw->hw_params_changed = false; @@ -550,6 +615,7 @@ static int snd_pcm_pipewire_prepare(snd_pcm_ioplug_t *io) done: pw->hw_ptr = 0; + pw->now = 0; pw->xrun_detected = false; pw->drained = false; pw->draining = false; @@ -568,9 +634,8 @@ static int snd_pcm_pipewire_start(snd_pcm_ioplug_t *io) snd_pcm_pipewire_t *pw = io->private_data; pw_thread_loop_lock(pw->main_loop); - pw_log_debug("%p:", pw); + pw_log_debug("%p: start", pw); pipewire_start(pw); - block_check(io); /* unblock socket for polling if needed */ pw_thread_loop_unlock(pw->main_loop); return 0; } @@ -579,8 +644,8 @@ static int snd_pcm_pipewire_stop(snd_pcm_ioplug_t *io) { snd_pcm_pipewire_t *pw = io->private_data; - pw_log_debug("%p:", pw); - pcm_poll_unblock_check(io); + pw_log_debug("%p: stop", pw); + update_active(io); pw_thread_loop_lock(pw->main_loop); if (pw->activated && pw->stream != NULL) { @@ -593,7 +658,7 @@ static int snd_pcm_pipewire_stop(snd_pcm_ioplug_t *io) static int snd_pcm_pipewire_pause(snd_pcm_ioplug_t * io, int enable) { - pw_log_debug("%p:", io); + pw_log_debug("%p: pause", io); if (enable) snd_pcm_pipewire_stop(io); @@ -646,6 +711,9 @@ static int snd_pcm_pipewire_hw_params(snd_pcm_ioplug_t * io, snd_pcm_pipewire_t *pw = io->private_data; bool planar; + snd_pcm_hw_params_dump(params, pw->output); + fflush(pw->log_file); + pw_log_debug("%p: hw_params buffer_size:%lu period_size:%lu", pw, io->buffer_size, io->period_size); switch(io->access) { @@ -726,6 +794,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 +815,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; } @@ -820,7 +890,10 @@ static int snd_pcm_pipewire_set_chmap(snd_pcm_ioplug_t * io, pw->format.channels = map->channels; for (i = 0; i < map->channels; i++) { pw->format.position[i] = chmap_to_channel(map->pos[i]); - pw_log_debug("map %d: %d %d", i, map->pos[i], pw->format.position[i]); + pw_log_debug("map %d: %s / %s", i, + snd_pcm_chmap_name(map->pos[i]), + spa_debug_type_find_short_name(spa_type_audio_channel, + pw->format.position[i])); } return 1; } @@ -890,8 +963,7 @@ static snd_pcm_ioplug_callback_t pipewire_pcm_callback = { .query_chmaps = snd_pcm_pipewire_query_chmaps, }; -static int pipewire_set_hw_constraint(snd_pcm_pipewire_t *pw, int rate, - snd_pcm_format_t format, int channels, int period_bytes) +static int pipewire_set_hw_constraint(snd_pcm_pipewire_t *pw, struct params *p) { unsigned int access_list[] = { SND_PCM_ACCESS_MMAP_INTERLEAVED, @@ -923,26 +995,38 @@ static int pipewire_set_hw_constraint(snd_pcm_pipewire_t *pw, int rate, int max_channels; int min_period_bytes; int max_period_bytes; + int min_buffer_bytes; + int max_buffer_bytes; int err; - if (rate > 0) { - min_rate = max_rate = rate; + if (p->rate > 0) { + min_rate = max_rate = SPA_CLAMP(p->rate, 1, MAX_RATE); } else { min_rate = 1; max_rate = MAX_RATE; } - if (channels > 0) { - min_channels = max_channels = channels; + if (p->channels > 0) { + min_channels = max_channels = SPA_CLAMP(p->channels, 1, MAX_CHANNELS); } else { min_channels = 1; max_channels = MAX_CHANNELS; } - if (period_bytes > 0) { - min_period_bytes = max_period_bytes = period_bytes; + if (p->period_bytes > 0) { + min_period_bytes = max_period_bytes = SPA_CLAMP(p->period_bytes, + MIN_PERIOD_BYTES, MAX_PERIOD_BYTES); + } else { + min_period_bytes = MIN_PERIOD_BYTES; + max_period_bytes = MAX_PERIOD_BYTES; + } + if (p->buffer_bytes > 0) { + min_buffer_bytes = max_buffer_bytes = SPA_CLAMP(p->buffer_bytes, + MIN_BUFFER_BYTES, MAX_BUFFER_BYTES); } else { - min_period_bytes = 128; - max_period_bytes = 2*1024*1024; + min_buffer_bytes = MIN_BUFFER_BYTES; + max_buffer_bytes = MAX_BUFFER_BYTES; } + if (min_period_bytes * 2 > max_buffer_bytes) + min_period_bytes = max_period_bytes = max_buffer_bytes / 2; if ((err = snd_pcm_ioplug_set_param_list(&pw->io, SND_PCM_IOPLUG_HW_ACCESS, SPA_N_ELEMENTS(access_list), access_list)) < 0 || @@ -951,22 +1035,22 @@ static int pipewire_set_hw_constraint(snd_pcm_pipewire_t *pw, int rate, (err = snd_pcm_ioplug_set_param_minmax(&pw->io, SND_PCM_IOPLUG_HW_RATE, min_rate, max_rate)) < 0 || (err = snd_pcm_ioplug_set_param_minmax(&pw->io, SND_PCM_IOPLUG_HW_BUFFER_BYTES, - MIN_BUFFERS*min_period_bytes, - MIN_BUFFERS*max_period_bytes)) < 0 || + min_buffer_bytes, + max_buffer_bytes)) < 0 || (err = snd_pcm_ioplug_set_param_minmax(&pw->io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, min_period_bytes, max_period_bytes)) < 0 || (err = snd_pcm_ioplug_set_param_minmax(&pw->io, SND_PCM_IOPLUG_HW_PERIODS, - MIN_BUFFERS, MAX_BUFFERS)) < 0) { + MIN_BUFFERS, 1024)) < 0) { pw_log_warn("Can't set param list: %s", snd_strerror(err)); return err; } - if (format != SND_PCM_FORMAT_UNKNOWN) { + if (p->format != SND_PCM_FORMAT_UNKNOWN) { err = snd_pcm_ioplug_set_param_list(&pw->io, SND_PCM_IOPLUG_HW_FORMAT, - 1, (unsigned int *)&format); + 1, (unsigned int *)&p->format); if (err < 0) { pw_log_warn("Can't set param list: %s", snd_strerror(err)); return err; @@ -994,7 +1078,7 @@ static void on_core_error(void *data, uint32_t id, int seq, int res, const char if (id == PW_ID_CORE) { pw->error = res; if (pw->fd != -1) - pcm_poll_unblock_check(&pw->io); + update_active(&pw->io); } pw_thread_loop_signal(pw->main_loop, false); } @@ -1004,51 +1088,96 @@ static const struct pw_core_events core_events = { .error = on_core_error, }; -static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, const char *name, - const char *node_name, - const char *server_name, - const char *playback_node, - const char *capture_node, - const char *role, - snd_pcm_stream_t stream, - int mode, - uint32_t flags, - int rate, - snd_pcm_format_t format, - int channels, - int period_bytes) + +static ssize_t log_write(void *cookie, const char *buf, size_t size) +{ + int len; + + while (size > 0) { + len = strcspn(buf, "\n"); + if (len > 0) + pw_log_debug("%.*s", (int)len, buf); + buf += len + 1; + size -= len + 1; + } + return size; +} + +static cookie_io_functions_t io_funcs = { + .write = log_write, +}; + +static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, + struct params *p, snd_pcm_stream_t stream, int mode) { snd_pcm_pipewire_t *pw; int err; const char *str; struct pw_properties *props = NULL; struct pw_loop *loop; + uint32_t val; assert(pcmp); pw = calloc(1, sizeof(*pw)); if (!pw) return -ENOMEM; + props = pw_properties_new(NULL, NULL); + if (props == NULL) { + err = -errno; + goto error; + } + + str = getenv("PIPEWIRE_ALSA"); + if (str != NULL) { + pw_properties_update_string(props, str, strlen(str)); + if ((str = pw_properties_get(props, "alsa.format"))) + p->format = snd_pcm_format_value(str); + if ((str = pw_properties_get(props, "alsa.rate")) && + spa_atou32(str, &val, 0)) + p->rate = val; + if ((str = pw_properties_get(props, "alsa.channels")) && + spa_atou32(str, &val, 0)) + p->channels = val; + if ((str = pw_properties_get(props, "alsa.period-bytes")) && + spa_atou32(str, &val, 0)) + p->period_bytes = val; + if ((str = pw_properties_get(props, "alsa.buffer-bytes")) && + spa_atou32(str, &val, 0)) + p->buffer_bytes = val; + } + str = getenv("PIPEWIRE_REMOTE"); if (str != NULL && str[0] != '\0') - server_name = str; + p->server_name = str; str = getenv("PIPEWIRE_NODE"); - pw_log_debug("%p: open %s %d %d %08x %d %s %d %d '%s'", pw, name, - stream, mode, flags, rate, - format != SND_PCM_FORMAT_UNKNOWN ? snd_pcm_format_name(format) : "none", - channels, period_bytes, str); + pw_log_debug("%p: open name:%s stream:%s mode:%d flags:%08x rate:%d format:%s " + "channels:%d period-bytes:%d buffer-bytes:%d target:'%s'", pw, p->node_name, + snd_pcm_stream_name(stream), mode, p->flags, p->rate, + snd_pcm_format_name(p->format), p->channels, p->period_bytes, + p->buffer_bytes, str); pw->fd = -1; pw->io.poll_fd = -1; - pw->flags = flags; + pw->flags = p->flags; + pw->log_file = fopencookie(pw, "w", io_funcs); + if (pw->log_file == NULL) { + pw_log_error("can't create log file: %m"); + err = -errno; + goto error; + } + if ((err = snd_output_stdio_attach(&pw->output, pw->log_file, 0)) < 0) { + pw_log_error("can't attach log file: %s", snd_strerror(err)); + goto error; + } - if (node_name == NULL) + if (p->node_name == NULL) pw->node_name = spa_aprintf("ALSA %s", stream == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture"); else - pw->node_name = strdup(node_name); + pw->node_name = strdup(p->node_name); if (pw->node_name == NULL) { err = -errno; @@ -1060,12 +1189,12 @@ static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, const char *name, pw->target = strdup(str); else { if (stream == SND_PCM_STREAM_PLAYBACK) - pw->target = playback_node ? strdup(playback_node) : NULL; + pw->target = p->playback_node ? strdup(p->playback_node) : NULL; else - pw->target = capture_node ? strdup(capture_node) : NULL; + pw->target = p->capture_node ? strdup(p->capture_node) : NULL; } - pw->role = (role && *role) ? strdup(role) : NULL; + pw->role = (p->role && *p->role) ? strdup(p->role) : NULL; pw->main_loop = pw_thread_loop_new("alsa-pipewire", NULL); if (pw->main_loop == NULL) { @@ -1083,13 +1212,11 @@ static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, const char *name, goto error; } - props = pw_properties_new(NULL, NULL); - pw_properties_setf(props, PW_KEY_APP_NAME, "PipeWire ALSA [%s]", pw_get_prgname()); - if (server_name) - pw_properties_set(props, PW_KEY_REMOTE_NAME, server_name); + if (p->server_name) + pw_properties_set(props, PW_KEY_REMOTE_NAME, p->server_name); if ((err = pw_thread_loop_start(pw->main_loop)) < 0) goto error; @@ -1121,20 +1248,21 @@ static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, const char *name, #endif pw->io.flags |= SND_PCM_IOPLUG_FLAG_MONOTONIC; - if ((err = snd_pcm_ioplug_create(&pw->io, name, stream, mode)) < 0) + if ((err = snd_pcm_ioplug_create(&pw->io, p->node_name, stream, mode)) < 0) goto error; - pw_log_debug("%p: open %s %d %d", pw, name, pw->io.stream, mode); - - if ((err = pipewire_set_hw_constraint(pw, rate, format, channels, - period_bytes)) < 0) + if ((err = pipewire_set_hw_constraint(pw, p)) < 0) goto error; + pw_log_debug("%p: opened name:%s stream:%s mode:%d", pw, p->node_name, + snd_pcm_stream_name(pw->io.stream), mode); + *pcmp = pw->io.pcm; return 0; error: + pw_log_debug("%p: failed to open %s :%s", pw, p->node_name, spa_strerror(err)); pw_properties_free(props); snd_pcm_pipewire_free(pw); return err; @@ -1145,16 +1273,7 @@ SPA_EXPORT SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) { snd_config_iterator_t i, next; - const char *node_name = NULL; - const char *server_name = NULL; - const char *playback_node = NULL; - const char *capture_node = NULL; - const char *role = NULL; - snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN; - int rate = 0; - int channels = 0; - int period_bytes = 0; - uint32_t flags = 0; + struct params params; int err; pw_init(NULL, NULL); @@ -1162,6 +1281,8 @@ SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) return -ENOTSUP; PW_LOG_TOPIC_INIT(alsa_log_topic); + spa_zero(params); + params.format = SND_PCM_FORMAT_UNKNOWN; snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); @@ -1171,35 +1292,35 @@ SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) if (spa_streq(id, "comment") || spa_streq(id, "type") || spa_streq(id, "hint")) continue; if (spa_streq(id, "name")) { - snd_config_get_string(n, &node_name); + snd_config_get_string(n, ¶ms.node_name); continue; } if (spa_streq(id, "server")) { - snd_config_get_string(n, &server_name); + snd_config_get_string(n, ¶ms.server_name); continue; } if (spa_streq(id, "playback_node")) { - snd_config_get_string(n, &playback_node); + snd_config_get_string(n, ¶ms.playback_node); continue; } if (spa_streq(id, "capture_node")) { - snd_config_get_string(n, &capture_node); + snd_config_get_string(n, ¶ms.capture_node); continue; } if (spa_streq(id, "role")) { - snd_config_get_string(n, &role); + snd_config_get_string(n, ¶ms.role); continue; } if (spa_streq(id, "exclusive")) { if (snd_config_get_bool(n)) - flags |= PW_STREAM_FLAG_EXCLUSIVE; + params.flags |= PW_STREAM_FLAG_EXCLUSIVE; continue; } if (spa_streq(id, "rate")) { long val; if (snd_config_get_integer(n, &val) == 0) - rate = val; + params.rate = val; else SNDERR("%s: invalid type", id); continue; @@ -1208,8 +1329,8 @@ SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) const char *str; if (snd_config_get_string(n, &str) == 0) { - format = snd_pcm_format_value(str); - if (format == SND_PCM_FORMAT_UNKNOWN) + params.format = snd_pcm_format_value(str); + if (*str && params.format == SND_PCM_FORMAT_UNKNOWN) SNDERR("%s: invalid value %s", id, str); } else { SNDERR("%s: invalid type", id); @@ -1220,7 +1341,7 @@ SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) long val; if (snd_config_get_integer(n, &val) == 0) - channels = val; + params.channels = val; else SNDERR("%s: invalid type", id); continue; @@ -1229,7 +1350,16 @@ SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) long val; if (snd_config_get_integer(n, &val) == 0) - period_bytes = val; + params.period_bytes = val; + else + SNDERR("%s: invalid type", id); + continue; + } + if (spa_streq(id, "buffer_bytes")) { + long val; + + if (snd_config_get_integer(n, &val) == 0) + params.buffer_bytes = val; else SNDERR("%s: invalid type", id); continue; @@ -1238,9 +1368,7 @@ SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) return -EINVAL; } - err = snd_pcm_pipewire_open(pcmp, name, node_name, server_name, playback_node, - capture_node, role, stream, mode, flags, rate, format, - channels, period_bytes); + err = snd_pcm_pipewire_open(pcmp, ¶ms, stream, mode); return err; } diff --git a/pipewire-alsa/conf/50-pipewire.conf b/pipewire-alsa/conf/50-pipewire.conf index f7e58472bda1e0b003c5a1046a762068b1e2d1f2..a3a08a61c98a00e71f2cde5b2c54369c7dd42330 100644 --- a/pipewire-alsa/conf/50-pipewire.conf +++ b/pipewire-alsa/conf/50-pipewire.conf @@ -4,9 +4,14 @@ defaults.pipewire.server "pipewire-0" defaults.pipewire.node "-1" defaults.pipewire.exclusive false defaults.pipewire.role "" +defaults.pipewire.rate 0 +defaults.pipewire.format "" +defaults.pipewire.channels 0 +defaults.pipewire.period_bytes 0 +defaults.pipewire.buffer_bytes 0 pcm.pipewire { - @args [ SERVER NODE EXCLUSIVE ROLE ] + @args [ SERVER NODE EXCLUSIVE ROLE RATE FORMAT CHANNELS PERIOD_BYTES BUFFER_BYTES ] @args.SERVER { type string default { @@ -35,7 +40,41 @@ pcm.pipewire { name defaults.pipewire.role } } - + @args.RATE { + type integer + default { + @func refer + name defaults.pipewire.rate + } + } + @args.FORMAT { + type string + default { + @func refer + name defaults.pipewire.format + } + } + @args.CHANNELS { + type integer + default { + @func refer + name defaults.pipewire.channels + } + } + @args.PERIOD_BYTES { + type integer + default { + @func refer + name defaults.pipewire.period_bytes + } + } + @args.BUFFER_BYTES { + type integer + default { + @func refer + name defaults.pipewire.buffer_bytes + } + } type pipewire server $SERVER @@ -43,6 +82,11 @@ pcm.pipewire { capture_node $NODE exclusive $EXCLUSIVE role $ROLE + rate $RATE + format $FORMAT + channels $CHANNELS + period_bytes $PERIOD_BYTES + buffer_bytes $BUFFER_BYTES hint { show on description "PipeWire Sound Server" 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-extensions.h b/pipewire-jack/src/pipewire-jack-extensions.h index f75f5d31e14bb4c0c971f47ec349eb820d80e5a7..8e38e9e6f001d411546aea9cd6c92f51a6140a8b 100644 --- a/pipewire-jack/src/pipewire-jack-extensions.h +++ b/pipewire-jack/src/pipewire-jack-extensions.h @@ -24,6 +24,7 @@ #ifndef PIPEWIRE_JACK_EXTENSIONS_H #define PIPEWIRE_JACK_EXTENSIONS_H +#include <stdint.h> #ifdef __cplusplus extern "C" { 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/hu.po b/po/hu.po index dcb2521679eab68102fdee5cde77befa6b23837e..7fb3b3d74944cb07fa3b6c878aa6eafd519272b9 100644 --- a/po/hu.po +++ b/po/hu.po @@ -1,18 +1,18 @@ -# Hungarian translation of PipeWire -# Copyright (C) 2012, 2016. Free Software Foundation, Inc. +# Hungarian translation for PipeWire. +# Copyright (C) 2012, 2016, 2022. Free Software Foundation, Inc. # This file is distributed under the same license as the PipeWire package. # -# KAMI <kami911@gmail.com>, 2012. +# KAMI <kami911 at gmail dot com>, 2012. # Gabor Kelemen <kelemeng at ubuntu dot com>, 2016. -# Balázs Úr <urbalazs at gmail dot com>, 2016. +# Balázs Úr <ur.balazs at fsf dot hu>, 2016, 2022. msgid "" msgstr "" "Project-Id-Version: PipeWire master\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: 2020-07-21 15:29+0000\n" -"Last-Translator: Balázs Meskó <meskobalazs@mailbox.org>\n" +"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" +"issues\n" +"POT-Creation-Date: 2022-09-15 15:26+0000\n" +"PO-Revision-Date: 2022-09-21 22:35+0200\n" +"Last-Translator: Balázs Úr <ur.balazs at fsf dot hu>\n" "Language-Team: Hungarian <https://translate.fedoraproject.org/projects/" "pipewire/pipewire/hu/>\n" "Language: hu\n" @@ -20,12 +20,9 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.1.1\n" -"X-Poedit-Language: Hungarian\n" -"X-Poedit-Country: HUNGARY\n" -"X-Poedit-SourceCharset: utf-8\n" +"X-Generator: Lokalize 19.12.3\n" -#: src/daemon/pipewire.c:43 +#: src/daemon/pipewire.c:46 #, c-format msgid "" "%s [options]\n" @@ -33,40 +30,64 @@ msgid "" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" +"%s [kapcsolók]\n" +" -h, --help Ezen súgó megjelenÃtése\n" +" --version Verzió megjelenÃtése\n" +" -c, --config BeállÃtás betöltése (alapérték: %s)\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" -msgstr "" +msgstr "PipeWire médiarendszer" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" -msgstr "" +msgstr "A PipeWire médiarendszer indÃtása" -#: src/examples/media-session/alsa-monitor.c:526 -#: spa/plugins/alsa/acp/compat.c:187 -msgid "Built-in Audio" -msgstr "BelsÅ‘ hangforrás" +#: 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 "Alagút ide: %s/%s" -#: src/examples/media-session/alsa-monitor.c:530 -#: spa/plugins/alsa/acp/compat.c:192 -msgid "Modem" -msgstr "Modem" +#: src/modules/module-fallback-sink.c:51 +msgid "Dummy Output" +msgstr "Üres kimenet" + +#: src/modules/module-pulse-tunnel.c:662 +#, c-format +msgid "Tunnel for %s@%s" +msgstr "Alagút ehhez: %s@%s" -#: src/examples/media-session/alsa-monitor.c:539 +#: src/modules/module-zeroconf-discover.c:332 msgid "Unknown device" -msgstr "" +msgstr "Ismeretlen eszköz" + +#: src/modules/module-zeroconf-discover.c:344 +#, c-format +msgid "%s on %s@%s" +msgstr "%s ezen: %s@%s" -#: src/tools/pw-cat.c:991 +#: src/modules/module-zeroconf-discover.c:348 +#, c-format +msgid "%s on %s" +msgstr "%s ezen: %s" + +#: 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 [kapcsolók] [<fájl>|-]\n" +" -h, --help Ezen súgó megjelenÃtése\n" +" --version Verzió megjelenÃtése\n" +" -v, --verbose Részletes műveletek engedélyezése\n" +"\n" -#: src/tools/pw-cat.c:998 +#: src/tools/pw-cat.c:791 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -80,11 +101,30 @@ 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 Távoli démon neve\n" +" --media-type MédiatÃpus beállÃtása (alapérték: " +"%s)\n" +" --media-category Médiakategória beállÃtása\n" +" (alapérték: %s)\n" +" --media-role Médiaszerep beállÃtása (alapérték: " +"%s)\n" +" --target Csomópont céljának beállÃtása\n" +" (alapérték: %s), a 0 azt jelenti,\n" +" hogy ne linkeljen\n" +" --latency Csomópont késleltetésének " +"beállÃtása\n" +" (alapérték: %s)\n" +" Xegység (egység = s, ms, us, ns)\n" +" vagy közvetlen minták (256)\n" +" a gyakoriság a forrásfájl egyike\n" +" -P --properties Csomópont tulajdonságainak " +"beállÃtása\n" +"\n" -#: src/tools/pw-cat.c:1016 +#: src/tools/pw-cat.c:809 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -103,16 +143,39 @@ msgid "" "%d)\n" "\n" msgstr "" +" --rate Mintavételi gyakoriság (kötelezÅ‘ a\n" +" rögzÃtéshez) (alapérték: %u)\n" +" --channels Csatornák száma (kötelezÅ‘ a\n" +" rögzÃtéshez) (alapérték: %u)\n" +" --channel-map Csatornaleképezés\n" +" ezek egyike: „stereoâ€, " +"„surround-51â€\n" +" stb. vagy csatornanevek vesszÅ‘vel\n" +" tagolt listája, például: „FL,FRâ€\n" +" --format Mintavételi formátum: %s (kötelezÅ‘ " +"a\n" +" rögzÃtéshez) (alapérték: %s)\n" +" --volume Adatfolyam hangereje 0-1.0\n" +" (alapérték: %.3f)\n" +" -q --quality Újramintavételezési minÅ‘ség (0-15)\n" +" (alapérték: %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 Lejátszási mód\n" +" -r, --record RögzÃtési mód\n" +" -m, --midi Midi mód\n" +" -d, --dsd DSD mód\n" +"\n" -#: src/tools/pw-cli.c:2932 +#: src/tools/pw-cli.c:2255 #, c-format msgid "" "%s [options] [command]\n" @@ -122,360 +185,353 @@ msgid "" " -r, --remote Remote daemon name\n" "\n" msgstr "" +"%s [kapcsolók] [parancs]\n" +" -h, --help Ezen súgó megjelenÃtése\n" +" --version Verzió megjelenÃtése\n" +" -d, --daemon IndÃtás démonként (alapérték: " +"hamis)\n" +" -r, --remote Távoli démon neve\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:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 +#: spa/plugins/bluez5/bluez5-device.c:1236 msgid "Off" -msgstr "Kikapcsolva" +msgstr "Ki" -#: spa/plugins/alsa/acp/channelmap.h:466 -msgid "(invalid)" -msgstr "(Érvénytelen)" - -#: spa/plugins/alsa/acp/alsa-mixer.c:2709 +#: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Bemenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2710 +#: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Dokkolóállomás bemenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2711 +#: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Dokkolóállomás mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2712 +#: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Dokkolóállomás vonalbemenet" -#: 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 "Vonalbemenet" -#: 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:1454 msgid "Microphone" msgstr "Mikrofon" -#: 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 "ElsÅ‘ mikrofon" +msgstr "ElülsÅ‘ mikrofon" -#: 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 "Hátsó mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2717 +#: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "KülsÅ‘ mikrofon" -#: 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 "BelsÅ‘ mikrofon" -#: 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 "Rádió" -#: 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 "Videó" -#: spa/plugins/alsa/acp/alsa-mixer.c:2721 +#: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Automatikus erÅ‘sÃtésszabályzás" -#: spa/plugins/alsa/acp/alsa-mixer.c:2722 +#: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Nincs automatikus erÅ‘sÃtésszabályzás" -#: spa/plugins/alsa/acp/alsa-mixer.c:2723 +#: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "ErÅ‘sÃtés" -#: spa/plugins/alsa/acp/alsa-mixer.c:2724 +#: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Nincs erÅ‘sÃtés" -#: spa/plugins/alsa/acp/alsa-mixer.c:2725 +#: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "ErÅ‘sÃtÅ‘" -#: spa/plugins/alsa/acp/alsa-mixer.c:2726 +#: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Nincs erÅ‘sÃtÅ‘" -#: spa/plugins/alsa/acp/alsa-mixer.c:2727 +#: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Basszuskiemelés" -#: spa/plugins/alsa/acp/alsa-mixer.c:2728 +#: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Nincs basszuskiemelés" -#: 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:1460 msgid "Speaker" msgstr "Hangszóró" -#: 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 "Analóg fejhallgató" +msgstr "Fejhallgató" -#: spa/plugins/alsa/acp/alsa-mixer.c:2797 +#: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Analóg bemenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2801 +#: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Dokkolóállomás mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2803 +#: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Fejhallgató mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2807 +#: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Analóg kimenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2809 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" -msgstr "Analóg fejhallgató" +msgstr "2. fejhallgató" -#: spa/plugins/alsa/acp/alsa-mixer.c:2810 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" -msgstr "Analóg mono kimenet" +msgstr "Fejhallató monó kimenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2811 +#: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Vonalkimenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2812 +#: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" -msgstr "Analóg mono kimenet" +msgstr "Analóg monó kimenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2813 +#: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Hangszórók" -#: 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 "Digitális kimenet (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2816 +#: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Digitális bemenet (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2817 +#: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Többcsatornás bemenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2818 +#: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Többcsatornás kimenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2819 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" -msgstr "%s kimenet" +msgstr "Játék kimenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2820 -#: spa/plugins/alsa/acp/alsa-mixer.c:2821 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:2763 +#: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" -msgstr "%s kimenet" +msgstr "Csevegés kimenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2822 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" -msgstr "%s bemenet" +msgstr "Csevegés bemenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2823 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" -msgstr "Virtuális térhatású nyelÅ‘" +msgstr "Virtuális térhatás 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4527 +#: spa/plugins/alsa/acp/alsa-mixer.c:4471 msgid "Analog Mono" -msgstr "Analóg mono" +msgstr "Analóg monó" -#: spa/plugins/alsa/acp/alsa-mixer.c:4528 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Analog Mono (Left)" -msgstr "Analóg mono" +msgstr "Analóg monó (bal)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4529 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Mono (Right)" -msgstr "Analóg mono" +msgstr "Analóg monó (jobb)" #. 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 "Analóg sztereó" -#: spa/plugins/alsa/acp/alsa-mixer.c:4531 +#: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Mono" -msgstr "Mono" +msgstr "Monó" -#: spa/plugins/alsa/acp/alsa-mixer.c:4532 +#: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Stereo" msgstr "Sztereó" -#: 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:1442 msgid "Headset" msgstr "Fejhallgató" -#: 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 "Hangszóró" +msgstr "Mikrofonos fejhallgató" -#: 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 "Többcsatornás" -#: spa/plugins/alsa/acp/alsa-mixer.c:4544 +#: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Analog Surround 2.1" msgstr "Analóg térhatású 2.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4545 +#: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Analog Surround 3.0" msgstr "Analóg térhatású 3.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4546 +#: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Analog Surround 3.1" msgstr "Analóg térhatású 3.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4547 +#: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Analog Surround 4.0" msgstr "Analóg térhatású 4.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4548 +#: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Analog Surround 4.1" msgstr "Analóg térhatású 4.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4549 +#: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Analog Surround 5.0" msgstr "Analóg térhatású 5.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4550 +#: spa/plugins/alsa/acp/alsa-mixer.c:4494 msgid "Analog Surround 5.1" msgstr "Analóg térhatású 5.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4551 +#: spa/plugins/alsa/acp/alsa-mixer.c:4495 msgid "Analog Surround 6.0" msgstr "Analóg térhatású 6.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4552 +#: spa/plugins/alsa/acp/alsa-mixer.c:4496 msgid "Analog Surround 6.1" msgstr "Analóg térhatású 6.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4553 +#: spa/plugins/alsa/acp/alsa-mixer.c:4497 msgid "Analog Surround 7.0" msgstr "Analóg térhatású 7.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4554 +#: spa/plugins/alsa/acp/alsa-mixer.c:4498 msgid "Analog Surround 7.1" msgstr "Analóg térhatású 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4555 +#: spa/plugins/alsa/acp/alsa-mixer.c:4499 msgid "Digital Stereo (IEC958)" msgstr "Digitális sztereó (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 "Digitális térhatású 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 "Digitális térhatású 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 "Digitális térhatású 5.1 (IEC958/DTS)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4559 +#: spa/plugins/alsa/acp/alsa-mixer.c:4503 msgid "Digital Stereo (HDMI)" -msgstr "Digitális térhatású (HDMI)" +msgstr "Digitális sztereó (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4560 +#: spa/plugins/alsa/acp/alsa-mixer.c:4504 msgid "Digital Surround 5.1 (HDMI)" msgstr "Digitális térhatású 5.1 (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4561 +#: spa/plugins/alsa/acp/alsa-mixer.c:4505 msgid "Chat" -msgstr "" +msgstr "Csevegés" -#: spa/plugins/alsa/acp/alsa-mixer.c:4562 +#: spa/plugins/alsa/acp/alsa-mixer.c:4506 msgid "Game" -msgstr "" +msgstr "Játék" -#: spa/plugins/alsa/acp/alsa-mixer.c:4696 +#: spa/plugins/alsa/acp/alsa-mixer.c:4640 msgid "Analog Mono Duplex" -msgstr "Analóg mono duplex" +msgstr "Analóg monó kétirányú" -#: spa/plugins/alsa/acp/alsa-mixer.c:4697 +#: spa/plugins/alsa/acp/alsa-mixer.c:4641 msgid "Analog Stereo Duplex" -msgstr "Analóg sztereó duplex" +msgstr "Analóg sztereó kétirányú" -#: spa/plugins/alsa/acp/alsa-mixer.c:4700 +#: spa/plugins/alsa/acp/alsa-mixer.c:4644 msgid "Digital Stereo Duplex (IEC958)" -msgstr "Analóg sztereó duplex (IEC958)" +msgstr "Digitális sztereó kétirányú (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4701 +#: spa/plugins/alsa/acp/alsa-mixer.c:4645 msgid "Multichannel Duplex" -msgstr "Többcsatornás duplex" +msgstr "Többcsatornás kétirányú" -#: spa/plugins/alsa/acp/alsa-mixer.c:4702 -#, fuzzy +#: spa/plugins/alsa/acp/alsa-mixer.c:4646 msgid "Stereo Duplex" -msgstr "Analóg sztereó duplex" +msgstr "Sztereó kétirányú" -#: spa/plugins/alsa/acp/alsa-mixer.c:4703 +#: spa/plugins/alsa/acp/alsa-mixer.c:4647 msgid "Mono Chat + 7.1 Surround" -msgstr "" +msgstr "Monó csevegés + 7.1 térhatású" -#: spa/plugins/alsa/acp/alsa-mixer.c:4806 +#: spa/plugins/alsa/acp/alsa-mixer.c:4754 #, c-format msgid "%s Output" msgstr "%s kimenet" -#: spa/plugins/alsa/acp/alsa-mixer.c:4813 +#: spa/plugins/alsa/acp/alsa-mixer.c:4761 #, c-format msgid "%s Input" msgstr "%s bemenet" -#: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 -#, fuzzy, c-format +#: 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 " "ms).\n" @@ -487,18 +543,18 @@ msgid_plural "" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" -"A „snd_pcm_avail()†függvény visszatérési értéke váratlanul nagy értékű: %lu " +"Az „snd_pcm_avail()†függvény különlegesen nagy értéket adott vissza: %lu " "bájt (%lu ms).\n" "Ez valószÃnűleg egy hiba eredménye az ALSA „%s†illesztÅ‘programban. Jelentse " "ezt a problémát az ALSA fejlesztÅ‘i felé." msgstr[1] "" -"A „snd_pcm_avail()†függvény visszatérési értéke váratlanul nagy értékű: %lu " +"Az „snd_pcm_avail()†függvény különlegesen nagy értéket adott vissza: %lu " "bájt (%lu ms).\n" "Ez valószÃnűleg egy hiba eredménye az ALSA „%s†illesztÅ‘programban. Jelentse " "ezt a problémát az ALSA fejlesztÅ‘i felé." -#: spa/plugins/alsa/acp/alsa-util.c:1241 -#, fuzzy, c-format +#: spa/plugins/alsa/acp/alsa-util.c:1253 +#, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" @@ -510,17 +566,17 @@ msgid_plural "" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" -"A „snd_pcm_delay()†függvény visszatérési értéke váratlanul nagy értékű: %li " +"Az „snd_pcm_delay()†függvény különlegesen nagy értéket adott vissza: %li " "bájt (%s%lu ms).\n" "Ez valószÃnűleg egy hiba eredménye az ALSA „%s†illesztÅ‘programban. Jelentse " "ezt a problémát az ALSA fejlesztÅ‘i felé." msgstr[1] "" -"A „snd_pcm_delay()†függvény visszatérési értéke váratlanul nagy értékű: %li " +"Az „snd_pcm_delay()†függvény különlegesen nagy értéket adott vissza: %li " "bájt (%s%lu ms).\n" "Ez valószÃnűleg egy hiba eredménye az ALSA „%s†illesztÅ‘programban. Jelentse " "ezt a problémát az ALSA fejlesztÅ‘i felé." -#: spa/plugins/alsa/acp/alsa-util.c:1288 +#: spa/plugins/alsa/acp/alsa-util.c:1300 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -528,13 +584,13 @@ msgid "" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" -"A „snd_pcm_avail_delay()†függvény furcsa értékeket adott vissza: a " +"Az „snd_pcm_avail_delay()†függvény furcsa értékeket adott vissza: a " "késleltetés (%lu) kisebb, mint az elérhetÅ‘ %lu.\n" "Ez valószÃnűleg egy hiba eredménye az ALSA „%s†illesztÅ‘programban. Jelentse " "ezt a problémát az ALSA fejlesztÅ‘i felé." -#: spa/plugins/alsa/acp/alsa-util.c:1331 -#, fuzzy, c-format +#: spa/plugins/alsa/acp/alsa-util.c:1343 +#, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" @@ -546,73 +602,114 @@ msgid_plural "" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" -"A „snd_pcm_mmap_begin()†függvény visszatérési értéke kivételesen nagy: %lu " -"bájt (%lu ms).\n" +"Az „snd_pcm_mmap_begin()†függvény különlegesen nagy értéket adott vissza: " +"%lu bájt (%lu ms).\n" "Ez valószÃnűleg egy hiba eredménye az ALSA „%s†illesztÅ‘programban. Jelentse " "ezt a problémát az ALSA fejlesztÅ‘i felé." msgstr[1] "" -"A „snd_pcm_mmap_begin()†függvény visszatérési értéke kivételesen nagy: %lu " -"bájt (%lu ms).\n" +"Az „snd_pcm_mmap_begin()†függvény különlegesen nagy értéket adott vissza: " +"%lu bájt (%lu ms).\n" "Ez valószÃnűleg egy hiba eredménye az ALSA „%s†illesztÅ‘programban. Jelentse " "ezt a problémát az ALSA fejlesztÅ‘i felé." -#: spa/plugins/bluez5/bluez5-device.c:1010 +#: spa/plugins/alsa/acp/channelmap.h:457 +msgid "(invalid)" +msgstr "(érvénytelen)" + +#: spa/plugins/alsa/acp/compat.c:189 +msgid "Built-in Audio" +msgstr "BeépÃtett hangforrás" + +#: spa/plugins/alsa/acp/compat.c:194 +msgid "Modem" +msgstr "Modem" + +#: spa/plugins/bluez5/bluez5-device.c:1247 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" -msgstr "" +msgstr "Hang átjáró (A2DP forrás és HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1033 +#: spa/plugins/bluez5/bluez5-device.c:1272 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" -msgstr "" +msgstr "Magas hűségű lejátszás (A2DP fogadó, %s kodek)" -#: spa/plugins/bluez5/bluez5-device.c:1035 +#: spa/plugins/bluez5/bluez5-device.c:1275 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" -msgstr "" +msgstr "Magas hűségű kétirányú (A2DP forrás/fogadó, %s kodek)" -#: spa/plugins/bluez5/bluez5-device.c:1041 +#: spa/plugins/bluez5/bluez5-device.c:1283 msgid "High Fidelity Playback (A2DP Sink)" -msgstr "" +msgstr "Magas hűségű lejátszás (A2DP fogadó)" -#: spa/plugins/bluez5/bluez5-device.c:1043 +#: spa/plugins/bluez5/bluez5-device.c:1285 msgid "High Fidelity Duplex (A2DP Source/Sink)" -msgstr "" +msgstr "Magas hűségű kétirányú (A2DP forrás/fogadó)" + +#: spa/plugins/bluez5/bluez5-device.c:1322 +#, c-format +msgid "High Fidelity Playback (BAP Sink, codec %s)" +msgstr "Magas hűségű lejátszás (BAP fogadó, %s kodek)" + +#: spa/plugins/bluez5/bluez5-device.c:1326 +#, c-format +msgid "High Fidelity Input (BAP Source, codec %s)" +msgstr "Magas hűségű bemenet (BAP forrás, %s kodek)" + +#: spa/plugins/bluez5/bluez5-device.c:1330 +#, c-format +msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" +msgstr "Magas hűségű kétirányú (BAP forrás/fogadó, %s kodek)" -#: spa/plugins/bluez5/bluez5-device.c:1070 +#: spa/plugins/bluez5/bluez5-device.c:1359 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" -msgstr "" +msgstr "Fejhallgató fejegység (HSP/HFP, %s kodek)" -#: spa/plugins/bluez5/bluez5-device.c:1074 +#: spa/plugins/bluez5/bluez5-device.c:1364 msgid "Headset Head Unit (HSP/HFP)" -msgstr "" - -# FIXME: utánanézni -#: spa/plugins/bluez5/bluez5-device.c:1140 +msgstr "Fejhallgató fejegység (HSP/HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:1443 +#: spa/plugins/bluez5/bluez5-device.c:1448 +#: spa/plugins/bluez5/bluez5-device.c:1455 +#: spa/plugins/bluez5/bluez5-device.c:1461 +#: spa/plugins/bluez5/bluez5-device.c:1467 +#: spa/plugins/bluez5/bluez5-device.c:1473 +#: spa/plugins/bluez5/bluez5-device.c:1479 +#: spa/plugins/bluez5/bluez5-device.c:1485 +#: spa/plugins/bluez5/bluez5-device.c:1491 msgid "Handsfree" msgstr "KihangosÃtó" -#: spa/plugins/bluez5/bluez5-device.c:1155 +#: spa/plugins/bluez5/bluez5-device.c:1449 +msgid "Handsfree (HFP)" +msgstr "KihangosÃtó (HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:1466 msgid "Headphone" -msgstr "Fülhallgató" +msgstr "Fejhallgató" -#: spa/plugins/bluez5/bluez5-device.c:1160 +#: spa/plugins/bluez5/bluez5-device.c:1472 msgid "Portable" msgstr "Hordozható" -#: spa/plugins/bluez5/bluez5-device.c:1165 +#: spa/plugins/bluez5/bluez5-device.c:1478 msgid "Car" msgstr "Autó" -#: spa/plugins/bluez5/bluez5-device.c:1170 +#: spa/plugins/bluez5/bluez5-device.c:1484 msgid "HiFi" msgstr "Hi-Fi" -#: spa/plugins/bluez5/bluez5-device.c:1175 +#: spa/plugins/bluez5/bluez5-device.c:1490 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:1181 -#, fuzzy +#: spa/plugins/bluez5/bluez5-device.c:1497 msgid "Bluetooth" -msgstr "Bluetooth bemenet" +msgstr "Bluetooth" + +#: spa/plugins/bluez5/bluez5-device.c:1498 +msgid "Bluetooth (HFP)" +msgstr "Bluetooth (HFP)" diff --git a/po/it.po b/po/it.po index f4a1c112a505d7d0073bb78826c3a2af0cb9ec29..bdd47ba8d7d530e92d9248f3f477272e200f882d 100644 --- a/po/it.po +++ b/po/it.po @@ -34,11 +34,11 @@ msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" -msgstr "Sistema Multimediale PipeWire" +msgstr "Sistema multimediale PipeWire" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" -msgstr "Avvia il Sistema Multimediale PipeWire" +msgstr "Avvia il sistema multimediale PipeWire" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 @@ -144,16 +144,16 @@ msgstr "Ingresso docking station" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" -msgstr "Microfono docking station" +msgstr "Microfono della docking station" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" -msgstr "Linea in docking station" +msgstr "Linea di ingresso nella docking station" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" -msgstr "Line-In" +msgstr "Linea di ingresso" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 @@ -258,7 +258,7 @@ msgstr "Uscita mono cuffie" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" -msgstr "Line-Out" +msgstr "Linea di uscita" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" @@ -350,7 +350,7 @@ msgstr "Vivavoce" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" -msgstr "Multi canale" +msgstr "Multicanale" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" @@ -442,7 +442,7 @@ msgstr "Duplex stereo digitale (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" -msgstr "Duplex multi canale" +msgstr "Duplex multicanale" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 msgid "Stereo Duplex" @@ -477,11 +477,11 @@ msgid_plural "" msgstr[0] "" "snd_pcm_avail() ha restituito un valore molto grande: %lu byte (%lu ms).\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " -"questo problema agli sviluppatori ALSA." +"questo problema ai suoi sviluppatori." msgstr[1] "" "snd_pcm_avail() ha restituito un valore molto grande: %lu byte (%lu ms).\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " -"questo problema agli sviluppatori ALSA." +"questo problema ai suoi sviluppatori." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, c-format @@ -498,11 +498,11 @@ msgid_plural "" msgstr[0] "" "snd_pcm_delay() ha restituito un valore molto grande: %li byte (%s%lu ms).\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " -"questo problema agli sviluppatori ALSA." +"questo problema ai suoi sviluppatori." msgstr[1] "" "snd_pcm_delay() ha restituito un valore molto grande: %li byte (%s%lu ms).\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " -"questo problema agli sviluppatori ALSA." +"questo problema ai suoi sviluppatori." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format @@ -515,7 +515,7 @@ msgstr "" "snd_pcm_avail() ha restituito dei valori strani: delay %lu è minore di avail " "%lu.\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " -"questo problema agli sviluppatori ALSA." +"questo problema ai suoi sviluppatori." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, c-format @@ -533,12 +533,12 @@ msgstr[0] "" "snd_pcm_mmap_begin() ha restituito un valore molto grande: %lu byte (%lu " "ms).\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " -"questo problema agli sviluppatori ALSA." +"questo problema ai suoi sviluppatori." msgstr[1] "" "snd_pcm_mmap_begin() ha restituito un valore molto grande: %lu byte (%lu " "ms).\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " -"questo problema agli sviluppatori ALSA." +"questo problema ai suoi sviluppatori." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" @@ -547,20 +547,20 @@ msgstr "Gateway Audio (Sorgente A2DP & HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" -msgstr "Riproduzione ad Alta Fedeltà (A2DP Sink, codec %s)" +msgstr "Riproduzione ad alta fedeltà (A2DP Sink, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" -msgstr "Duplex ad Alta Fedeltà (Sorgente/Sink, A2DP codec %s)" +msgstr "Duplex ad alta fedeltà (Sorgente/Sink, A2DP codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" -msgstr "Riproduzione ad Alta Fedeltà (A2DP Sink)" +msgstr "Riproduzione ad alta fedeltà (A2DP Sink)" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" -msgstr "Duplex ad Alta Fedeltà (A2DP Source/Sink)" +msgstr "Duplex ad alta fedeltà (A2DP Source/Sink)" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format @@ -573,7 +573,7 @@ msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" -msgstr "Sistema mani-libere" +msgstr "Vivavoce" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Headphone" 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..032979a9724d28bac04ab9b758f2c79e598b4d5b 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-09-15 15:26+0000\n" +"PO-Revision-Date: 2022-09-25 15:20+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:662 #, 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:1236 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:1454 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:1460 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:1442 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,92 @@ msgstr "Wbudowany dźwiÄ™k" msgid "Modem" msgstr "Modem" -#: spa/plugins/bluez5/bluez5-device.c:1172 +#: spa/plugins/bluez5/bluez5-device.c:1247 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:1272 #, 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:1275 #, 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:1283 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:1285 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:1322 +#, c-format +msgid "High Fidelity Playback (BAP Sink, codec %s)" +msgstr "Odtwarzanie o wysokiej dokÅ‚adnoÅ›ci (odpÅ‚yw BAP, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1326 +#, c-format +msgid "High Fidelity Input (BAP Source, codec %s)" +msgstr "WejÅ›cie o wysokiej dokÅ‚adnoÅ›ci (źródÅ‚o BAP, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1330 +#, c-format +msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" +msgstr "Dupleks o wysokiej dokÅ‚adnoÅ›ci (źródÅ‚o/odpÅ‚yw BAP, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1359 #, 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:1364 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:1443 +#: spa/plugins/bluez5/bluez5-device.c:1448 +#: spa/plugins/bluez5/bluez5-device.c:1455 +#: spa/plugins/bluez5/bluez5-device.c:1461 +#: spa/plugins/bluez5/bluez5-device.c:1467 +#: spa/plugins/bluez5/bluez5-device.c:1473 +#: spa/plugins/bluez5/bluez5-device.c:1479 +#: spa/plugins/bluez5/bluez5-device.c:1485 +#: spa/plugins/bluez5/bluez5-device.c:1491 msgid "Handsfree" msgstr "Zestaw gÅ‚oÅ›nomówiÄ…cy" -#: spa/plugins/bluez5/bluez5-device.c:1340 +#: spa/plugins/bluez5/bluez5-device.c:1449 +msgid "Handsfree (HFP)" +msgstr "Zestaw gÅ‚oÅ›nomówiÄ…cy (HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:1466 msgid "Headphone" msgstr "SÅ‚uchawki" -#: spa/plugins/bluez5/bluez5-device.c:1345 +#: spa/plugins/bluez5/bluez5-device.c:1472 msgid "Portable" msgstr "PrzenoÅ›ne" -#: spa/plugins/bluez5/bluez5-device.c:1350 +#: spa/plugins/bluez5/bluez5-device.c:1478 msgid "Car" msgstr "Samochód" -#: spa/plugins/bluez5/bluez5-device.c:1355 +#: spa/plugins/bluez5/bluez5-device.c:1484 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:1360 +#: spa/plugins/bluez5/bluez5-device.c:1490 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:1366 +#: spa/plugins/bluez5/bluez5-device.c:1497 msgid "Bluetooth" msgstr "Bluetooth" + +#: spa/plugins/bluez5/bluez5-device.c:1498 +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/alloc.h b/spa/include/spa/buffer/alloc.h index 6417073838d71ed03c9fede73078ed7113a677e2..ec855415323dc09305bb6464a2bf7b829270eb35 100644 --- a/spa/include/spa/buffer/alloc.h +++ b/spa/include/spa/buffer/alloc.h @@ -161,8 +161,9 @@ static inline int spa_buffer_alloc_fill_info(struct spa_buffer_alloc_info *info, *target += info->chunk_size; for (i = 0, size = 0; i < n_datas; i++) { + int64_t align = data_aligns[i]; info->max_align = SPA_MAX(info->max_align, data_aligns[i]); - size = SPA_ROUND_UP_N(size, data_aligns[i]); + size = SPA_ROUND_UP_N(size, align); size += datas[i].maxsize; } info->data_size = size; 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/buffer/meta.h b/spa/include/spa/buffer/meta.h index ae72ef970e5dc13a3b967f57e7398163fbd6c4ce..e270c56cacbaf13d9d9a93b5f1cc2ae127b067f4 100644 --- a/spa/include/spa/buffer/meta.h +++ b/spa/include/spa/buffer/meta.h @@ -64,9 +64,15 @@ struct spa_meta { void *data; /**< pointer to metadata */ }; -#define spa_meta_first(m) ((m)->data) -#define spa_meta_end(m) SPA_PTROFF((m)->data,(m)->size,void) -#define spa_meta_check(p,m) (SPA_PTROFF(p,sizeof(*p),void) <= spa_meta_end(m)) +static inline void *spa_meta_first(const struct spa_meta *m) { + return m->data; +} +#define spa_meta_first spa_meta_first +static inline void *spa_meta_end(const struct spa_meta *m) { + return SPA_PTROFF(m->data,m->size,void); +} +#define spa_meta_end spa_meta_end +#define spa_meta_check(p,m) (SPA_PTROFF(p,sizeof(*(p)),void) <= spa_meta_end(m)) /** * Describes essential buffer header metadata such as flags and @@ -92,11 +98,14 @@ struct spa_meta_region { struct spa_region region; }; -#define spa_meta_region_is_valid(m) ((m)->region.size.width != 0 && (m)->region.size.height != 0) +static inline bool spa_meta_region_is_valid(const struct spa_meta_region *m) { + return m->region.size.width != 0 && m->region.size.height != 0; +} +#define spa_meta_region_is_valid spa_meta_region_is_valid /** iterate all the items in a metadata */ #define spa_meta_for_each(pos,meta) \ - for (pos = (__typeof(pos))spa_meta_first(meta); \ + for ((pos) = (__typeof(pos))spa_meta_first(meta); \ spa_meta_check(pos, meta); \ (pos)++) diff --git a/spa/include/spa/debug/log.h b/spa/include/spa/debug/log.h index 8311e95460ce0221cb60b5fe82c6c70a4fea6cd6..bfe2d5ff84dbd061b1086dd4d21183f5aef615bd 100644 --- a/spa/include/spa/debug/log.h +++ b/spa/include/spa/debug/log.h @@ -36,10 +36,10 @@ extern "C" { */ #ifndef spa_debug -#define spa_debug(fmt,...) ({ printf(fmt"\n", ## __VA_ARGS__); }) +#define spa_debug(fmt,...) ({ printf((fmt"\n"), ## __VA_ARGS__); }) #endif #ifndef spa_debugn -#define spa_debugn(fmt,...) ({ printf(fmt, ## __VA_ARGS__); }) +#define spa_debugn(fmt,...) ({ printf((fmt), ## __VA_ARGS__); }) #endif /** diff --git a/spa/include/spa/graph/graph.h b/spa/include/spa/graph/graph.h index ba402481c96a7ca62f5d6e443e7a5f15b35448c2..0e887cd3c6d0ad436cb7eb40c709c262351faa2b 100644 --- a/spa/include/spa/graph/graph.h +++ b/spa/include/spa/graph/graph.h @@ -121,12 +121,12 @@ struct spa_graph_node { int __res = 0; \ spa_callbacks_call_res(&(n)->callbacks, \ struct spa_graph_node_callbacks, __res, \ - method, version, ##__VA_ARGS__); \ + method, (version), ##__VA_ARGS__); \ __res; \ }) -#define spa_graph_node_process(n) spa_graph_node_call(n, process, 0, n) -#define spa_graph_node_reuse_buffer(n,p,i) spa_graph_node_call(n, reuse_buffer, 0, n, p, i) +#define spa_graph_node_process(n) spa_graph_node_call((n), process, 0, (n)) +#define spa_graph_node_reuse_buffer(n,p,i) spa_graph_node_call((n), reuse_buffer, 0, (n), (p), (i)) struct spa_graph_port { struct spa_list link; /**< link in node port list */ diff --git a/spa/include/spa/interfaces/audio/aec.h b/spa/include/spa/interfaces/audio/aec.h index 17e4e4e463f63657a36168cb11beea4309a718a8..f598aa35a25e97780843520b889296628a0951b7 100644 --- a/spa/include/spa/interfaces/audio/aec.h +++ b/spa/include/spa/interfaces/audio/aec.h @@ -36,7 +36,7 @@ extern "C" { #define SPA_TYPE_INTERFACE_AUDIO_AEC SPA_TYPE_INFO_INTERFACE_BASE "Audio:AEC" -#define SPA_VERSION_AUDIO_AEC 0 +#define SPA_VERSION_AUDIO_AEC 1 struct spa_audio_aec { struct spa_interface iface; const char *name; @@ -60,7 +60,7 @@ struct spa_audio_aec_events { }; struct spa_audio_aec_methods { -#define SPA_VERSION_AUDIO_AEC_METHODS 0 +#define SPA_VERSION_AUDIO_AEC_METHODS 1 uint32_t version; int (*add_listener) (void *object, @@ -68,18 +68,22 @@ 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); + /* since 0.3.58, version 1:1 */ + int (*activate) (void *object); + /* since 0.3.58, version 1:1 */ + int (*deactivate) (void *object); }; #define spa_audio_aec_method(o,method,version,...) \ ({ \ int _res = -ENOTSUP; \ - struct spa_audio_aec *_o = o; \ + struct spa_audio_aec *_o = (o); \ spa_interface_call_res(&_o->iface, \ struct spa_audio_aec_methods, _res, \ - method, version, ##__VA_ARGS__); \ + method, (version), ##__VA_ARGS__); \ _res; \ }) @@ -87,6 +91,8 @@ struct spa_audio_aec_methods { #define spa_audio_aec_init(o,...) spa_audio_aec_method(o, init, 0, __VA_ARGS__) #define spa_audio_aec_run(o,...) spa_audio_aec_method(o, run, 0, __VA_ARGS__) #define spa_audio_aec_set_props(o,...) spa_audio_aec_method(o, set_props, 0, __VA_ARGS__) +#define spa_audio_aec_activate(o) spa_audio_aec_method(o, activate, 1) +#define spa_audio_aec_deactivate(o) spa_audio_aec_method(o, deactivate, 1) #ifdef __cplusplus } /* extern "C" */ diff --git a/spa/include/spa/monitor/device.h b/spa/include/spa/monitor/device.h index 481874d184130342c7ecc16b8fb434fa56b71bd8..59fea51484c03ddba5d1ac03ac67fd82ebae7116 100644 --- a/spa/include/spa/monitor/device.h +++ b/spa/include/spa/monitor/device.h @@ -71,7 +71,7 @@ struct spa_device_info { uint32_t n_params; /**< number of elements in params */ }; -#define SPA_DEVICE_INFO_INIT() (struct spa_device_info){ SPA_VERSION_DEVICE_INFO, } +#define SPA_DEVICE_INFO_INIT() ((struct spa_device_info){ SPA_VERSION_DEVICE_INFO, }) /** * Information about a device object @@ -92,7 +92,7 @@ struct spa_device_object_info { const struct spa_dict *props; /**< extra object properties */ }; -#define SPA_DEVICE_OBJECT_INFO_INIT() (struct spa_device_object_info){ SPA_VERSION_DEVICE_OBJECT_INFO, } +#define SPA_DEVICE_OBJECT_INFO_INIT() ((struct spa_device_object_info){ SPA_VERSION_DEVICE_OBJECT_INFO, }) /** the result of spa_device_enum_params() */ #define SPA_RESULT_TYPE_DEVICE_PARAMS 1 @@ -243,10 +243,10 @@ struct spa_device_methods { #define spa_device_method(o,method,version,...) \ ({ \ int _res = -ENOTSUP; \ - struct spa_device *_o = o; \ + struct spa_device *_o = (o); \ spa_interface_call_res(&_o->iface, \ struct spa_device_methods, _res, \ - method, version, ##__VA_ARGS__); \ + method, (version), ##__VA_ARGS__); \ _res; \ }) diff --git a/spa/include/spa/node/io.h b/spa/include/spa/node/io.h index 74635c79a5c8ee0972ed92d1db0c64795492ed01..9211f65977e45f9b788d517ac4adf687eec2dafa 100644 --- a/spa/include/spa/node/io.h +++ b/spa/include/spa/node/io.h @@ -100,7 +100,7 @@ struct spa_io_buffers { uint32_t buffer_id; /**< a buffer id */ }; -#define SPA_IO_BUFFERS_INIT (struct spa_io_buffers) { SPA_STATUS_OK, SPA_ID_INVALID, } +#define SPA_IO_BUFFERS_INIT ((struct spa_io_buffers) { SPA_STATUS_OK, SPA_ID_INVALID, }) /** * IO area to exchange a memory region @@ -110,7 +110,7 @@ struct spa_io_memory { uint32_t size; /**< the size of \a data */ void *data; /**< a memory pointer */ }; -#define SPA_IO_MEMORY_INIT (struct spa_io_memory) { SPA_STATUS_OK, 0, NULL, } +#define SPA_IO_MEMORY_INIT ((struct spa_io_memory) { SPA_STATUS_OK, 0, NULL, }) /** A range, suitable for input ports that can suggest a range to output ports */ struct spa_io_range { diff --git a/spa/include/spa/node/node.h b/spa/include/spa/node/node.h index f32adec16cd1e2563a93aa627c66e2eb3e6fcfc6..6efa6d01fd75ab3139ed163167c74fc902af8e9b 100644 --- a/spa/include/spa/node/node.h +++ b/spa/include/spa/node/node.h @@ -85,7 +85,7 @@ struct spa_node_info { uint32_t n_params; /**< number of items in \a params */ }; -#define SPA_NODE_INFO_INIT() (struct spa_node_info) { 0, } +#define SPA_NODE_INFO_INIT() ((struct spa_node_info) { 0, }) /** * Port information structure @@ -124,7 +124,7 @@ struct spa_port_info { uint32_t n_params; /**< number of items in \a params */ }; -#define SPA_PORT_INFO_INIT() (struct spa_port_info) { 0, } +#define SPA_PORT_INFO_INIT() ((struct spa_port_info) { 0, }) #define SPA_RESULT_TYPE_NODE_ERROR 1 #define SPA_RESULT_TYPE_NODE_PARAMS 2 @@ -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/dsd.h b/spa/include/spa/param/audio/dsd.h index 3228f2565df89c1c4ea8351dae04d9c511f0b8ee..3b317f2ed44e989d804441697e780ce1e295894a 100644 --- a/spa/include/spa/param/audio/dsd.h +++ b/spa/include/spa/param/audio/dsd.h @@ -68,7 +68,7 @@ struct spa_audio_info_dsd { uint32_t position[SPA_AUDIO_MAX_CHANNELS]; /*< channel position from enum spa_audio_channel */ }; -#define SPA_AUDIO_INFO_DSD_INIT(...) (struct spa_audio_info_dsd) { __VA_ARGS__ } +#define SPA_AUDIO_INFO_DSD_INIT(...) ((struct spa_audio_info_dsd) { __VA_ARGS__ }) /** * \} diff --git a/spa/include/spa/param/audio/iec958.h b/spa/include/spa/param/audio/iec958.h index fb46c561e1be4db3266de6782a9ee7c52aa5ec75..94451387fce5d6944764b94e0a6e654f4ae6fa6d 100644 --- a/spa/include/spa/param/audio/iec958.h +++ b/spa/include/spa/param/audio/iec958.h @@ -56,7 +56,7 @@ struct spa_audio_info_iec958 { uint32_t rate; /*< sample rate */ }; -#define SPA_AUDIO_INFO_IEC958_INIT(...) (struct spa_audio_info_iec958) { __VA_ARGS__ } +#define SPA_AUDIO_INFO_IEC958_INIT(...) ((struct spa_audio_info_iec958) { __VA_ARGS__ }) /** * \} 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..5690db1db0cce703d2d5895f938b46c09eacdd39 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 @@ -294,7 +294,7 @@ struct spa_audio_info_raw { uint32_t position[SPA_AUDIO_MAX_CHANNELS]; /*< channel position from enum spa_audio_channel */ }; -#define SPA_AUDIO_INFO_RAW_INIT(...) (struct spa_audio_info_raw) { __VA_ARGS__ } +#define SPA_AUDIO_INFO_RAW_INIT(...) ((struct spa_audio_info_raw) { __VA_ARGS__ }) #define SPA_KEY_AUDIO_FORMAT "audio.format" /**< an audio format as string, * Ex. "S16LE" */ @@ -311,7 +311,7 @@ struct spa_audio_info_dsp { enum spa_audio_format format; /*< format, one of the DSP formats in enum spa_audio_format_dsp */ }; -#define SPA_AUDIO_INFO_DSP_INIT(...) (struct spa_audio_info_dsp) { __VA_ARGS__ } +#define SPA_AUDIO_INFO_DSP_INIT(...) ((struct spa_audio_info_dsp) { __VA_ARGS__ }) /** * \} diff --git a/spa/include/spa/param/bluetooth/audio.h b/spa/include/spa/param/bluetooth/audio.h index 5c215b411f273e88f3b80f81fef4236f27bb3493..27f3197178501ce03a2469cf4b35db05f6da235d 100644 --- a/spa/include/spa/param/bluetooth/audio.h +++ b/spa/include/spa/param/bluetooth/audio.h @@ -49,10 +49,18 @@ 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, SPA_BLUETOOTH_AUDIO_CODEC_MSBC, + + /* BAP */ + SPA_BLUETOOTH_AUDIO_CODEC_LC3 = 0x200, }; /** diff --git a/spa/include/spa/param/bluetooth/type-info.h b/spa/include/spa/param/bluetooth/type-info.h index 0471dcce853f77d93ad2d1321051ee3cb6ddd78d..729bd58cefb9870337904d2084933577c61b5ec3 100644 --- a/spa/include/spa/param/bluetooth/type-info.h +++ b/spa/include/spa/param/bluetooth/type-info.h @@ -53,10 +53,17 @@ 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 }, + { SPA_BLUETOOTH_AUDIO_CODEC_LC3, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "lc3", NULL }, + { 0, 0, NULL, NULL }, }; diff --git a/spa/include/spa/param/latency-utils.h b/spa/include/spa/param/latency-utils.h index 57a6828813ef285a308ac249fc6687afa6f396e3..e072574c3c72caf9ee942283f7ddf79b6a153932 100644 --- a/spa/include/spa/param/latency-utils.h +++ b/spa/include/spa/param/latency-utils.h @@ -50,7 +50,7 @@ struct spa_latency_info { uint64_t max_ns; }; -#define SPA_LATENCY_INFO(dir,...) (struct spa_latency_info) { .direction = (dir), ## __VA_ARGS__ } +#define SPA_LATENCY_INFO(dir,...) ((struct spa_latency_info) { .direction = (dir), ## __VA_ARGS__ }) static inline int spa_latency_info_compare(const struct spa_latency_info *a, struct spa_latency_info *b) @@ -146,7 +146,7 @@ struct spa_process_latency_info { uint64_t ns; }; -#define SPA_PROCESS_LATENCY_INFO_INIT(...) (struct spa_process_latency_info) { __VA_ARGS__ } +#define SPA_PROCESS_LATENCY_INFO_INIT(...) ((struct spa_process_latency_info) { __VA_ARGS__ }) static inline int spa_process_latency_parse(const struct spa_pod *latency, struct spa_process_latency_info *info) diff --git a/spa/include/spa/param/param.h b/spa/include/spa/param/param.h index 6059ee3618b49e263e67c2535fa71f1f79f4ebf2..7a870ffea44260f3859e5c6f648dadf82371f567 100644 --- a/spa/include/spa/param/param.h +++ b/spa/include/spa/param/param.h @@ -75,7 +75,7 @@ struct spa_param_info { uint32_t padding[5]; }; -#define SPA_PARAM_INFO(id,flags) (struct spa_param_info){ (id), (flags) } +#define SPA_PARAM_INFO(id,flags) ((struct spa_param_info){ (id), (flags) }) /** properties for SPA_TYPE_OBJECT_ParamBuffers */ enum spa_param_buffers { diff --git a/spa/include/spa/param/video/raw.h b/spa/include/spa/param/video/raw.h index dae4e738eb6ce11aecf289d7077376fc8ad60fc0..47fbd19b3c14f6131b6c7090924f992e4dce169d 100644 --- a/spa/include/spa/param/video/raw.h +++ b/spa/include/spa/param/video/raw.h @@ -206,14 +206,14 @@ struct spa_video_info_raw { enum spa_video_color_primaries color_primaries; /**< color primaries. used to convert between R'G'B' and CIE XYZ */ }; -#define SPA_VIDEO_INFO_RAW_INIT(...) (struct spa_video_info_raw) { __VA_ARGS__ } +#define SPA_VIDEO_INFO_RAW_INIT(...) ((struct spa_video_info_raw) { __VA_ARGS__ }) struct spa_video_info_dsp { enum spa_video_format format; int64_t modifier; }; -#define SPA_VIDEO_INFO_DSP_INIT(...) (struct spa_video_info_dsp) { __VA_ARGS__ } +#define SPA_VIDEO_INFO_DSP_INIT(...) ((struct spa_video_info_dsp) { __VA_ARGS__ }) /** * \} diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index 624b543c401df2e7d50bd9c2df20cee332b19278..860673a7cef6d0e8f45b4f17071d8bc8acd71bfc 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -69,7 +69,7 @@ struct spa_pod_builder { struct spa_callbacks callbacks; }; -#define SPA_POD_BUILDER_INIT(buffer,size) (struct spa_pod_builder){ buffer, size, 0, {}, {} } +#define SPA_POD_BUILDER_INIT(buffer,size) ((struct spa_pod_builder){ (buffer), (size), 0, {}, {} }) static inline void spa_pod_builder_get_state(struct spa_pod_builder *builder, struct spa_pod_builder_state *state) @@ -143,8 +143,10 @@ static inline int spa_pod_builder_raw(struct spa_pod_builder *builder, const voi if (offset + size > builder->size) { res = -ENOSPC; - spa_callbacks_call_res(&builder->callbacks, struct spa_pod_builder_callbacks, res, - overflow, 0, offset + size); + if (offset <= builder->size) + spa_callbacks_call_res(&builder->callbacks, + struct spa_pod_builder_callbacks, res, + overflow, 0, offset + size); } if (res == 0 && data) memcpy(SPA_PTROFF(builder->data, offset, void), data, size); @@ -212,7 +214,7 @@ spa_pod_builder_primitive(struct spa_pod_builder *builder, const struct spa_pod return res; } -#define SPA_POD_INIT(size,type) (struct spa_pod) { size, type } +#define SPA_POD_INIT(size,type) ((struct spa_pod) { (size), (type) }) #define SPA_POD_INIT_None() SPA_POD_INIT(0, SPA_TYPE_None) @@ -229,7 +231,7 @@ static inline int spa_pod_builder_child(struct spa_pod_builder *builder, uint32_ return spa_pod_builder_raw(builder, &p, sizeof(p)); } -#define SPA_POD_INIT_Bool(val) (struct spa_pod_bool){ { sizeof(uint32_t), SPA_TYPE_Bool }, val ? 1 : 0, 0 } +#define SPA_POD_INIT_Bool(val) ((struct spa_pod_bool){ { sizeof(uint32_t), SPA_TYPE_Bool }, (val) ? 1 : 0, 0 }) static inline int spa_pod_builder_bool(struct spa_pod_builder *builder, bool val) { @@ -237,7 +239,7 @@ static inline int spa_pod_builder_bool(struct spa_pod_builder *builder, bool val return spa_pod_builder_primitive(builder, &p.pod); } -#define SPA_POD_INIT_Id(val) (struct spa_pod_id){ { sizeof(uint32_t), SPA_TYPE_Id }, (uint32_t)val, 0 } +#define SPA_POD_INIT_Id(val) ((struct spa_pod_id){ { sizeof(uint32_t), SPA_TYPE_Id }, (val), 0 }) static inline int spa_pod_builder_id(struct spa_pod_builder *builder, uint32_t val) { @@ -245,7 +247,7 @@ static inline int spa_pod_builder_id(struct spa_pod_builder *builder, uint32_t v return spa_pod_builder_primitive(builder, &p.pod); } -#define SPA_POD_INIT_Int(val) (struct spa_pod_int){ { sizeof(int32_t), SPA_TYPE_Int }, (int32_t)val, 0 } +#define SPA_POD_INIT_Int(val) ((struct spa_pod_int){ { sizeof(int32_t), SPA_TYPE_Int }, (val), 0 }) static inline int spa_pod_builder_int(struct spa_pod_builder *builder, int32_t val) { @@ -253,7 +255,7 @@ static inline int spa_pod_builder_int(struct spa_pod_builder *builder, int32_t v return spa_pod_builder_primitive(builder, &p.pod); } -#define SPA_POD_INIT_Long(val) (struct spa_pod_long){ { sizeof(int64_t), SPA_TYPE_Long }, (int64_t)val } +#define SPA_POD_INIT_Long(val) ((struct spa_pod_long){ { sizeof(int64_t), SPA_TYPE_Long }, (val) }) static inline int spa_pod_builder_long(struct spa_pod_builder *builder, int64_t val) { @@ -261,7 +263,7 @@ static inline int spa_pod_builder_long(struct spa_pod_builder *builder, int64_t return spa_pod_builder_primitive(builder, &p.pod); } -#define SPA_POD_INIT_Float(val) (struct spa_pod_float){ { sizeof(float), SPA_TYPE_Float }, val, 0 } +#define SPA_POD_INIT_Float(val) ((struct spa_pod_float){ { sizeof(float), SPA_TYPE_Float }, (val), 0 }) static inline int spa_pod_builder_float(struct spa_pod_builder *builder, float val) { @@ -269,7 +271,7 @@ static inline int spa_pod_builder_float(struct spa_pod_builder *builder, float v return spa_pod_builder_primitive(builder, &p.pod); } -#define SPA_POD_INIT_Double(val) (struct spa_pod_double){ { sizeof(double), SPA_TYPE_Double }, val } +#define SPA_POD_INIT_Double(val) ((struct spa_pod_double){ { sizeof(double), SPA_TYPE_Double }, (val) }) static inline int spa_pod_builder_double(struct spa_pod_builder *builder, double val) { @@ -277,7 +279,7 @@ static inline int spa_pod_builder_double(struct spa_pod_builder *builder, double return spa_pod_builder_primitive(builder, &p.pod); } -#define SPA_POD_INIT_String(len) (struct spa_pod_string){ { len, SPA_TYPE_String } } +#define SPA_POD_INIT_String(len) ((struct spa_pod_string){ { (len), SPA_TYPE_String } }) static inline int spa_pod_builder_write_string(struct spa_pod_builder *builder, const char *str, uint32_t len) @@ -307,7 +309,7 @@ static inline int spa_pod_builder_string(struct spa_pod_builder *builder, const return spa_pod_builder_string_len(builder, str ? str : "", len); } -#define SPA_POD_INIT_Bytes(len) (struct spa_pod_bytes){ { len, SPA_TYPE_Bytes } } +#define SPA_POD_INIT_Bytes(len) ((struct spa_pod_bytes){ { (len), SPA_TYPE_Bytes } }) static inline int spa_pod_builder_bytes(struct spa_pod_builder *builder, const void *bytes, uint32_t len) @@ -327,7 +329,7 @@ spa_pod_builder_reserve_bytes(struct spa_pod_builder *builder, uint32_t len) return SPA_POD_BODY(spa_pod_builder_deref(builder, offset)); } -#define SPA_POD_INIT_Pointer(type,value) (struct spa_pod_pointer){ { sizeof(struct spa_pod_pointer_body), SPA_TYPE_Pointer }, { type, 0, value } } +#define SPA_POD_INIT_Pointer(type,value) ((struct spa_pod_pointer){ { sizeof(struct spa_pod_pointer_body), SPA_TYPE_Pointer }, { (type), 0, (value) } }) static inline int spa_pod_builder_pointer(struct spa_pod_builder *builder, uint32_t type, const void *val) @@ -336,7 +338,7 @@ spa_pod_builder_pointer(struct spa_pod_builder *builder, uint32_t type, const vo return spa_pod_builder_primitive(builder, &p.pod); } -#define SPA_POD_INIT_Fd(fd) (struct spa_pod_fd){ { sizeof(int64_t), SPA_TYPE_Fd }, fd } +#define SPA_POD_INIT_Fd(fd) ((struct spa_pod_fd){ { sizeof(int64_t), SPA_TYPE_Fd }, (fd) }) static inline int spa_pod_builder_fd(struct spa_pod_builder *builder, int64_t fd) { @@ -344,7 +346,7 @@ static inline int spa_pod_builder_fd(struct spa_pod_builder *builder, int64_t fd return spa_pod_builder_primitive(builder, &p.pod); } -#define SPA_POD_INIT_Rectangle(val) (struct spa_pod_rectangle){ { sizeof(struct spa_rectangle), SPA_TYPE_Rectangle }, val } +#define SPA_POD_INIT_Rectangle(val) ((struct spa_pod_rectangle){ { sizeof(struct spa_rectangle), SPA_TYPE_Rectangle }, (val) }) static inline int spa_pod_builder_rectangle(struct spa_pod_builder *builder, uint32_t width, uint32_t height) @@ -353,7 +355,7 @@ spa_pod_builder_rectangle(struct spa_pod_builder *builder, uint32_t width, uint3 return spa_pod_builder_primitive(builder, &p.pod); } -#define SPA_POD_INIT_Fraction(val) (struct spa_pod_fraction){ { sizeof(struct spa_fraction), SPA_TYPE_Fraction }, val } +#define SPA_POD_INIT_Fraction(val) ((struct spa_pod_fraction){ { sizeof(struct spa_fraction), SPA_TYPE_Fraction }, (val) }) static inline int spa_pod_builder_fraction(struct spa_pod_builder *builder, uint32_t num, uint32_t denom) @@ -389,12 +391,12 @@ spa_pod_builder_array(struct spa_pod_builder *builder, } #define SPA_POD_INIT_CHOICE_BODY(type, flags, child_size, child_type) \ - (struct spa_pod_choice_body) { type, flags, { child_size, child_type }} + ((struct spa_pod_choice_body) { (type), (flags), { (child_size), (child_type) }}) #define SPA_POD_INIT_Choice(type, ctype, child_type, n_vals, ...) \ - (struct { struct spa_pod_choice choice; ctype vals[n_vals];}) \ - { { { n_vals * sizeof(ctype) + sizeof(struct spa_pod_choice_body), SPA_TYPE_Choice }, \ - { type, 0, { sizeof(ctype), child_type } } }, { __VA_ARGS__ } } + ((struct { struct spa_pod_choice choice; ctype vals[(n_vals)];}) \ + { { { (n_vals) * sizeof(ctype) + sizeof(struct spa_pod_choice_body), SPA_TYPE_Choice }, \ + { (type), 0, { sizeof(ctype), (child_type) } } }, { __VA_ARGS__ } }) static inline int spa_pod_builder_push_choice(struct spa_pod_builder *builder, struct spa_pod_frame *frame, @@ -409,7 +411,7 @@ spa_pod_builder_push_choice(struct spa_pod_builder *builder, struct spa_pod_fram return res; } -#define SPA_POD_INIT_Struct(size) (struct spa_pod_struct){ { size, SPA_TYPE_Struct } } +#define SPA_POD_INIT_Struct(size) ((struct spa_pod_struct){ { (size), SPA_TYPE_Struct } }) static inline int spa_pod_builder_push_struct(struct spa_pod_builder *builder, struct spa_pod_frame *frame) @@ -421,7 +423,7 @@ spa_pod_builder_push_struct(struct spa_pod_builder *builder, struct spa_pod_fram return res; } -#define SPA_POD_INIT_Object(size,type,id,...) (struct spa_pod_object){ { size, SPA_TYPE_Object }, { type, id }, ##__VA_ARGS__ } +#define SPA_POD_INIT_Object(size,type,id,...) ((struct spa_pod_object){ { (size), SPA_TYPE_Object }, { (type), (id) }, ##__VA_ARGS__ }) static inline int spa_pod_builder_push_object(struct spa_pod_builder *builder, struct spa_pod_frame *frame, @@ -436,7 +438,7 @@ spa_pod_builder_push_object(struct spa_pod_builder *builder, struct spa_pod_fram } #define SPA_POD_INIT_Prop(key,flags,size,type) \ - (struct spa_pod_prop){ key, flags, { size, type } } + ((struct spa_pod_prop){ (key), (flags), { (size), (type) } }) static inline int spa_pod_builder_prop(struct spa_pod_builder *builder, uint32_t key, uint32_t flags) @@ -446,7 +448,7 @@ spa_pod_builder_prop(struct spa_pod_builder *builder, uint32_t key, uint32_t fla } #define SPA_POD_INIT_Sequence(size,unit) \ - (struct spa_pod_sequence){ { size, SPA_TYPE_Sequence}, {unit, 0 } } + ((struct spa_pod_sequence){ { (size), SPA_TYPE_Sequence}, {(unit), 0 } }) static inline int spa_pod_builder_push_sequence(struct spa_pod_builder *builder, struct spa_pod_frame *frame, uint32_t unit) @@ -650,26 +652,29 @@ static inline int spa_pod_builder_add(struct spa_pod_builder *builder, ...) #define spa_pod_builder_add_object(b,type,id,...) \ ({ \ + struct spa_pod_builder *_b = (b); \ struct spa_pod_frame _f; \ - spa_pod_builder_push_object(b, &_f, type, id); \ - spa_pod_builder_add(b, ##__VA_ARGS__, 0); \ - spa_pod_builder_pop(b, &_f); \ + spa_pod_builder_push_object(_b, &_f, type, id); \ + spa_pod_builder_add(_b, ##__VA_ARGS__, 0); \ + spa_pod_builder_pop(_b, &_f); \ }) #define spa_pod_builder_add_struct(b,...) \ ({ \ + struct spa_pod_builder *_b = (b); \ struct spa_pod_frame _f; \ - spa_pod_builder_push_struct(b, &_f); \ - spa_pod_builder_add(b, ##__VA_ARGS__, NULL); \ - spa_pod_builder_pop(b, &_f); \ + spa_pod_builder_push_struct(_b, &_f); \ + spa_pod_builder_add(_b, ##__VA_ARGS__, NULL); \ + spa_pod_builder_pop(_b, &_f); \ }) #define spa_pod_builder_add_sequence(b,unit,...) \ ({ \ + struct spa_pod_builder *_b = (b); \ struct spa_pod_frame _f; \ - spa_pod_builder_push_sequence(b, &_f, unit); \ - spa_pod_builder_add(b, ##__VA_ARGS__, 0, 0); \ - spa_pod_builder_pop(b, &_f); \ + spa_pod_builder_push_sequence(_b, &_f, unit); \ + spa_pod_builder_add(_b, ##__VA_ARGS__, 0, 0); \ + spa_pod_builder_pop(_b, &_f); \ }) /** Copy a pod structure */ diff --git a/spa/include/spa/pod/command.h b/spa/include/spa/pod/command.h index 0c292eeae5e2aa3fab0e9fe174b1028e6b41c8c1..3cd30a0e4e23c5e8e7cab37c14c90005027b168f 100644 --- a/spa/include/spa/pod/command.h +++ b/spa/include/spa/pod/command.h @@ -47,12 +47,12 @@ struct spa_command { }; #define SPA_COMMAND_TYPE(cmd) ((cmd)->body.body.type) -#define SPA_COMMAND_ID(cmd,type) (SPA_COMMAND_TYPE(cmd) == type ? \ +#define SPA_COMMAND_ID(cmd,type) (SPA_COMMAND_TYPE(cmd) == (type) ? \ (cmd)->body.body.id : SPA_ID_INVALID) -#define SPA_COMMAND_INIT_FULL(t,size,type,id,...) (t) \ - { { size, SPA_TYPE_Object }, \ - { { type, id }, ##__VA_ARGS__ } } \ +#define SPA_COMMAND_INIT_FULL(t,size,type,id,...) ((t) \ + { { (size), SPA_TYPE_Object }, \ + { { (type), (id) }, ##__VA_ARGS__ } }) #define SPA_COMMAND_INIT(type,id) \ SPA_COMMAND_INIT_FULL(struct spa_command, \ diff --git a/spa/include/spa/pod/event.h b/spa/include/spa/pod/event.h index 2ecfb0eabb643816ef1d5596c079073929967c61..c5ebc78e81d81a7e5f2948e8e5a213af94532a0b 100644 --- a/spa/include/spa/pod/event.h +++ b/spa/include/spa/pod/event.h @@ -46,7 +46,7 @@ struct spa_event { }; #define SPA_EVENT_TYPE(ev) ((ev)->body.body.type) -#define SPA_EVENT_ID(ev,type) (SPA_EVENT_TYPE(ev) == type ? \ +#define SPA_EVENT_ID(ev,type) (SPA_EVENT_TYPE(ev) == (type) ? \ (ev)->body.body.id : SPA_ID_INVALID) #define SPA_EVENT_INIT_FULL(t,size,type,id,...) (t) \ diff --git a/spa/include/spa/pod/parser.h b/spa/include/spa/pod/parser.h index c0a3099da64fd5f6a3f1afc4d2b36252187e3501..e0546751f455bf2a1e5fc297aa487a87b7d77e6f 100644 --- a/spa/include/spa/pod/parser.h +++ b/spa/include/spa/pod/parser.h @@ -53,7 +53,7 @@ struct spa_pod_parser { struct spa_pod_parser_state state; }; -#define SPA_POD_PARSER_INIT(buffer,size) (struct spa_pod_parser){ buffer, size, 0, {} } +#define SPA_POD_PARSER_INIT(buffer,size) ((struct spa_pod_parser){ (buffer), (size), 0, {} }) static inline void spa_pod_parser_init(struct spa_pod_parser *parser, const void *data, uint32_t size) @@ -82,12 +82,20 @@ spa_pod_parser_reset(struct spa_pod_parser *parser, struct spa_pod_parser_state static inline struct spa_pod * spa_pod_parser_deref(struct spa_pod_parser *parser, uint32_t offset, uint32_t size) { - if (offset + 8 <= size) { - struct spa_pod *pod = SPA_PTROFF(parser->data, offset, struct spa_pod); - if (offset + SPA_POD_SIZE(pod) <= size) - return pod; + /* Cast to uint64_t to avoid wraparound. Add 8 for the pod itself. */ + const uint64_t long_offset = (uint64_t)offset + 8; + if (long_offset <= size && (offset & 7) == 0) { + /* Use void* because creating a misaligned pointer is undefined. */ + void *pod = SPA_PTROFF(parser->data, offset, void); + /* + * Check that the pointer is aligned and that the size (rounded + * to the next multiple of 8) is in bounds. + */ + if (SPA_IS_ALIGNED(pod, __alignof__(struct spa_pod)) && + long_offset + SPA_ROUND_UP_N((uint64_t)SPA_POD_BODY_SIZE(pod), 8) <= size) + return (struct spa_pod *)pod; } - return NULL; + return NULL; } static inline struct spa_pod *spa_pod_parser_frame(struct spa_pod_parser *parser, struct spa_pod_frame *frame) @@ -285,10 +293,15 @@ static inline bool spa_pod_parser_can_collect(const struct spa_pod *pod, char ty if (pod == NULL) return false; - if (spa_pod_is_choice(pod) && - SPA_POD_CHOICE_TYPE(pod) == SPA_CHOICE_None && - spa_pod_parser_can_collect(SPA_POD_CHOICE_CHILD(pod), type)) - return true; + if (SPA_POD_TYPE(pod) == SPA_TYPE_Choice) { + if (!spa_pod_is_choice(pod)) + return false; + if (type == 'V') + return true; + if (SPA_POD_CHOICE_TYPE(pod) != SPA_CHOICE_None) + return false; + pod = SPA_POD_CHOICE_CHILD(pod); + } switch (type) { case 'P': @@ -328,7 +341,6 @@ static inline bool spa_pod_parser_can_collect(const struct spa_pod *pod, char ty case 'O': return spa_pod_is_object(pod) || spa_pod_is_none(pod); case 'V': - return spa_pod_is_choice(pod); default: return false; } @@ -355,7 +367,7 @@ do { \ break; \ case 's': \ *va_arg(args, char**) = \ - (pod == NULL || (SPA_POD_TYPE(pod) == SPA_TYPE_None) \ + ((pod) == NULL || (SPA_POD_TYPE(pod) == SPA_TYPE_None) \ ? NULL \ : (char *)SPA_POD_CONTENTS(struct spa_pod_string, pod)); \ break; \ @@ -407,8 +419,8 @@ do { \ { \ const struct spa_pod **d = va_arg(args, const struct spa_pod**); \ if (d) \ - *d = (pod == NULL || (SPA_POD_TYPE(pod) == SPA_TYPE_None) \ - ? NULL : pod); \ + *d = ((pod) == NULL || (SPA_POD_TYPE(pod) == SPA_TYPE_None) \ + ? NULL : (pod)); \ break; \ } \ default: \ @@ -493,8 +505,7 @@ static inline int spa_pod_parser_getv(struct spa_pod_parser *parser, va_list arg } SPA_POD_PARSER_SKIP(*format, args); } else { - if (pod->type == SPA_TYPE_Choice && *format != 'V' && - SPA_POD_CHOICE_TYPE(pod) == SPA_CHOICE_None) + if (pod->type == SPA_TYPE_Choice && *format != 'V') pod = SPA_POD_CHOICE_CHILD(pod); SPA_POD_PARSER_COLLECT(pod, *format, args); diff --git a/spa/include/spa/pod/pod.h b/spa/include/spa/pod/pod.h index 2d2eaad6a18b4df69657844fdeb91f53c68566eb..1864b19b9f4c50bd2ba584ff427b9f0468d2fdc9 100644 --- a/spa/include/spa/pod/pod.h +++ b/spa/include/spa/pod/pod.h @@ -39,7 +39,7 @@ extern "C" { #define SPA_POD_BODY_SIZE(pod) (((struct spa_pod*)(pod))->size) #define SPA_POD_TYPE(pod) (((struct spa_pod*)(pod))->type) -#define SPA_POD_SIZE(pod) (sizeof(struct spa_pod) + SPA_POD_BODY_SIZE(pod)) +#define SPA_POD_SIZE(pod) ((uint64_t)sizeof(struct spa_pod) + SPA_POD_BODY_SIZE(pod)) #define SPA_POD_CONTENTS_SIZE(type,pod) (SPA_POD_SIZE(pod)-sizeof(type)) #define SPA_POD_CONTENTS(type,pod) SPA_PTROFF((pod),sizeof(type),void) @@ -52,7 +52,7 @@ struct spa_pod { uint32_t type; /* a basic id of enum spa_type */ }; -#define SPA_POD_VALUE(type,pod) (((type*)pod)->value) +#define SPA_POD_VALUE(type,pod) (((type*)(pod))->value) struct spa_pod_bool { struct spa_pod pod; diff --git a/spa/include/spa/support/log-impl.h b/spa/include/spa/support/log-impl.h index 124153d073dfb39a722c87e626ee77f48705d893..e1ee21dd86f317bfea7a40babe71c5d39c9f16cc 100644 --- a/spa/include/spa/support/log-impl.h +++ b/spa/include/spa/support/log-impl.h @@ -121,7 +121,7 @@ struct { \ #define SPA_LOG_IMPL_INIT(name) \ { { { SPA_TYPE_INTERFACE_Log, SPA_VERSION_LOG, \ - SPA_CALLBACKS_INIT(&name.methods, &name) }, \ + SPA_CALLBACKS_INIT(&(name).methods, &(name)) }, \ SPA_LOG_LEVEL_INFO, }, \ { SPA_VERSION_LOG_METHODS, \ spa_log_impl_log, \ diff --git a/spa/include/spa/support/log.h b/spa/include/spa/support/log.h index e4990d1bb69d64944e0463c1a8c9f9160266c883..008fb34175fea0e49ff273181cb48d3a7c09b220 100644 --- a/spa/include/spa/support/log.h +++ b/spa/include/spa/support/log.h @@ -212,7 +212,7 @@ struct spa_log_methods { #define SPA_LOG_TOPIC(v, t) \ - (struct spa_log_topic){ .version = v, .topic = (t)} + (struct spa_log_topic){ .version = (v), .topic = (t)} #define spa_log_topic_init(l, topic) \ do { \ @@ -231,10 +231,10 @@ do { \ ({ \ struct spa_log *_log = l; \ enum spa_log_level _lev = _log ? _log->level : SPA_LOG_LEVEL_NONE; \ - struct spa_log_topic *_t = (struct spa_log_topic *)topic; \ + struct spa_log_topic *_t = (struct spa_log_topic *)(topic); \ if (_t && _t->has_custom_level) \ _lev = _t->level; \ - _lev >= lev; \ + _lev >= (lev); \ }) /* Transparently calls to version 0 log if v1 is not supported */ @@ -269,30 +269,52 @@ do { \ } \ }) +#define spa_logt_lev(l,lev,t,...) \ + spa_log_logt(l,lev,t,__FILE__,__LINE__,__func__,__VA_ARGS__) + +#define spa_log_lev(l,lev,...) \ + spa_logt_lev(l,lev,SPA_LOG_TOPIC_DEFAULT,__VA_ARGS__) + #define spa_log_log(l,lev,...) \ spa_log_logt(l,lev,SPA_LOG_TOPIC_DEFAULT,__VA_ARGS__) #define spa_log_logv(l,lev,...) \ spa_log_logtv(l,lev,SPA_LOG_TOPIC_DEFAULT,__VA_ARGS__) -#define spa_log_error(l,...) spa_log_log(l,SPA_LOG_LEVEL_ERROR,__FILE__,__LINE__,__func__,__VA_ARGS__) -#define spa_log_warn(l,...) spa_log_log(l,SPA_LOG_LEVEL_WARN,__FILE__,__LINE__,__func__,__VA_ARGS__) -#define spa_log_info(l,...) spa_log_log(l,SPA_LOG_LEVEL_INFO,__FILE__,__LINE__,__func__,__VA_ARGS__) -#define spa_log_debug(l,...) spa_log_log(l,SPA_LOG_LEVEL_DEBUG,__FILE__,__LINE__,__func__,__VA_ARGS__) -#define spa_log_trace(l,...) spa_log_log(l,SPA_LOG_LEVEL_TRACE,__FILE__,__LINE__,__func__,__VA_ARGS__) +#define spa_log_error(l,...) spa_log_lev(l,SPA_LOG_LEVEL_ERROR,__VA_ARGS__) +#define spa_log_warn(l,...) spa_log_lev(l,SPA_LOG_LEVEL_WARN,__VA_ARGS__) +#define spa_log_info(l,...) spa_log_lev(l,SPA_LOG_LEVEL_INFO,__VA_ARGS__) +#define spa_log_debug(l,...) spa_log_lev(l,SPA_LOG_LEVEL_DEBUG,__VA_ARGS__) +#define spa_log_trace(l,...) spa_log_lev(l,SPA_LOG_LEVEL_TRACE,__VA_ARGS__) -#define spa_logt_error(l,t,...) spa_log_logt(l,SPA_LOG_LEVEL_ERROR,t,__FILE__,__LINE__,__func__,__VA_ARGS__) -#define spa_logt_warn(l,t,...) spa_log_logt(l,SPA_LOG_LEVEL_WARN,t,__FILE__,__LINE__,__func__,__VA_ARGS__) -#define spa_logt_info(l,t,...) spa_log_logt(l,SPA_LOG_LEVEL_INFO,t,__FILE__,__LINE__,__func__,__VA_ARGS__) -#define spa_logt_debug(l,t,...) spa_log_logt(l,SPA_LOG_LEVEL_DEBUG,t,__FILE__,__LINE__,__func__,__VA_ARGS__) -#define spa_logt_trace(l,t,...) spa_log_logt(l,SPA_LOG_LEVEL_TRACE,t,__FILE__,__LINE__,__func__,__VA_ARGS__) +#define spa_logt_error(l,t,...) spa_logt_lev(l,SPA_LOG_LEVEL_ERROR,t,__VA_ARGS__) +#define spa_logt_warn(l,t,...) spa_logt_lev(l,SPA_LOG_LEVEL_WARN,t,__VA_ARGS__) +#define spa_logt_info(l,t,...) spa_logt_lev(l,SPA_LOG_LEVEL_INFO,t,__VA_ARGS__) +#define spa_logt_debug(l,t,...) spa_logt_lev(l,SPA_LOG_LEVEL_DEBUG,t,__VA_ARGS__) +#define spa_logt_trace(l,t,...) spa_logt_lev(l,SPA_LOG_LEVEL_TRACE,t,__VA_ARGS__) #ifndef FASTPATH -#define spa_log_trace_fp(l,...) spa_log_log(l,SPA_LOG_LEVEL_TRACE,__FILE__,__LINE__,__func__,__VA_ARGS__) +#define spa_log_trace_fp(l,...) spa_log_lev(l,SPA_LOG_LEVEL_TRACE,__VA_ARGS__) #else #define spa_log_trace_fp(l,...) #endif +#define spa_log_hexdump(l,lev,indent,data,len) \ +({ \ + char str[512]; \ + uint8_t *buf = (uint8_t *)(data); \ + size_t i, j = (len); \ + int pos = 0; \ + \ + for (i = 0; i < j; i++) { \ + if (i % 16 == 0) \ + pos = 0; \ + pos += sprintf(str + pos, "%02x ", buf[i]); \ + if (i % 16 == 15 || i == j - 1) \ + spa_log_lev(l,lev, "%*s" "%s",indent,"", str); \ + } \ +}) + /** \fn spa_log_error */ /** keys can be given when initializing the logger handle */ diff --git a/spa/include/spa/support/plugin.h b/spa/include/spa/support/plugin.h index e66bdc99760809c00cf2f5c499aac77f89d02a4d..80cadda61ee49de149f15994d61b83b34cb12594 100644 --- a/spa/include/spa/support/plugin.h +++ b/spa/include/spa/support/plugin.h @@ -105,7 +105,7 @@ static inline void *spa_support_find(const struct spa_support *support, return NULL; } -#define SPA_SUPPORT_INIT(type,data) (struct spa_support) { (type), (data) } +#define SPA_SUPPORT_INIT(type,data) ((struct spa_support) { (type), (data) }) struct spa_handle_factory { /** The version of this structure */ diff --git a/spa/include/spa/support/system.h b/spa/include/spa/support/system.h index 8076ceb4bbc71da6d46917750be6d0363595efdf..79127d9062cc9d089914aef0ce9b9d9664dd970c 100644 --- a/spa/include/spa/support/system.h +++ b/spa/include/spa/support/system.h @@ -119,7 +119,7 @@ struct spa_system_methods { #define spa_system_method_r(o,method,version,...) \ ({ \ - int _res = -ENOTSUP; \ + volatile int _res = -ENOTSUP; \ struct spa_system *_o = o; \ spa_interface_call_res(&_o->iface, \ struct spa_system_methods, _res, \ diff --git a/spa/include/spa/utils/defs.h b/spa/include/spa/utils/defs.h index 874fab410f6d23c58b9f139444e642aee168f30f..686ee86b430b9b845d83744838329a8b038247d5 100644 --- a/spa/include/spa/utils/defs.h +++ b/spa/include/spa/utils/defs.h @@ -27,8 +27,18 @@ #ifdef __cplusplus extern "C" { +# if __cplusplus >= 201103L +# define SPA_STATIC_ASSERT static_assert +# endif #else -#include <stdbool.h> +# include <stdbool.h> +# if __STDC_VERSION__ >= 201112L +# define SPA_STATIC_ASSERT _Static_assert +# endif +#endif +#ifndef SPA_STATIC_ASSERT +#define SPA_STATIC_ASSERT(a, b) \ + ((void)sizeof(struct { int spa_static_assertion_failed : 2 * !!(a) - 1; })) #endif #include <inttypes.h> #include <signal.h> @@ -74,8 +84,16 @@ extern "C" { #define SPA_FLAG_MASK(field,mask,flag) (((field) & (mask)) == (flag)) #define SPA_FLAG_IS_SET(field,flag) SPA_FLAG_MASK(field,flag,flag) #define SPA_FLAG_SET(field,flag) ((field) |= (flag)) -#define SPA_FLAG_CLEAR(field,flag) ((field) &= ~(flag)) -#define SPA_FLAG_UPDATE(field,flag,val) ((val) ? SPA_FLAG_SET(field,flag) : SPA_FLAG_CLEAR(field,flag)) +#define SPA_FLAG_CLEAR(field, flag) \ +({ \ + SPA_STATIC_ASSERT(__builtin_constant_p(flag) ? \ + (__typeof__(flag))(__typeof__(field))(__typeof__(flag))(flag) == (flag) : \ + sizeof(field) >= sizeof(flag), \ + "truncation problem when masking " #field \ + " with ~" #flag); \ + ((field) &= ~(__typeof__(field))(flag)); \ +}) +#define SPA_FLAG_UPDATE(field,flag,val) ((val) ? SPA_FLAG_SET((field),(flag)) : SPA_FLAG_CLEAR((field),(flag))) enum spa_direction { SPA_DIRECTION_INPUT = 0, @@ -84,25 +102,25 @@ enum spa_direction { #define SPA_DIRECTION_REVERSE(d) ((d) ^ 1) -#define SPA_RECTANGLE(width,height) (struct spa_rectangle){ width, height } +#define SPA_RECTANGLE(width,height) ((struct spa_rectangle){ (width), (height) }) struct spa_rectangle { uint32_t width; uint32_t height; }; -#define SPA_POINT(x,y) (struct spa_point){ x, y } +#define SPA_POINT(x,y) ((struct spa_point){ (x), (y) }) struct spa_point { int32_t x; int32_t y; }; -#define SPA_REGION(x,y,width,height) (struct spa_region){ SPA_POINT(x,y), SPA_RECTANGLE(width,height) } +#define SPA_REGION(x,y,width,height) ((struct spa_region){ SPA_POINT(x,y), SPA_RECTANGLE(width,height) }) struct spa_region { struct spa_point position; struct spa_rectangle size; }; -#define SPA_FRACTION(num,denom) (struct spa_fraction){ num, denom } +#define SPA_FRACTION(num,denom) ((struct spa_fraction){ (num), (denom) }) struct spa_fraction { uint32_t num; uint32_t denom; @@ -120,7 +138,7 @@ struct spa_fraction { * ``` */ #define SPA_FOR_EACH_ELEMENT(arr, ptr) \ - for (ptr = arr; (void*)ptr < SPA_PTROFF(arr, sizeof(arr), void); ptr++) + for ((ptr) = arr; (void*)(ptr) < SPA_PTROFF(arr, sizeof(arr), void); (ptr)++) #define SPA_ABS(a) \ ({ \ @@ -131,13 +149,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,10 +165,16 @@ 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); \ - a = b; b = _t; \ + (a) = b; (b) = _t; \ }) #define SPA_TYPECHECK(type,x) \ @@ -174,7 +198,7 @@ struct spa_fraction { #define SPA_MEMBER(b,o,t) SPA_PTROFF(b,o,t) #define SPA_MEMBER_ALIGN(b,o,a,t) SPA_PTROFF_ALIGN(b,o,a,t) -#define SPA_CONTAINER_OF(p,t,m) ((t*)((uintptr_t)p - offsetof(t,m))) +#define SPA_CONTAINER_OF(p,t,m) ((t*)((uintptr_t)(p) - offsetof(t,m))) #define SPA_PTRDIFF(p1,p2) ((intptr_t)(p1) - (intptr_t)(p2)) @@ -188,7 +212,7 @@ struct spa_fraction { #define SPA_IDX_INVALID ((unsigned int)-1) #define SPA_ID_INVALID ((uint32_t)0xffffffff) -#define SPA_NSEC_PER_SEC (1000000000ll) +#define SPA_NSEC_PER_SEC (1000000000LL) #define SPA_NSEC_PER_MSEC (1000000ll) #define SPA_NSEC_PER_USEC (1000ll) #define SPA_USEC_PER_SEC (1000000ll) @@ -209,6 +233,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 +243,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 @@ -231,7 +257,17 @@ struct spa_fraction { #define SPA_ROUND_DOWN(num,value) ((num) - ((num) % (value))) #define SPA_ROUND_UP(num,value) ((((num) + (value) - 1) / (value)) * (value)) -#define SPA_ROUND_DOWN_N(num,align) ((num) & ~((align) - 1)) +#define SPA_MASK_NEGATED(num1, num2) \ +({ \ + SPA_STATIC_ASSERT(__builtin_constant_p(num2) ? \ + (__typeof__(num2))(__typeof__(num1))(__typeof__(num2))(num2) == (num2) : \ + sizeof(num1) >= sizeof(num2), \ + "truncation problem when masking " #num1 \ + " with ~" #num2); \ + ((num1) & ~(__typeof__(num1))(num2)); \ +}) + +#define SPA_ROUND_DOWN_N(num,align) SPA_MASK_NEGATED((num), (align) - 1) #define SPA_ROUND_UP_N(num,align) SPA_ROUND_DOWN_N((num) + ((align) - 1),align) #define SPA_PTR_ALIGNMENT(p,align) ((intptr_t)(p) & ((align)-1)) diff --git a/spa/include/spa/utils/dict.h b/spa/include/spa/utils/dict.h index 558c6fd016f3c60167c8f60da3310f2023bf8fef..f9a0b5b6b55a813215a4f39047b41a5a082e1990 100644 --- a/spa/include/spa/utils/dict.h +++ b/spa/include/spa/utils/dict.h @@ -48,7 +48,7 @@ struct spa_dict_item { const char *value; }; -#define SPA_DICT_ITEM_INIT(key,value) (struct spa_dict_item) { key, value } +#define SPA_DICT_ITEM_INIT(key,value) ((struct spa_dict_item) { (key), (value) }) struct spa_dict { #define SPA_DICT_FLAG_SORTED (1<<0) /**< items are sorted */ @@ -57,8 +57,8 @@ struct spa_dict { const struct spa_dict_item *items; }; -#define SPA_DICT_INIT(items,n_items) (struct spa_dict) { 0, n_items, items } -#define SPA_DICT_INIT_ARRAY(items) (struct spa_dict) { 0, SPA_N_ELEMENTS(items), items } +#define SPA_DICT_INIT(items,n_items) ((struct spa_dict) { 0, (n_items), (items) }) +#define SPA_DICT_INIT_ARRAY(items) ((struct spa_dict) { 0, SPA_N_ELEMENTS(items), (items) }) #define spa_dict_for_each(item, dict) \ for ((item) = (dict)->items; \ diff --git a/spa/include/spa/utils/hook.h b/spa/include/spa/utils/hook.h index 9b1a50b63bc98a1377743171cab2a16a4d317a00..f71323b05e84ec8e51ef002d3a5e717fd6281245 100644 --- a/spa/include/spa/utils/hook.h +++ b/spa/include/spa/utils/hook.h @@ -142,7 +142,7 @@ struct spa_callbacks { * Initialize the set of functions \a funcs as a \ref spa_callbacks, together * with \a _data. */ -#define SPA_CALLBACKS_INIT(_funcs,_data) (struct spa_callbacks){ _funcs, _data, } +#define SPA_CALLBACKS_INIT(_funcs,_data) ((struct spa_callbacks){ (_funcs), (_data), }) /** \struct spa_interface */ @@ -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..0f93149e6ed966f65ce3424a5a802dc2658c01d2 100644 --- a/spa/include/spa/utils/json.h +++ b/spa/include/spa/utils/json.h @@ -58,20 +58,20 @@ struct spa_json { uint32_t depth; }; -#define SPA_JSON_INIT(data,size) (struct spa_json) { (data), (data)+(size), } +#define SPA_JSON_INIT(data,size) ((struct spa_json) { (data), (data)+(size), }) static inline void spa_json_init(struct spa_json * iter, const char *data, size_t size) { *iter = SPA_JSON_INIT(data, size); } -#define SPA_JSON_ENTER(iter) (struct spa_json) { (iter)->cur, (iter)->end, (iter), } +#define SPA_JSON_ENTER(iter) ((struct spa_json) { (iter)->cur, (iter)->end, (iter), }) static inline void spa_json_enter(struct spa_json * iter, struct spa_json * sub) { *sub = SPA_JSON_ENTER(iter); } -#define SPA_JSON_SAVE(iter) (struct spa_json) { (iter)->cur, (iter)->end, } +#define SPA_JSON_SAVE(iter) ((struct spa_json) { (iter)->cur, (iter)->end, }) /** Get the next token. \a value points to the token and the return value * is the length. */ @@ -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/keys.h b/spa/include/spa/utils/keys.h index 80d578fc0ee9a1fdb72c7be69ab18ec8a74c9c89..8a2003abf5d5112c76c2a19b1988845e2f62f5eb 100644 --- a/spa/include/spa/utils/keys.h +++ b/spa/include/spa/utils/keys.h @@ -62,6 +62,8 @@ extern "C" { #define SPA_KEY_API_ALSA_USE_UCM "api.alsa.use-ucm" /**< if UCM should be used */ #define SPA_KEY_API_ALSA_IGNORE_DB "api.alsa.ignore-dB" /**< if decibel info should be ignored */ #define SPA_KEY_API_ALSA_OPEN_UCM "api.alsa.open.ucm" /**< if UCM should be opened card */ +#define SPA_KEY_API_ALSA_DISABLE_LONGNAME \ + "api.alsa.disable-longname" /**< if card long name should not be passed to MIDI port */ /** info from alsa card_info */ #define SPA_KEY_API_ALSA_CARD_ID "api.alsa.card.id" /**< id from card_info */ diff --git a/spa/include/spa/utils/list.h b/spa/include/spa/utils/list.h index 62aa9a3df7f9cc7cf6a4d5ee586d7ed1e46fc442..b57657a0ff15d983e81cb6f870f52554d893ea9f 100644 --- a/spa/include/spa/utils/list.h +++ b/spa/include/spa/utils/list.h @@ -44,13 +44,18 @@ struct spa_list { struct spa_list *prev; }; -#define SPA_LIST_INIT(list) (struct spa_list){ list, list } +#define SPA_LIST_INIT(list) ((struct spa_list){ (list), (list) }) 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) @@ -93,25 +98,25 @@ static inline void spa_list_remove(struct spa_list *elem) (&(pos)->member == (head)) #define spa_list_next(pos, member) \ - SPA_CONTAINER_OF((pos)->member.next, __typeof__(*pos), member) + SPA_CONTAINER_OF((pos)->member.next, __typeof__(*(pos)), member) #define spa_list_prev(pos, member) \ - SPA_CONTAINER_OF((pos)->member.prev, __typeof__(*pos), member) + SPA_CONTAINER_OF((pos)->member.prev, __typeof__(*(pos)), member) #define spa_list_consume(pos, head, member) \ - for (pos = spa_list_first(head, __typeof__(*pos), member); \ + for ((pos) = spa_list_first(head, __typeof__(*(pos)), member); \ !spa_list_is_empty(head); \ - pos = spa_list_first(head, __typeof__(*pos), member)) + (pos) = spa_list_first(head, __typeof__(*(pos)), member)) #define spa_list_for_each_next(pos, head, curr, member) \ - for (pos = spa_list_first(curr, __typeof__(*pos), member); \ + for ((pos) = spa_list_first(curr, __typeof__(*(pos)), member); \ !spa_list_is_end(pos, head, member); \ - pos = spa_list_next(pos, member)) + (pos) = spa_list_next(pos, member)) #define spa_list_for_each_prev(pos, head, curr, member) \ - for (pos = spa_list_last(curr, __typeof__(*pos), member); \ + for ((pos) = spa_list_last(curr, __typeof__(*(pos)), member); \ !spa_list_is_end(pos, head, member); \ - pos = spa_list_prev(pos, member)) + (pos) = spa_list_prev(pos, member)) #define spa_list_for_each(pos, head, member) \ spa_list_for_each_next(pos, head, head, member) @@ -120,16 +125,16 @@ static inline void spa_list_remove(struct spa_list *elem) spa_list_for_each_prev(pos, head, head, member) #define spa_list_for_each_safe_next(pos, tmp, head, curr, member) \ - for (pos = spa_list_first(curr, __typeof__(*pos), member); \ - tmp = spa_list_next(pos, member), \ + for ((pos) = spa_list_first(curr, __typeof__(*(pos)), member); \ + (tmp) = spa_list_next(pos, member), \ !spa_list_is_end(pos, head, member); \ - pos = tmp) + (pos) = (tmp)) #define spa_list_for_each_safe_prev(pos, tmp, head, curr, member) \ - for (pos = spa_list_last(curr, __typeof__(*pos), member); \ - tmp = spa_list_prev(pos, member), \ + for ((pos) = spa_list_last(curr, __typeof__(*(pos)), member); \ + (tmp) = spa_list_prev(pos, member), \ !spa_list_is_end(pos, head, member); \ - pos = tmp) + (pos) = (tmp)) #define spa_list_for_each_safe(pos, tmp, head, member) \ spa_list_for_each_safe_next(pos, tmp, head, head, member) @@ -141,11 +146,11 @@ static inline void spa_list_remove(struct spa_list *elem) spa_list_prepend(head, &(cursor).member) #define spa_list_for_each_cursor(pos, cursor, head, member) \ - for(pos = spa_list_first(&(cursor).member, __typeof__(*(pos)), member); \ + for((pos) = spa_list_first(&(cursor).member, __typeof__(*(pos)), member); \ spa_list_remove(&(pos)->member), \ spa_list_append(&(cursor).member, &(pos)->member), \ !spa_list_is_end(pos, head, member); \ - pos = spa_list_next(&cursor, member)) + (pos) = spa_list_next(&(cursor), member)) #define spa_list_cursor_end(cursor, member) \ spa_list_remove(&(cursor).member) diff --git a/spa/include/spa/utils/names.h b/spa/include/spa/utils/names.h index 7d60b07ed80579d8eaf3da9e92733542a322e020..f8104b7421e96742307ed36be5b81a61347542cf 100644 --- a/spa/include/spa/utils/names.h +++ b/spa/include/spa/utils/names.h @@ -114,13 +114,15 @@ extern "C" { /** keys for bluez5 factory names */ #define SPA_NAME_API_BLUEZ5_ENUM_DBUS "api.bluez5.enum.dbus" /**< a dbus Device interface */ #define SPA_NAME_API_BLUEZ5_DEVICE "api.bluez5.device" /**< a Device interface */ -#define SPA_NAME_API_BLUEZ5_A2DP_SINK "api.bluez5.a2dp.sink" /**< a playback Node interface for A2DP profiles */ -#define SPA_NAME_API_BLUEZ5_A2DP_SOURCE "api.bluez5.a2dp.source" /**< a capture Node interface for A2DP profiles */ +#define SPA_NAME_API_BLUEZ5_MEDIA_SINK "api.bluez5.media.sink" /**< a playback Node interface for A2DP/BAP profiles */ +#define SPA_NAME_API_BLUEZ5_MEDIA_SOURCE "api.bluez5.media.source" /**< a capture Node interface for A2DP/BAP profiles */ +#define SPA_NAME_API_BLUEZ5_A2DP_SINK "api.bluez5.a2dp.sink" /**< alias for media.sink */ +#define SPA_NAME_API_BLUEZ5_A2DP_SOURCE "api.bluez5.a2dp.source" /**< alias for media.source */ #define SPA_NAME_API_BLUEZ5_SCO_SINK "api.bluez5.sco.sink" /**< a playback Node interface for HSP/HFP profiles */ #define SPA_NAME_API_BLUEZ5_SCO_SOURCE "api.bluez5.sco.source" /**< a capture Node interface for HSP/HFP profiles */ /** keys for codec factory names */ -#define SPA_NAME_API_CODEC_BLUEZ5_A2DP "api.codec.bluez5.a2dp" /**< Bluez5 A2DP codec plugin */ +#define SPA_NAME_API_CODEC_BLUEZ5_MEDIA "api.codec.bluez5.media" /**< Bluez5 Media codec plugin */ /** keys for v4l2 factory names */ #define SPA_NAME_API_V4L2_ENUM_UDEV "api.v4l2.enum.udev" /**< a v4l2 udev Device interface */ diff --git a/spa/include/spa/utils/result.h b/spa/include/spa/utils/result.h index 67ee401a1447146cd6e61c1ef919180c0123d141..05a1ef96b4e3de4dc2e54fc904aef3a318bb7c9a 100644 --- a/spa/include/spa/utils/result.h +++ b/spa/include/spa/utils/result.h @@ -55,7 +55,7 @@ extern "C" { #define spa_strerror(err) \ ({ \ - int _err = -err; \ + int _err = -(err); \ if (SPA_RESULT_IS_ASYNC(err)) \ _err = EINPROGRESS; \ strerror(_err); \ diff --git a/spa/include/spa/utils/ringbuffer.h b/spa/include/spa/utils/ringbuffer.h index 19bfb8675551c66195901ac8c193125c61d91cec..ed14939a78b9a876a0230e46b2fc9d445e0ef9eb 100644 --- a/spa/include/spa/utils/ringbuffer.h +++ b/spa/include/spa/utils/ringbuffer.h @@ -53,7 +53,7 @@ struct spa_ringbuffer { uint32_t writeindex; /*< the current write index */ }; -#define SPA_RINGBUFFER_INIT() (struct spa_ringbuffer) { 0, 0 } +#define SPA_RINGBUFFER_INIT() ((struct spa_ringbuffer) { 0, 0 }) /** * Initialize a spa_ringbuffer with \a size. diff --git a/spa/meson.build b/spa/meson.build index 82e274e9ff871c50f4217d78a7c3dc2c1b4fcde7..b3193241ff162490f87a94734825beb5184e28ec 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -62,6 +62,10 @@ 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') + lc3_dep = dependency('lc3', required : get_option('bluez5-codec-lc3')) + summary({'LC3': lc3_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..5dd292683d21a8a23ed3cdf6d82a432ad9b351a2 100644 --- a/spa/plugins/alsa/90-pipewire-alsa.rules +++ b/spa/plugins/alsa/90-pipewire-alsa.rules @@ -177,6 +177,13 @@ 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" + +# Asus Xonar SE +ATTRS{idVendor}=="0b05", ATTRS{idProduct}=="189d", ENV{ACP_PROFILE_SET}="asus-xonar-se.conf" + GOTO="pipewire_end" LABEL="pipewire_check_pci" diff --git a/spa/plugins/alsa/acp-tool.c b/spa/plugins/alsa/acp-tool.c index bddefab53b800f6f21e3da5d5354e0e319140bf4..0fc92a4f32b1c22e31c990e8d352d818bffffcd2 100644 --- a/spa/plugins/alsa/acp-tool.c +++ b/spa/plugins/alsa/acp-tool.c @@ -584,6 +584,9 @@ static int handle_input(struct data *data) return -errno; buf[r] = 0; + if (r == 0) + return -EPIPE; + if ((p = strchr(buf, '#'))) *p = '\0'; @@ -679,8 +682,12 @@ static int do_prompt(struct data *data) if (err < 0) return -errno; - if (pfds[0].revents & POLLIN) - handle_input(data); + if (pfds[0].revents & POLLIN) { + if ((err = handle_input(data)) < 0) { + if (err == -EPIPE) + break; + } + } if (count < 2) continue; diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index 39475baf5ef7cfe905901e6e0a52d954615a6148..f23232ed3ab9b249180dd4f09bae79ba4d88837a 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); } @@ -1535,7 +1546,7 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props) } impl->ucm.default_sample_spec.format = PA_SAMPLE_S16NE; - impl->ucm.default_sample_spec.rate = 44100; + impl->ucm.default_sample_spec.rate = 48000; impl->ucm.default_sample_spec.channels = 2; pa_channel_map_init_extend(&impl->ucm.default_channel_map, impl->ucm.default_sample_spec.channels, PA_CHANNEL_MAP_ALSA); @@ -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/acp.h b/spa/plugins/alsa/acp/acp.h index 8db9f8f2783810cd5f78605069a3a48d562d201f..b61ad720097a22409848deeaf9d6fb24196e9a5e 100644 --- a/spa/plugins/alsa/acp/acp.h +++ b/spa/plugins/alsa/acp/acp.h @@ -49,7 +49,7 @@ struct acp_dict_item { const char *key; const char *value; }; -#define ACP_DICT_ITEM_INIT(key,value) (struct acp_dict_item) { key, value } +#define ACP_DICT_ITEM_INIT(key,value) ((struct acp_dict_item) { (key), (value) }) struct acp_dict { uint32_t flags; @@ -115,8 +115,8 @@ struct acp_format { uint32_t map[ACP_MAX_CHANNELS]; }; -#define ACP_DICT_INIT(items,n_items) (struct acp_dict) { 0, n_items, items } -#define ACP_DICT_INIT_ARRAY(items) (struct acp_dict) { 0, sizeof(items)/sizeof((items)[0]), items } +#define ACP_DICT_INIT(items,n_items) ((struct acp_dict) { 0, (n_items), (items) }) +#define ACP_DICT_INIT_ARRAY(items) ((struct acp_dict) { 0, sizeof(items)/sizeof((items)[0]), (items) }) #define acp_dict_for_each(item, dict) \ for ((item) = (dict)->items; \ 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/acp/array.h b/spa/plugins/alsa/acp/array.h index ef3b60362540d7adab07ddc03a7e967c886cece0..8a976ca7988accd54942b6e7a85b73d907dd38f8 100644 --- a/spa/plugins/alsa/acp/array.h +++ b/spa/plugins/alsa/acp/array.h @@ -39,7 +39,7 @@ typedef struct pa_array { size_t extend; /**< number of bytes to extend with */ } pa_array; -#define PW_ARRAY_INIT(extend) (struct pa_array) { NULL, 0, 0, extend } +#define PW_ARRAY_INIT(extend) ((struct pa_array) { NULL, 0, 0, (extend) }) #define pa_array_get_len_s(a,s) ((a)->size / (s)) #define pa_array_get_unchecked_s(a,idx,s,t) (t*)((uint8_t*)(a)->data + (int)((idx)*(s))) diff --git a/spa/plugins/alsa/alsa-pcm-sink.c b/spa/plugins/alsa/alsa-pcm-sink.c index b26554b683c0f8e53a194ea0e589cbbf2e6ca4df..73c421eec52d173bcd7e507749fba56ecdfbb08b 100644 --- a/spa/plugins/alsa/alsa-pcm-sink.c +++ b/spa/plugins/alsa/alsa-pcm-sink.c @@ -56,9 +56,9 @@ static void emit_node_info(struct state *this, bool full) if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { - struct spa_dict_item items[4]; + struct spa_dict_item items[7]; uint32_t i, n_items = 0; - char latency[64]; + char latency[64], period[64], nperiods[64], headroom[64]; items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "alsa"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Sink"); @@ -66,6 +66,12 @@ static void emit_node_info(struct state *this, bool full) if (this->have_format) { snprintf(latency, sizeof(latency), "%lu/%d", this->buffer_frames / 2, this->rate); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_MAX_LATENCY, latency); + snprintf(period, sizeof(period), "%lu", this->period_frames); + items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-size", period); + snprintf(nperiods, sizeof(nperiods), "%lu", this->buffer_frames / this->period_frames); + items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-num", nperiods); + snprintf(headroom, sizeof(headroom), "%u", this->headroom); + items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.headroom", headroom); } this->info.props = &SPA_DICT_INIT(items, n_items); @@ -616,7 +622,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 +679,7 @@ static int port_set_format(void *object, } emit_port_info(this, false); - return 0; + return err; } static int @@ -707,6 +713,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 +804,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..7f72d53ab62fbc5f1254757f6b7e6fd6024f9289 100644 --- a/spa/plugins/alsa/alsa-pcm-source.c +++ b/spa/plugins/alsa/alsa-pcm-source.c @@ -57,9 +57,9 @@ static void emit_node_info(struct state *this, bool full) if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { - struct spa_dict_item items[4]; + struct spa_dict_item items[7]; uint32_t i, n_items = 0; - char latency[64]; + char latency[64], period[64], nperiods[64], headroom[64]; items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "alsa"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Source"); @@ -67,6 +67,12 @@ static void emit_node_info(struct state *this, bool full) if (this->have_format) { snprintf(latency, sizeof(latency), "%lu/%d", this->buffer_frames / 2, this->rate); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_MAX_LATENCY, latency); + snprintf(period, sizeof(period), "%lu", this->period_frames); + items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-size", period); + snprintf(nperiods, sizeof(nperiods), "%lu", this->buffer_frames / this->period_frames); + items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-num", nperiods); + snprintf(headroom, sizeof(headroom), "%u", this->headroom); + items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.headroom", headroom); } this->info.props = &SPA_DICT_INIT(items, n_items); @@ -566,7 +572,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 +616,7 @@ static int port_set_format(void *object, } emit_port_info(this, false); - return 0; + return err; } static int @@ -753,8 +759,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..d0cdb3e58c9412fd26172e6f76814b46ed66ad87 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,34 @@ 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"); + + state->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; + state->rate_limit.burst = 1; + 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 +542,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 +573,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 +593,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) @@ -751,8 +785,8 @@ static bool uint32_array_contains(uint32_t *vals, uint32_t n_vals, uint32_t val) return false; } -static int add_rate(struct state *state, uint32_t scale, bool all, uint32_t index, uint32_t *next, - snd_pcm_hw_params_t *params, struct spa_pod_builder *b) +static int add_rate(struct state *state, uint32_t scale, uint32_t interleave, bool all, uint32_t index, uint32_t *next, + uint32_t min_allowed_rate, snd_pcm_hw_params_t *params, struct spa_pod_builder *b) { struct spa_pod_frame f[1]; int err, dir; @@ -763,6 +797,14 @@ 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 interleave:%u all:%d", + min, max, min_allowed_rate, scale, interleave, all); + + min = SPA_MAX(min_allowed_rate * scale / interleave, min) * interleave / scale; + max = max * interleave / scale; + if (max < min) + return 0; + if (!state->multi_rate && state->card->format_ref > 0) rate = state->card->rate; else @@ -779,6 +821,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 +875,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 +957,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 +982,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 +1073,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, 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 +1130,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 +1168,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, 1, true, index & 0xffff, next, 0, params, b)) != 1) return res; (*next)++; @@ -1135,6 +1193,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 +1225,15 @@ 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, 8, SPA_ABS(interleave), true, index & 0xffff, + next, 44100, params, b)) != 1) return res; if ((res = add_channels(state, true, index & 0xffff, next, params, b)) != 1) @@ -1187,7 +1255,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 +1321,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 +1436,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 +1446,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 +1490,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 +1513,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 +1636,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; } @@ -1689,15 +1782,17 @@ recover: static int get_avail(struct state *state, uint64_t current_time) { - int res; + int res, missed; snd_pcm_sframes_t avail; if (SPA_UNLIKELY((avail = snd_pcm_avail(state->hndl)) < 0)) { if ((res = alsa_recover(state, avail)) < 0) return res; if ((avail = snd_pcm_avail(state->hndl)) < 0) { - spa_log_warn(state->log, "%s: snd_pcm_avail after recover: %s", - state->props.device, snd_strerror(avail)); + if ((missed = ratelimit_test(&state->rate_limit, current_time)) >= 0) { + spa_log_warn(state->log, "%s: (%d missed) snd_pcm_avail after recover: %s", + state->props.device, missed, snd_strerror(avail)); + } avail = state->threshold * 2; } } else { @@ -1709,7 +1804,7 @@ static int get_avail(struct state *state, uint64_t current_time) #if 0 static int get_avail_htimestamp(struct state *state, uint64_t current_time) { - int res; + int res, missed; snd_pcm_uframes_t avail; snd_htimestamp_t tstamp; uint64_t then; @@ -1718,8 +1813,10 @@ static int get_avail_htimestamp(struct state *state, uint64_t current_time) if ((res = alsa_recover(state, avail)) < 0) return res; if ((res = snd_pcm_htimestamp(state->hndl, &avail, &tstamp)) < 0) { - spa_log_warn(state->log, "%s: snd_pcm_htimestamp error: %s", - state->props.device, snd_strerror(res)); + if ((missed = ratelimit_test(&state->rate_limit, current_time)) >= 0) { + spa_log_warn(state->log, "%s: (%d missed) snd_pcm_htimestamp error: %s", + state->props.device, missed, snd_strerror(res)); + } avail = state->threshold * 2; } } else { @@ -1762,6 +1859,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; } @@ -1789,15 +1887,14 @@ static int update_time(struct state *state, uint64_t current_time, snd_pcm_sfram state, follower, state->last_threshold, state->threshold, diff, err); state->last_threshold = state->threshold; state->alsa_sync = true; + state->alsa_sync_warning = false; } if (err > state->max_error) { err = state->max_error; state->alsa_sync = true; - state->alsa_sync_warning = (diff == 0); } else if (err < -state->max_error) { err = -state->max_error; state->alsa_sync = true; - state->alsa_sync_warning = (diff == 0); } if (!follower || state->matching) @@ -1894,7 +1991,8 @@ int spa_alsa_write(struct state *state) const snd_pcm_channel_area_t *my_areas; snd_pcm_uframes_t written, frames, offset, off, to_write, total_written, max_write; snd_pcm_sframes_t commitres; - int res = 0; + int res = 0, missed; + size_t frame_size = state->frame_size; check_position_config(state); @@ -1913,13 +2011,18 @@ int spa_alsa_write(struct state *state) return res; if (SPA_UNLIKELY(state->alsa_sync)) { - if (SPA_UNLIKELY(state->alsa_sync_warning)) { - spa_log_warn(state->log, "%s: follower delay:%ld target:%ld thr:%u, resync", - state->props.device, delay, target, state->threshold); - state->alsa_sync_warning = false; - } else - spa_log_info(state->log, "%s: follower delay:%ld target:%ld thr:%u, resync", - state->props.device, delay, target, state->threshold); + enum spa_log_level lev; + + if (SPA_UNLIKELY(state->alsa_sync_warning)) + lev = SPA_LOG_LEVEL_WARN; + else + lev = SPA_LOG_LEVEL_INFO; + + if ((missed = ratelimit_test(&state->rate_limit, current_time)) >= 0) { + spa_log_lev(state->log, lev, "%s: follower delay:%ld target:%ld thr:%u, " + "resync (%d missed)", state->props.device, delay, + target, state->threshold, missed); + } if (delay > target) snd_pcm_rewind(state->hndl, delay - target); @@ -1927,7 +2030,8 @@ int spa_alsa_write(struct state *state) spa_alsa_silence(state, target - delay); delay = target; state->alsa_sync = false; - } + } else + state->alsa_sync_warning = true; } total_written = 0; @@ -1951,61 +2055,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 +2157,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 +2173,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 +2200,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); @@ -2136,7 +2224,7 @@ int spa_alsa_read(struct state *state) const snd_pcm_channel_area_t *my_areas; snd_pcm_uframes_t read, frames, offset; snd_pcm_sframes_t commitres; - int res = 0; + int res = 0, missed; check_position_config(state); @@ -2145,7 +2233,6 @@ int spa_alsa_read(struct state *state) if (state->following && state->alsa_started) { uint64_t current_time; snd_pcm_uframes_t avail, delay, target; - uint32_t threshold = state->threshold; current_time = state->position->clock.nsec; @@ -2158,13 +2245,18 @@ int spa_alsa_read(struct state *state) return res; if (state->alsa_sync) { - if (SPA_UNLIKELY(state->alsa_sync_warning)) { - spa_log_warn(state->log, "%s: follower delay:%lu target:%lu thr:%u, resync", - state->props.device, delay, target, threshold); - state->alsa_sync_warning = false; - } else - spa_log_info(state->log, "%s: follower delay:%lu target:%lu thr:%u, resync", - state->props.device, delay, target, threshold); + enum spa_log_level lev; + + if (SPA_UNLIKELY(state->alsa_sync_warning)) + lev = SPA_LOG_LEVEL_WARN; + else + lev = SPA_LOG_LEVEL_INFO; + + if ((missed = ratelimit_test(&state->rate_limit, current_time)) >= 0) { + spa_log_lev(state->log, lev, "%s: follower delay:%ld target:%ld thr:%u, " + "resync (%d missed)", state->props.device, delay, + target, state->threshold, missed); + } if (delay < target) max_read = target - delay; @@ -2172,7 +2264,8 @@ int spa_alsa_read(struct state *state) snd_pcm_forward(state->hndl, delay - target); delay = target; state->alsa_sync = false; - } + } else + state->alsa_sync_warning = true; if (avail < state->read_size) max_read = 0; @@ -2350,7 +2443,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 +2538,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", @@ -2463,6 +2554,7 @@ int spa_alsa_start(struct state *state) reset_buffers(state); state->alsa_sync = true; + state->alsa_sync_warning = false; state->alsa_recovering = false; state->alsa_started = false; diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index 6f16e36961beba10b3e1c1c13bbd2373fa4789f6..c630de3acc349b5fc919f72c602cabefa4997a82 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -87,7 +87,6 @@ struct channel_map { uint32_t pos[SPA_AUDIO_MAX_CHANNELS]; }; - struct card { struct spa_list link; int ref; @@ -98,6 +97,13 @@ struct card { uint32_t rate; }; +struct ratelimit { + uint64_t interval; + uint64_t begin; + unsigned burst; + unsigned n_printed, n_missed; +}; + struct state { struct spa_handle handle; struct spa_node node; @@ -106,6 +112,9 @@ struct state { struct spa_system *data_system; struct spa_loop *data_loop; + FILE *log_file; + struct ratelimit rate_limit; + uint32_t card_index; struct card *card; snd_pcm_stream_t stream; @@ -340,6 +349,23 @@ static inline uint32_t spa_alsa_get_iec958_codecs(struct state *state, uint32_t return i; } +static inline int ratelimit_test(struct ratelimit *r, uint64_t now) +{ + unsigned missed = 0; + if (r->begin + r->interval < now) { + missed = r->n_missed; + r->begin = now; + r->n_printed = 0; + r->n_missed = 0; + } else if (r->n_printed >= r->burst) { + r->n_missed++; + return -1; + } + r->n_printed++; + return missed; +} + + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/spa/plugins/alsa/alsa-seq-bridge.c b/spa/plugins/alsa/alsa-seq-bridge.c index 57ce40434f3ed6989682939f0480416f5cb0187c..bf29301472d7c0639a35e0dcec91d2e468987c23 100644 --- a/spa/plugins/alsa/alsa-seq-bridge.c +++ b/spa/plugins/alsa/alsa-seq-bridge.c @@ -48,6 +48,7 @@ static void reset_props(struct props *props) { strncpy(props->device, DEFAULT_DEVICE, sizeof(props->device)); strncpy(props->clock_name, DEFAULT_CLOCK_NAME, sizeof(props->clock_name)); + props->disable_longname = 0; } static int impl_node_enum_params(void *object, int seq, @@ -249,7 +250,7 @@ static void emit_port_info(struct seq_state *this, struct seq_port *port, bool f snd_seq_port_info_t *info; snd_seq_client_info_t *client_info; char card[8]; - char name[128]; + char name[256]; char path[128]; char alias[128]; @@ -261,11 +262,34 @@ static void emit_port_info(struct seq_state *this, struct seq_port *port, bool f snd_seq_get_any_client_info(this->sys.hndl, port->addr.client, client_info); - snprintf(name, sizeof(name), "%s:(%s_%d) %s", - snd_seq_client_info_get_name(client_info), - port->direction == SPA_DIRECTION_OUTPUT ? "capture" : "playback", - port->addr.port, - snd_seq_port_info_get_name(info)); + int card_id; + + // Failed to obtain card number (software device) or disabled + if (this->props.disable_longname || (card_id = snd_seq_client_info_get_card(client_info)) < 0) { + snprintf(name, sizeof(name), "%s:(%s_%d) %s", + snd_seq_client_info_get_name(client_info), + port->direction == SPA_DIRECTION_OUTPUT ? "capture" : "playback", + port->addr.port, + snd_seq_port_info_get_name(info)); + } else { + char *longname; + if (snd_card_get_longname(card_id, &longname) == 0) { + snprintf(name, sizeof(name), "%s:(%s_%d) %s", + longname, + port->direction == SPA_DIRECTION_OUTPUT ? "capture" : "playback", + port->addr.port, + snd_seq_port_info_get_name(info)); + free(longname); + } else { + // At least add card number to be distinct + snprintf(name, sizeof(name), "%s %d:(%s_%d) %s", + snd_seq_client_info_get_name(client_info), + card_id, + port->direction == SPA_DIRECTION_OUTPUT ? "capture" : "playback", + port->addr.port, + snd_seq_port_info_get_name(info)); + } + } clean_name(name); snprintf(path, sizeof(path), "alsa:seq:%s:client_%d:%s_%d", @@ -927,6 +951,8 @@ impl_init(const struct spa_handle_factory *factory, } else if (spa_streq(k, "clock.name")) { spa_scnprintf(this->props.clock_name, sizeof(this->props.clock_name), "%s", s); + } else if (spa_streq(k, SPA_KEY_API_ALSA_DISABLE_LONGNAME)) { + this->props.disable_longname = spa_atob(s); } } diff --git a/spa/plugins/alsa/alsa-seq.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-seq.h b/spa/plugins/alsa/alsa-seq.h index 91f763e241df52b8d12e39dedffb686cb3315d7b..5d5ed51378de2fe6ea95c9585a898b6341a833bd 100644 --- a/spa/plugins/alsa/alsa-seq.h +++ b/spa/plugins/alsa/alsa-seq.h @@ -52,6 +52,7 @@ extern "C" { struct props { char device[64]; char clock_name[64]; + bool disable_longname; }; #define MAX_EVENT_SIZE 1024 diff --git a/spa/plugins/alsa/alsa-udev.c b/spa/plugins/alsa/alsa-udev.c index 8501ff642a6b086f11bcd741e6261434271babf2..30975523fbff30de2ea03d43ae5fb8a5813179c9 100644 --- a/spa/plugins/alsa/alsa-udev.c +++ b/spa/plugins/alsa/alsa-udev.c @@ -256,7 +256,7 @@ static int check_device_pcm_class(const char *devname) /* Check device class */ spa_scnprintf(path, sizeof(path), "/sys/class/sound/%s/pcm_class", devname); - f = fopen(path, "r"); + f = fopen(path, "re"); if (f == NULL) return -errno; sz = fread(buf, 1, sizeof(buf) - 1, f); @@ -361,7 +361,7 @@ static int check_device_available(struct impl *this, struct device *device, int spa_scnprintf(path, sizeof(path), "/proc/asound/card%u/%s/%s/status", (unsigned int)device->id, entry->d_name, entry_pcm->d_name); - f = fopen(path, "r"); + f = fopen(path, "re"); if (f == NULL) goto done; sz = fread(buf, 1, 6, f); @@ -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/mixer/profile-sets/asus-xonar-se.conf b/spa/plugins/alsa/mixer/profile-sets/asus-xonar-se.conf new file mode 100644 index 0000000000000000000000000000000000000000..a47d1ba4466530e12abc328e95685fb56109633c --- /dev/null +++ b/spa/plugins/alsa/mixer/profile-sets/asus-xonar-se.conf @@ -0,0 +1,79 @@ +# This file is part of PulseAudio. +# +# 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/>. + +; ASUS Xonar SE card. +; This card has two devices for each rear and front panel jacks. +; +; See default.conf for an explanation on the directives used here. + +[General] +auto-profiles = yes + +[Mapping analog-stereo-front] +description = Analog Stereo Front +device-strings = hw:%f,1 +channel-map = left,right +paths-output = analog-output analog-output-headphones +paths-input = analog-input-mic analog-input-headphone-mic analog-input-headset-mic +priority = 15 + +[Mapping analog-stereo-rear] +description = Analog Stereo Rear +device-strings = hw:%f,0 +channel-map = left,right +paths-output = analog-output analog-output-speaker +paths-input = analog-input analog-input-mic analog-input-linein +priority = 14 + +[Mapping analog-surround-21] +device-strings = surround21:%f +channel-map = front-left,front-right,lfe +paths-output = analog-output-speaker +priority = 13 +direction = output + +[Mapping analog-surround-40] +device-strings = surround40:%f +channel-map = front-left,front-right,rear-left,rear-right +paths-output = analog-output-speaker +priority = 12 +direction = output + +[Mapping analog-surround-41] +device-strings = surround41:%f +channel-map = front-left,front-right,rear-left,rear-right,lfe +paths-output = analog-output-speaker +priority = 13 +direction = output + +[Mapping analog-surround-50] +device-strings = surround50:%f +channel-map = front-left,front-right,rear-left,rear-right,front-center +paths-output = analog-output-speaker +priority = 12 +direction = output + +[Mapping analog-surround-51] +device-strings = surround51:%f +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe +paths-output = analog-output-speaker +priority = 13 +direction = output + +[Mapping iec958-stereo] +device-strings = iec958:%f +channel-map = left,right +paths-output = iec958-stereo-output +priority = 5 \ No newline at end of file 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..21bf45f822e960f2c16c19fcb030ee24447f4e13 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 */ @@ -336,7 +336,7 @@ static int negotiate_buffers(struct impl *this) struct spa_data *datas; uint32_t follower_flags, conv_flags; - spa_log_debug(this->log, "%p: %d", this, this->n_buffers); + spa_log_debug(this->log, "%p: n_buffers:%d", this, this->n_buffers); if (this->target == this->follower) return 0; @@ -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; @@ -723,6 +738,8 @@ static int negotiate_format(struct impl *this) struct spa_pod_builder b = { 0 }; int res; + spa_log_debug(this->log, "%p: have_format:%d", this, this->have_format); + if (this->have_format) return 0; @@ -731,7 +748,6 @@ static int negotiate_format(struct impl *this) spa_pod_builder_init(&b, buffer, sizeof(buffer)); - spa_log_debug(this->log, "%p: negiotiate", this); spa_node_send_command(this->follower, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamBegin)); @@ -777,7 +793,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, @@ -798,6 +814,8 @@ static int impl_node_send_command(void *object, const struct spa_command *comman switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: + if (this->started) + return 0; if ((res = negotiate_format(this)) < 0) return res; if ((res = negotiate_buffers(this)) < 0) @@ -806,11 +824,12 @@ static int impl_node_send_command(void *object, const struct spa_command *comman case SPA_NODE_COMMAND_Suspend: configure_format(this, 0, NULL); SPA_FALLTHROUGH - case SPA_NODE_COMMAND_Flush: - this->io_buffers.status = SPA_STATUS_OK; - SPA_FALLTHROUGH case SPA_NODE_COMMAND_Pause: this->started = false; + spa_log_debug(this->log, "%p: stopped", this); + break; + case SPA_NODE_COMMAND_Flush: + this->io_buffers.status = SPA_STATUS_OK; break; default: break; @@ -834,6 +853,7 @@ static int impl_node_send_command(void *object, const struct spa_command *comman switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: this->started = true; + spa_log_debug(this->log, "%p: started", this); break; } return res; @@ -852,6 +872,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 +1158,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); @@ -1362,6 +1401,11 @@ static int impl_node_process(void *object) struct impl *this = object; int status = 0, fstatus, retry = 8; + if (!this->started) { + spa_log_warn(this->log, "%p: scheduling stopped node", this); + return -EIO; + } + spa_log_trace_fp(this->log, "%p: process convert:%p driver:%d", this, this->convert, this->driver); diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 02c7bc1a99bcc56c8aa0041850d473c5dd5f5b4a..9aa4a3c57ba446e7dee70d053a3e1e3187fa5475 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,155 @@ #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/json.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_disabled:1; + unsigned int resample_quality; + 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->have_soft_volume = false; + props->mix_disabled = false; + props->resample_disabled = false; + props->resample_quality = RESAMPLE_DEFAULT_QUALITY; + props->rate = 1.0; +} 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 +183,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 +199,49 @@ 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 setup:1; + unsigned int resample_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 +259,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 +366,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 +373,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 +415,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 +429,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 +751,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 +758,295 @@ 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; + 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; +} - spa_log_debug(this->log, "%p: %d.%d info", this, direction, port); +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; - if (direction == SPA_DIRECTION_INPUT || is_monitor) - spa_node_emit_port_info(&this->hooks, direction, port, info); -} + spa_pod_parser_pod(&prs, params); + if (spa_pod_parser_push_struct(&prs, &f) < 0) + return 0; -static const struct spa_node_events fmt_input_events = { - SPA_VERSION_NODE_EVENTS, - .port_info = fmt_input_port_info, -}; + while (true) { + const char *name; + struct spa_pod *pod; + char value[512]; -static void fmt_output_port_info(void *data, - enum spa_direction direction, uint32_t port, - const struct spa_port_info *info) -{ - struct impl *this = data; + if (spa_pod_parser_get_string(&prs, &name) < 0) + break; - if (this->fmt_removing[direction]) - info = NULL; + if (spa_pod_parser_get_pod(&prs, &pod) < 0) + break; - spa_log_debug(this->log, "%p: %d.%d info", this, direction, port); + 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 if (spa_pod_is_none(pod)) { + spa_zero(value); + } else + continue; - if (direction == SPA_DIRECTION_OUTPUT) - spa_node_emit_port_info(&this->hooks, direction, port, info); + spa_log_info(this->log, "key:'%s' val:'%s'", name, value); + changed += audioconvert_set_param(this, name, value); + } + return changed; } -static const struct spa_node_events fmt_output_events = { - SPA_VERSION_NODE_EVENTS, - .port_info = fmt_output_port_info, -}; - -static void on_channelmix_info(void *data, const struct spa_node_info *info) +static int apply_props(struct impl *this, const struct spa_pod *param) { - struct impl *this = data; - uint32_t i; - - if ((info->change_mask & SPA_NODE_CHANGE_MASK_PARAMS) == 0) - return; - - for (i = 0; i < info->n_params; i++) { - uint32_t idx; - - switch (info->params[i].id) { - case SPA_PARAM_PropInfo: - idx = IDX_PropInfo; + 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_PROP_mute: + if (spa_pod_get_bool(&prop->value, &p->channel.mute) == 0) { + have_channel_volume = true; + changed++; + } break; - case SPA_PARAM_Props: - idx = IDX_Props; + 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 (!this->add_listener) - this->params[idx].user++; } - emit_node_info(this, false); + if (changed) { + if (have_soft_volume) + p->have_soft_volume = true; + else if (have_channel_volume) + p->have_soft_volume = false; + + channelmix_init(&this->mix); + set_volume(this); + } + 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; - - spa_log_debug(this->log, "%p: mode %d", this, mode); + const uint8_t *val = SPA_POD_BODY(value); + uint32_t size = SPA_POD_BODY_SIZE(value); + struct props *p = &this->props; - /* 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 +1059,506 @@ 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; - res = reconfigure_mode(this, mode, dir, monitor, infop); + 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; +} + +#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 have_format:%d", this, dir->have_format); + + 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->resample_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]; + + spa_log_debug(this->log, "%p: setup:%d in_format:%d out_format:%d", this, + this->setup, in->have_format, out->have_format); + + if (this->setup) + return 0; + + 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); + } + this->setup = true; + + 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 - case SPA_NODE_COMMAND_Flush: - flush_convert(this); - SPA_FALLTHROUGH + this->setup = false; + SPA_FALLTHROUGH; case SPA_NODE_COMMAND_Pause: this->started = false; break; + case SPA_NODE_COMMAND_Flush: + reset_node(this); + 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 +1569,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 +1594,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,6 +1609,77 @@ impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_ 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(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, @@ -1000,74 +1687,296 @@ impl_node_port_enum_params(void *object, int seq, 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[4096]; + 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: port %d.%d %d %u", this, direction, port_id, seq, id); + 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; + + if (!port->have_format) + return -EIO; + if (result.index > 0) + return 0; + + if (PORT_IS_DSP(this, direction, port_id)) { + /* DSP ports always use the quantum_limit as the buffer + * size. */ + size = this->quantum_limit; + } else { + uint32_t irate, orate; + struct dir *dir = &this->dir[direction]; + + /* Convert ports are scaled so that they can always + * provide one quantum of data */ + irate = dir->format.info.raw.rate; + + /* 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 port *port; + int res; + + 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 +1984,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 +2045,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 +2122,594 @@ 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; + + 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; +} - return spa_node_port_reuse_buffer(target, port_id, buffer_id); +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]; + } - spa_return_val_if_fail(this != NULL, -EINVAL); + 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); + } + } + } + } - spa_log_trace_fp(this->log, "%p: process %d %d", this, this->n_links, this->n_nodes); + /* 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; + } - while (1) { - res = SPA_STATUS_OK; - ready = 0; - for (i = 0; i < this->n_nodes; i++) { - r = spa_node_process(this->nodes[i]); + 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); - spa_log_trace_fp(this->log, "%p: process %d %d: %s", - this, i, r, r < 0 ? spa_strerror(r) : "ok"); + 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); - if (SPA_UNLIKELY(r < 0)) - return r; + 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; + bd->chunk->stride = 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); + } + } + } + } - if (r & SPA_STATUS_HAVE_DATA) - ready++; - 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); + /* 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; } - if (res & SPA_STATUS_HAVE_DATA) - break; - if (ready == 0) - break; + /* 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); + + 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; + } + + 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 (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]; + } + + 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 (!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]; + } + 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; + bd->chunk->stride = port->stride; + SPA_FLAG_UPDATE(bd->chunk->flags, SPA_CHUNK_FLAG_EMPTY, in_empty); + spa_log_trace_fp(this->log, "out: offs:%d stride:%d size:%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->resample_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,46 +2752,66 @@ 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; + 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; - 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); + spa_json_init(&it[0], val, len); + if (spa_json_enter_array(&it[0], &it[1]) <= 0) + spa_json_init(&it[1], val, len); - return size; + 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, @@ -1309,8 +2820,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 +2834,51 @@ 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->resample_peaks = spa_atob(s); + else if (spa_streq(k, "resample.prefill")) + SPA_FLAG_UPDATE(this->resample.options, + RESAMPLE_OPTION_PREFILL, 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 if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) { + if (s != NULL) + this->props.n_channels = parse_position(this->props.channel_map, s, strlen(s)); + } + else + audioconvert_set_param(this, k, s); + } + + this->props.channel.n_volumes = this->props.n_channels; + this->props.soft.n_volumes = this->props.n_channels; + this->props.monitor.n_volumes = this->props.n_channels; + + this->dir[SPA_DIRECTION_INPUT].latency = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); + this->dir[SPA_DIRECTION_OUTPUT].latency = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, @@ -1347,67 +2900,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..f12f35f85aeb322202328b382bb11b06628b6c5e 100644 --- a/spa/plugins/audioconvert/channelmix-ops-c.c +++ b/spa/plugins/audioconvert/channelmix-ops-c.c @@ -46,6 +46,16 @@ static inline void vol_c(float *d, const float *s, float vol, uint32_t n_samples d[n] = s[n] * vol; } } +static inline void conv_c(float *d, const float **s, float *c, uint32_t n_c, uint32_t n_samples) +{ + uint32_t n, j; + for (n = 0; n < n_samples; n++) { + float sum = 0.0f; + for (j = 0; j < n_c; j++) + sum += s[j][n] * c[j]; + d[n] = sum; + } +} static inline void avg_c(float *d, const float *s0, const float *s1, uint32_t n_samples) { @@ -78,7 +88,7 @@ void channelmix_f32_n_m_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { - uint32_t i, j, n, n_dst = mix->dst_chan, n_src = mix->src_chan; + uint32_t i, j, n_dst = mix->dst_chan, n_src = mix->src_chan; float **d = (float **) dst; const float **s = (const float **) src; @@ -94,16 +104,27 @@ channelmix_f32_n_m_c(struct channelmix *mix, void * SPA_RESTRICT dst[], clear_c(d[i], n_samples); } else { - for (n = 0; n < n_samples; n++) { - for (i = 0; i < n_dst; i++) { - float sum = 0.0f; - for (j = 0; j < n_src; j++) - sum += s[j][n] * mix->matrix[i][j]; - d[i][n] = sum; + for (i = 0; i < n_dst; i++) { + float *di = d[i]; + float mj[n_src]; + const float *sj[n_src]; + uint32_t n_j = 0; + + for (j = 0; j < n_src; j++) { + if (mix->matrix[i][j] == 0.0f) + continue; + mj[n_j] = mix->matrix[i][j]; + sj[n_j++] = s[j]; + } + if (n_j == 0) { + clear_c(di, n_samples); + } else if (n_j == 1) { + lr4_process(&mix->lr4[i], di, sj[0], mj[0], n_samples); + } else { + conv_c(di, sj, mj, n_j, n_samples); + lr4_process(&mix->lr4[i], di, di, 1.0f, n_samples); } } - for (i = 0; i < n_dst; i++) - lr4_process(&mix->lr4[i], d[i], d[i], 1.0f, n_samples); } } @@ -177,6 +198,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 +206,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 +331,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 +419,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 +430,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..7189cadd3dbf89b5aaf06506c2b02e0d120ddcd5 100644 --- a/spa/plugins/audioconvert/channelmix-ops-sse.c +++ b/spa/plugins/audioconvert/channelmix-ops-sse.c @@ -26,113 +26,171 @@ #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)); +static inline void copy_sse(float *d, const float *s, uint32_t n_samples) +{ + spa_memcpy(d, s, n_samples * sizeof(float)); +} + +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(&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)); } - 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)); +} + +static inline void conv_sse(float *d, const float **s, float *c, uint32_t n_c, uint32_t n_samples) +{ + __m128 mi[n_c], sum[2]; + uint32_t n, j, unrolled; + bool aligned = true; + + for (j = 0; j < n_c; j++) { + mi[j] = _mm_set1_ps(c[j]); + aligned &= SPA_IS_ALIGNED(s[j], 16); } - 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]); - if (SPA_IS_ALIGNED(di, 16) && - SPA_IS_ALIGNED(si, 16)) - unrolled = n_samples & ~15; - else - unrolled = 0; + if (aligned && SPA_IS_ALIGNED(d, 16)) + unrolled = n_samples & ~7; + 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 += 8) { + sum[0] = sum[1] = _mm_setzero_ps(); + for (j = 0; j < n_c; j++) { + sum[0] = _mm_add_ps(sum[0], _mm_mul_ps(_mm_load_ps(&s[j][n + 0]), mi[j])); + sum[1] = _mm_add_ps(sum[1], _mm_mul_ps(_mm_load_ps(&s[j][n + 4]), mi[j])); } + _mm_store_ps(&d[n + 0], sum[0]); + _mm_store_ps(&d[n + 4], sum[1]); + } + for (; n < n_samples; n++) { + sum[0] = _mm_setzero_ps(); + for (j = 0; j < n_c; j++) + sum[0] = _mm_add_ss(sum[0], _mm_mul_ss(_mm_load_ss(&s[j][n]), mi[j])); + _mm_store_ss(&d[n], sum[0]); } } -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; +void +channelmix_f32_n_m_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; + uint32_t i, j, n_dst = mix->dst_chan, n_src = mix->src_chan; - 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 (i = 0; i < n_dst; i++) { + float *di = d[i]; + float mj[n_src]; + const float *sj[n_src]; + uint32_t n_j = 0; + + for (j = 0; j < n_src; j++) { + if (mix->matrix[i][j] == 0.0f) + continue; + mj[n_j] = mix->matrix[i][j]; + sj[n_j++] = s[j]; } - 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 (n_j == 0) { + clear_sse(di, n_samples); + } else if (n_j == 1) { + if (mix->lr4[i].active) + lr4_process(&mix->lr4[i], di, sj[0], mj[0], n_samples); + else + vol_sse(di, sj[0], mj[0], n_samples); + } else { + conv_sse(di, sj, mj, n_j, n_samples); + lr4_process(&mix->lr4[i], di, di, 1.0f, n_samples); } } +} + +/* 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 (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 +210,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 +265,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 +318,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..025b2a8eb1dc1eb06ea7466bf7fc995bceb6b4c5 100644 --- a/spa/plugins/audioconvert/channelmix-ops.c +++ b/spa/plugins/audioconvert/channelmix-ops.c @@ -31,30 +31,18 @@ #include <spa/support/log.h> #include <spa/utils/defs.h> -#define VOLUME_MIN 0.0f -#define VOLUME_NORM 1.0f - #include "channelmix-ops.h" #include "hilbert.h" -#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) -#define MASK_STEREO _M(FL)|_M(FR)|_M(UNKNOWN) -#define MASK_QUAD _M(FL)|_M(FR)|_M(RL)|_M(RR)|_M(UNKNOWN) -#define MASK_3_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE) -#define MASK_5_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE)|_M(SL)|_M(SR)|_M(RL)|_M(RR) -#define MASK_7_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE)|_M(SL)|_M(SR)|_M(RL)|_M(RR) - #define ANY ((uint32_t)-1) #define EQ ((uint32_t)-2) 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 +50,56 @@ 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" }, +#if defined (HAVE_SSE) + MAKE(ANY, 0, ANY, 0, channelmix_f32_n_m_sse), +#endif + 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) @@ -151,6 +145,8 @@ static int make_matrix(struct channelmix *mix) float matrix[SPA_AUDIO_MAX_CHANNELS][SPA_AUDIO_MAX_CHANNELS] = {{ 0.0f }}; uint64_t src_mask = mix->src_mask; uint64_t dst_mask = mix->dst_mask; + uint32_t src_chan = mix->src_chan; + uint32_t dst_chan = mix->dst_chan; uint64_t unassigned, keep; uint32_t i, j, ic, jc, matrix_encoding = MATRIX_NORMAL; float clev = SQRT1_2; @@ -166,7 +162,7 @@ static int make_matrix(struct channelmix *mix) /* move the MONO mask to FRONT so that the lower bits can be shifted * away. */ if ((src_mask & (1Ull << SPA_AUDIO_CHANNEL_MONO)) != 0) { - if (mix->src_chan == 1) + if (src_chan == 1) src_mask = 0; else src_mask |= (1ULL << SPA_AUDIO_CHANNEL_FC); @@ -180,29 +176,33 @@ static int make_matrix(struct channelmix *mix) /* unknown channels or just 1 channel */ if (src_mask == 0 || dst_mask == 0) { - if (mix->src_chan == 1) { + if (src_chan == 1) { /* one FC/MONO src goes everywhere */ - spa_log_debug(mix->log, "distribute FC/MONO"); + spa_log_debug(mix->log, "distribute FC/MONO (%f)", 1.0f); for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) matrix[i][0]= 1.0f; - } else if (mix->dst_chan == 1) { + } else if (dst_chan == 1) { /* one FC/MONO dst get average of everything */ - spa_log_debug(mix->log, "average FC/MONO"); + spa_log_debug(mix->log, "average FC/MONO (%f)", 1.0f / src_chan); for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) - matrix[0][i]= 1.0f / mix->src_chan; + matrix[0][i]= 1.0f / src_chan; } else { /* just pair channels */ - spa_log_debug(mix->log, "pairing channels"); + spa_log_debug(mix->log, "pairing channels (%f)", 1.0f); for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) matrix[i][i]= 1.0f; } + if (dst_mask & FRONT) + filter_fc = true; + if (dst_mask & _MASK(LFE)) + filter_lfe = true; src_mask = dst_mask = ~0LU; goto done; } else { spa_log_debug(mix->log, "matching channels"); for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) { if ((src_mask & dst_mask & (1ULL << i))) { - spa_log_debug(mix->log, "matched %u", i); + spa_log_debug(mix->log, "matched channel %u (%f)", i, 1.0f); matrix[i][i]= 1.0f; } } @@ -227,11 +227,12 @@ static int make_matrix(struct channelmix *mix) if (unassigned & FRONT){ if ((dst_mask & STEREO) == STEREO){ - spa_log_debug(mix->log, "assign FC to STEREO"); if(src_mask & STEREO) { + spa_log_debug(mix->log, "assign FC to STEREO (%f)", clev); _MATRIX(FL,FC) += clev; _MATRIX(FR,FC) += clev; } else { + spa_log_debug(mix->log, "assign FC to STEREO (%f)", SQRT1_2); _MATRIX(FL,FC) += SQRT1_2; _MATRIX(FR,FC) += SQRT1_2; } @@ -242,11 +243,13 @@ static int make_matrix(struct channelmix *mix) if (unassigned & STEREO){ if (dst_mask & FRONT) { - spa_log_debug(mix->log, "assign STEREO to FC"); + spa_log_debug(mix->log, "assign STEREO to FC (%f)", SQRT1_2); _MATRIX(FC,FL) += SQRT1_2; _MATRIX(FC,FR) += SQRT1_2; - if (src_mask & FRONT) + if (src_mask & FRONT) { + spa_log_debug(mix->log, "assign FC to FC (%f)", clev * SQRT2); _MATRIX(FC,FC) = clev * SQRT2; + } keep &= ~FRONT; } else { spa_log_warn(mix->log, "can't assign STEREO"); @@ -255,11 +258,11 @@ static int make_matrix(struct channelmix *mix) if (unassigned & _MASK(RC)) { if (dst_mask & REAR){ - spa_log_debug(mix->log, "assign RC to RL+RR"); + spa_log_debug(mix->log, "assign RC to RL+RR (%f)", SQRT1_2); _MATRIX(RL,RC) += SQRT1_2; _MATRIX(RR,RC) += SQRT1_2; } else if (dst_mask & SIDE) { - spa_log_debug(mix->log, "assign RC to SL+SR"); + spa_log_debug(mix->log, "assign RC to SL+SR (%f)", SQRT1_2); _MATRIX(SL,RC) += SQRT1_2; _MATRIX(SR,RC) += SQRT1_2; } else if(dst_mask & STEREO) { @@ -278,7 +281,7 @@ static int make_matrix(struct channelmix *mix) _MATRIX(FR,RC) += slev * SQRT1_2; } } else if (dst_mask & FRONT) { - spa_log_debug(mix->log, "assign RC to FC"); + spa_log_debug(mix->log, "assign RC to FC (%f)", slev * SQRT1_2); _MATRIX(FC,RC) += slev * SQRT1_2; } else { spa_log_warn(mix->log, "can't assign RC"); @@ -300,7 +303,7 @@ static int make_matrix(struct channelmix *mix) _MATRIX(SR,RR) += 1.0f; } } else if (dst_mask & STEREO) { - spa_log_debug(mix->log, "assign RL+RR to FL+FR %f", slev); + spa_log_debug(mix->log, "assign RL+RR to FL+FR (%f)", slev); if (matrix_encoding == MATRIX_DOLBY) { _MATRIX(FL,RL) -= slev * SQRT1_2; _MATRIX(FL,RR) -= slev * SQRT1_2; @@ -316,7 +319,8 @@ static int make_matrix(struct channelmix *mix) _MATRIX(FR,RR) += slev; } } else if (dst_mask & FRONT) { - spa_log_debug(mix->log, "assign RL+RR to FC"); + spa_log_debug(mix->log, "assign RL+RR to FC (%f)", + slev * SQRT1_2); _MATRIX(FC,RL)+= slev * SQRT1_2; _MATRIX(FC,RR)+= slev * SQRT1_2; } else { @@ -326,36 +330,41 @@ static int make_matrix(struct channelmix *mix) if (unassigned & SIDE) { if (dst_mask & REAR) { - spa_log_debug(mix->log, "assign SL+SR to RL+RR"); if (src_mask & _MASK(RL)) { + spa_log_debug(mix->log, "assign SL+SR to RL+RR (%f)", SQRT1_2); _MATRIX(RL,SL) += SQRT1_2; _MATRIX(RR,SR) += SQRT1_2; } else { + spa_log_debug(mix->log, "assign SL+SR to RL+RR (%f)", 1.0f); _MATRIX(RL,SL) += 1.0f; _MATRIX(RR,SR) += 1.0f; } } else if (dst_mask & _MASK(RC)) { - spa_log_debug(mix->log, "assign SL+SR to RC"); + spa_log_debug(mix->log, "assign SL+SR to RC (%f)", SQRT1_2); _MATRIX(RC,SL)+= SQRT1_2; _MATRIX(RC,SR)+= SQRT1_2; } else if (dst_mask & STEREO) { - spa_log_debug(mix->log, "assign SL+SR to FL+FR"); if (matrix_encoding == MATRIX_DOLBY) { + spa_log_debug(mix->log, "assign SL+SR to FL+FR (%f)", + slev * SQRT1_2); _MATRIX(FL,SL) -= slev * SQRT1_2; _MATRIX(FL,SR) -= slev * SQRT1_2; _MATRIX(FR,SL) += slev * SQRT1_2; _MATRIX(FR,SR) += slev * SQRT1_2; } else if (matrix_encoding == MATRIX_DPLII) { + spa_log_debug(mix->log, "assign SL+SR to FL+FR (%f / %f)", + slev * SQRT3_2, slev * SQRT1_2); _MATRIX(FL,SL) -= slev * SQRT3_2; _MATRIX(FL,SR) -= slev * SQRT1_2; _MATRIX(FR,SL) += slev * SQRT1_2; _MATRIX(FR,SR) += slev * SQRT3_2; } else { + spa_log_debug(mix->log, "assign SL+SR to FL+FR (%f)", slev); _MATRIX(FL,SL) += slev; _MATRIX(FR,SR) += slev; } } else if (dst_mask & FRONT) { - spa_log_debug(mix->log, "assign SL+SR to FC"); + spa_log_debug(mix->log, "assign SL+SR to FC (%f)", slev * SQRT1_2); _MATRIX(FC,SL) += slev * SQRT1_2; _MATRIX(FC,SR) += slev * SQRT1_2; } else { @@ -365,11 +374,11 @@ static int make_matrix(struct channelmix *mix) if (unassigned & _MASK(FLC)) { if (dst_mask & STEREO) { - spa_log_debug(mix->log, "assign FLC+FRC to FL+FR"); + spa_log_debug(mix->log, "assign FLC+FRC to FL+FR (%f)", 1.0f); _MATRIX(FL,FLC)+= 1.0f; _MATRIX(FR,FRC)+= 1.0f; } else if(dst_mask & FRONT) { - spa_log_debug(mix->log, "assign FLC+FRC to FC"); + spa_log_debug(mix->log, "assign FLC+FRC to FC (%f)", SQRT1_2); _MATRIX(FC,FLC)+= SQRT1_2; _MATRIX(FC,FRC)+= SQRT1_2; } else { @@ -379,10 +388,11 @@ static int make_matrix(struct channelmix *mix) if (unassigned & _MASK(LFE) && SPA_FLAG_IS_SET(mix->options, CHANNELMIX_OPTION_MIX_LFE)) { if (dst_mask & FRONT) { - spa_log_debug(mix->log, "assign LFE to FC"); + spa_log_debug(mix->log, "assign LFE to FC (%f)", llev); _MATRIX(FC,LFE) += llev; } else if (dst_mask & STEREO) { - spa_log_debug(mix->log, "assign LFE to FL+FR"); + spa_log_debug(mix->log, "assign LFE to FL+FR (%f)", + llev * SQRT1_2); _MATRIX(FL,LFE) += llev * SQRT1_2; _MATRIX(FR,LFE) += llev * SQRT1_2; } else { @@ -395,9 +405,18 @@ 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 (%f)", clev); + _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"); + spa_log_debug(mix->log, "produce FC from STEREO (%f)", clev); _MATRIX(FC,FL) += clev; _MATRIX(FC,FR) += clev; filter_fc = true; @@ -407,12 +426,12 @@ static int make_matrix(struct channelmix *mix) } if (unassigned & _MASK(LFE)) { if ((src_mask & STEREO) == STEREO) { - spa_log_debug(mix->log, "produce LFE from STEREO"); + spa_log_debug(mix->log, "produce LFE from STEREO (%f)", llev); _MATRIX(LFE,FL) += llev; _MATRIX(LFE,FR) += llev; filter_lfe = true; } else if ((src_mask & FRONT) == FRONT) { - spa_log_debug(mix->log, "produce LFE from FC"); + spa_log_debug(mix->log, "produce LFE from FC (%f)", llev); _MATRIX(LFE,FC) += llev; filter_lfe = true; } else { @@ -421,32 +440,38 @@ static int make_matrix(struct channelmix *mix) } if (unassigned & SIDE) { if ((src_mask & REAR) == REAR) { - spa_log_debug(mix->log, "produce SIDE from REAR"); + spa_log_debug(mix->log, "produce SIDE from REAR (%f)", 1.0f); _MATRIX(SL,RL) += 1.0f; _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) { - spa_log_debug(mix->log, "produce SIDE from FC"); + spa_log_debug(mix->log, "produce SIDE from STEREO (%f)", slev); + _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 (%f)", clev); _MATRIX(SL,FC) += clev; _MATRIX(SR,FC) += clev; + } else { + spa_log_debug(mix->log, "won't produce SIDE"); } } if (unassigned & REAR) { if ((src_mask & SIDE) == SIDE) { - spa_log_debug(mix->log, "produce REAR from SIDE"); + spa_log_debug(mix->log, "produce REAR from SIDE (%f)", 1.0f); _MATRIX(RL,SL) += 1.0f; _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) { - spa_log_debug(mix->log, "produce REAR from FC"); + spa_log_debug(mix->log, "produce REAR from STEREO (%f)", slev); + _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 (%f)", clev); _MATRIX(RL,FC) += clev; _MATRIX(RR,FC) += clev; + } else { + spa_log_debug(mix->log, "won't produce SIDE"); } } @@ -458,6 +483,8 @@ done: for (jc = 0, j = 0; j < SPA_AUDIO_MAX_CHANNELS; j++) { if ((src_mask & (1UL << j)) == 0) continue; + if (ic >= dst_chan || jc >= src_chan) + continue; mix->matrix_orig[ic][jc++] = matrix[i][j]; sum += fabs(matrix[i][j]); } @@ -476,11 +503,10 @@ done: if (SPA_FLAG_IS_SET(mix->options, CHANNELMIX_OPTION_NORMALIZE) && maxsum > 1.0f) { spa_log_debug(mix->log, "normalize %f", maxsum); - for (i = 0; i < ic; i++) - for (j = 0; j < jc; j++) + for (i = 0; i < dst_chan; i++) + for (j = 0; j < src_chan; j++) mix->matrix_orig[i][j] /= maxsum; } - return 0; } @@ -514,6 +540,12 @@ static void impl_channelmix_set_volume(struct channelmix *mix, float volume, boo mix->matrix[i][j] = mix->matrix_orig[i][j] * volumes[i]; } } + } else if (n_channel_volumes == 0) { + for (i = 0; i < dst_chan; i++) { + for (j = 0; j < src_chan; j++) { + mix->matrix[i][j] = mix->matrix_orig[i][j] * vol; + } + } } SPA_FLAG_SET(mix->flags, CHANNELMIX_FLAG_ZERO); @@ -551,6 +583,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 +597,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..911fc0f0c2994ffb3deb4ed4ebb8acff305bb81b 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,12 @@ DEFINE_FUNCTION(f32_7p1_4, c); #if defined (HAVE_SSE) DEFINE_FUNCTION(copy, sse); -DEFINE_FUNCTION(f32_2_4, sse); +DEFINE_FUNCTION(f32_n_m, 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..92ecb5a0e116c340eae041d2d3dd5d87fdb29a81 100644 --- a/spa/plugins/audioconvert/fmt-ops-c.c +++ b/spa/plugins/audioconvert/fmt-ops-c.c @@ -33,1499 +33,401 @@ #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; +#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); - 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++; - } -} +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); -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; +MAKE_D_TO_I(f32, float, alaw, uint8_t, f32_to_alaw); +MAKE_D_TO_I(f32, float, ulaw, uint8_t, f32_to_ulaw); - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = s[i][j]; - } -} +MAKE_I_TO_I(f32, float, u16, uint16_t, F32_TO_U16); +MAKE_D_TO_I(f32, float, u16, uint16_t, F32_TO_U16); -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; +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); - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = s[i][j]; - } -} +MAKE_I_TO_I(f32, float, u32, uint32_t, F32_TO_U32); +MAKE_D_TO_I(f32, float, u32, uint32_t, F32_TO_U32); -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; +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); - 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; - } - } -} +MAKE_I_TO_I(f32, float, u24, uint24_t, F32_TO_U24); +MAKE_D_TO_I(f32, float, u24, uint24_t, F32_TO_U24); -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; +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); - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = s[i][j]; - } -} +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); -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; +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); - for (j = 0; j < n_samples; j++) { - for (i = 0; i < n_channels; i++) - *d++ = bswap_32(s[i][j]); - } -} +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); -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]; - } -} +static inline int32_t +lcnoise(uint32_t *state) +{ + *state = (*state * 96314165) + 907633515; + return (int32_t)(*state); +} + +void conv_noise_none_c(struct convert *conv, float *noise, uint32_t n_samples) +{ + memset(noise, 0, n_samples * sizeof(float)); +} + +void conv_noise_rect_c(struct convert *conv, float *noise, uint32_t n_samples) +{ + uint32_t n; + uint32_t *state = &conv->random[0]; + const float scale = conv->scale; + + for (n = 0; n < n_samples; n++) + noise[n] = lcnoise(state) * scale; +} + +void conv_noise_tri_c(struct convert *conv, float *noise, uint32_t n_samples) +{ + uint32_t n; + const float scale = conv->scale; + uint32_t *state = &conv->random[0]; + + for (n = 0; n < n_samples; n++) + noise[n] = (lcnoise(state) - lcnoise(state)) * scale; +} + +void conv_noise_tri_hf_c(struct convert *conv, float *noise, uint32_t n_samples) +{ + uint32_t n; + const float scale = conv->scale; + uint32_t *state = &conv->random[0]; + int32_t *prev = &conv->prev[0], old, new; + + old = *prev; + for (n = 0; n < n_samples; n++) { + new = lcnoise(state); + noise[n] = (new - old) * scale; + old = new; + } + *prev = old; +} + +void conv_noise_pattern_c(struct convert *conv, float *noise, uint32_t n_samples) +{ + uint32_t n; + const float scale = conv->scale; + int32_t *prev = &conv->prev[0], old; + + old = *prev; + for (n = 0; n < n_samples; n++) + noise[n] = scale * (1-((old++>>10)&1)); + *prev = old; +} + +#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; \ + convert_update_noise(conv, noise, 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; \ + convert_update_noise(conv, noise, 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; \ + float *noise = conv->noise; \ + const float *ns = conv->ns; \ + uint32_t n, n_ns = conv->n_ns; \ + convert_update_noise(conv, noise, 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; \ + float *noise = conv->noise; \ + const float *ns = conv->ns; \ + uint32_t n, n_ns = conv->n_ns; \ + convert_update_noise(conv, noise, 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..4e2fce72ffa30f4511890f04e6f50e8cfbc5cd29 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,138 @@ 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; \ +}) + +void conv_noise_rect_sse2(struct convert *conv, float *noise, uint32_t n_samples) +{ + uint32_t n; + const uint32_t *r = conv->random; + __m128 scale = _mm_set1_ps(conv->scale); + __m128i in[1]; + __m128 out[1]; + + for (n = 0; n < n_samples; n += 4) { + in[0] = _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]); + } +} + +void conv_noise_tri_sse2(struct convert *conv, float *noise, uint32_t n_samples) +{ + uint32_t n; + const uint32_t *r = conv->random; + __m128 scale = _mm_set1_ps(conv->scale); + __m128i in[1]; + __m128 out[1]; + + 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]); + } +} + +void conv_noise_tri_hf_sse2(struct convert *conv, float *noise, uint32_t n_samples) +{ + uint32_t n; + int32_t *p = conv->prev; + const uint32_t *r = conv->random; + __m128 scale = _mm_set1_ps(conv->scale); + __m128i in[1], old[1], new[1]; + __m128 out[1]; + + 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]); +} + +static void +conv_f32d_to_s32_1s_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, + float *noise, uint32_t n_channels, uint32_t n_samples) +{ + const float *s = src; + 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; + float *noise = conv->noise; + + convert_update_noise(conv, noise, 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], noise, 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 +763,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 +861,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 +942,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 +1029,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 +1050,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 +1060,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 +1069,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 +1100,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 +1110,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 +1127,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 +1143,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 +1154,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 +1175,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 +1194,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 +1207,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 +1233,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 +1264,127 @@ 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, + const float *noise, uint32_t n_channels, uint32_t n_samples) +{ + const float *s0 = src; + int16_t *d = dst; + 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; + float *noise = conv->noise; + + convert_update_noise(conv, noise, 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], noise, n_channels, chunk); + } + } +} + +static void +conv_f32_to_s16_1_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, + const float *noise, uint32_t n_samples) +{ + const float *s = src; + int16_t *d = dst; + 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; + float *noise = conv->noise; + + convert_update_noise(conv, noise, 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], noise, 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 +1394,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 +1405,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 +1427,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..a888f38c3c0fb75698143427ddc95c4a623daba6 100644 --- a/spa/plugins/audioconvert/fmt-ops.c +++ b/spa/plugins/audioconvert/fmt-ops.c @@ -32,6 +32,9 @@ #include "fmt-ops.h" +#define NOISE_SIZE (1<<10) +#define RANDOM_SIZE (16) + typedef void (*convert_func_t) (struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples); @@ -39,307 +42,528 @@ 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) - { 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_noise_sse2, SPA_CPU_FLAG_SSE2, CONV_NOISE), #endif - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S16P, 0, 0, conv_f32d_to_s16d_c }, + MAKE(F32P, S16P, 0, conv_f32d_to_s16d_noise_c, 0, CONV_NOISE), +#if defined (HAVE_SSE2) + MAKE(F32P, S16P, 0, conv_f32d_to_s16d_sse2, SPA_CPU_FLAG_SSE2), +#endif + 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), - { SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_S16_OE, 0, 0, conv_f32d_to_s16s_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_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, U32, 0, conv_f32_to_u32_c), + MAKE(F32P, U32, 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; - for (i = 0; i < SPA_N_ELEMENTS(conv_table); i++) { 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; } +typedef void (*noise_func_t) (struct convert *conv, float * noise, uint32_t n_samples); + +struct noise_info { + uint32_t method; + + noise_func_t noise; + const char *name; + + uint32_t cpu_flags; +}; + +#define MAKE(method,func,...) \ + { NOISE_METHOD_ ##method, func, #func , __VA_ARGS__ } + +static struct noise_info noise_table[] = +{ +#if defined (HAVE_SSE2) + MAKE(RECTANGULAR, conv_noise_rect_sse2, SPA_CPU_FLAG_SSE2), + MAKE(TRIANGULAR, conv_noise_tri_sse2, SPA_CPU_FLAG_SSE2), + MAKE(TRIANGULAR_HF, conv_noise_tri_hf_sse2, SPA_CPU_FLAG_SSE2), +#endif + MAKE(NONE, conv_noise_none_c), + MAKE(RECTANGULAR, conv_noise_rect_c), + MAKE(TRIANGULAR, conv_noise_tri_c), + MAKE(TRIANGULAR_HF, conv_noise_tri_hf_c), + MAKE(PATTERN, conv_noise_pattern_c), +}; +#undef MAKE + +static const struct noise_info *find_noise_info(uint32_t method, + uint32_t cpu_flags) +{ + size_t i; + for (i = 0; i < SPA_N_ELEMENTS(noise_table); i++) { + if (noise_table[i].method == method && + MATCH_CPU_FLAGS(noise_table[i].cpu_flags, cpu_flags)) + return &noise_table[i]; + } + return NULL; +} + static void impl_convert_free(struct convert *conv) { conv->process = NULL; + free(conv->data); + conv->data = 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; + const struct noise_info *ninfo; + uint32_t i, conv_flags, data_size[3]; + + 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; + ninfo = find_noise_info(conv->noise_method, conv->cpu_flags); + if (ninfo == NULL) + return -ENOTSUP; + + conv->noise_size = NOISE_SIZE; + + data_size[0] = SPA_ROUND_UP(conv->noise_size * sizeof(float), FMT_OPS_MAX_ALIGN); + data_size[1] = SPA_ROUND_UP(RANDOM_SIZE * sizeof(uint32_t), FMT_OPS_MAX_ALIGN); + data_size[2] = SPA_ROUND_UP(RANDOM_SIZE * sizeof(int32_t), FMT_OPS_MAX_ALIGN); + + conv->data = calloc(FMT_OPS_MAX_ALIGN + + data_size[0] + data_size[1] + data_size[2], 1); + if (conv->data == NULL) + return -errno; + + conv->noise = SPA_PTR_ALIGN(conv->data, FMT_OPS_MAX_ALIGN, float); + conv->random = SPA_PTROFF(conv->noise, data_size[0], uint32_t); + conv->prev = SPA_PTROFF(conv->random, data_size[1], int32_t); + + for (i = 0; i < RANDOM_SIZE; i++) + conv->random[i] = random(); + conv->is_passthrough = conv->src_fmt == conv->dst_fmt; conv->cpu_flags = info->cpu_flags; + conv->update_noise = ninfo->noise; 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..ee4f343fca19d760344ba0f8b05632d2600413ce 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,174 +33,276 @@ #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 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 MAX_NS 64 +#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; + int32_t *prev; +#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 (*update_noise) (struct convert *conv, float *noise, uint32_t n_samples); void (*process) (struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples); void (*free) (struct convert *conv); + + void *data; }; 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_update_noise(conv,...) (conv)->update_noise(conv, __VA_ARGS__) #define convert_process(conv,...) (conv)->process(conv, __VA_ARGS__) #define convert_free(conv) (conv)->free(conv) -#define DEFINE_FUNCTION(name,arch) \ -void conv_##name##_##arch(struct convert *conv, void * SPA_RESTRICT dst[], \ - const void * SPA_RESTRICT src[], uint32_t n_samples) \ +#define DEFINE_NOISE_FUNCTION(name,arch) \ +void conv_noise_##name##_##arch(struct convert *conv, float *noise, \ + uint32_t n_samples) -#define FMT_OPS_MAX_ALIGN 32 +DEFINE_NOISE_FUNCTION(none, c); +DEFINE_NOISE_FUNCTION(rect, c); +DEFINE_NOISE_FUNCTION(tri, c); +DEFINE_NOISE_FUNCTION(tri_hf, c); +DEFINE_NOISE_FUNCTION(pattern, c); +#if defined(HAVE_SSE2) +DEFINE_NOISE_FUNCTION(rect, sse2); +DEFINE_NOISE_FUNCTION(tri, sse2); +DEFINE_NOISE_FUNCTION(tri_hf, sse2); +#endif + +#undef DEFINE_NOISE_FUNCTION + +#define DEFINE_FUNCTION(name,arch) \ +void conv_##name##_##arch(struct convert *conv, void * SPA_RESTRICT dst[], \ + const void * SPA_RESTRICT src[], uint32_t n_samples) DEFINE_FUNCTION(copy8d, c); DEFINE_FUNCTION(copy8, c); @@ -256,62 +358,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 +449,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..f08527a5a60581b9dc3aceaabedaab681b31328e 100644 --- a/spa/plugins/audioconvert/meson.build +++ b/spa/plugins/audioconvert/meson.build @@ -1,22 +1,33 @@ -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', + 'peaks-ops-c.c', + 'resample-native-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', - 'resample-peaks-sse.c', 'volume-ops-sse.c', + 'peaks-ops-sse.c', 'channelmix-ops-sse.c' ], - c_args : [sse_args, '-O3', '-DHAVE_SSE'], + c_args : [sse_args, '-Ofast', '-DHAVE_SSE'], dependencies : [ spa_dep ], install : false ) @@ -89,15 +100,11 @@ endif audioconvert_lib = static_library('audioconvert', ['fmt-ops.c', - 'biquad.c', - 'crossover.c', 'channelmix-ops.c', - 'channelmix-ops-c.c', + 'peaks-ops.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], @@ -126,6 +133,7 @@ test_apps = [ 'test-audioconvert', 'test-channelmix', 'test-fmt-ops', + 'test-peaks', 'test-resample', ] diff --git a/spa/plugins/audioconvert/peaks-ops-c.c b/spa/plugins/audioconvert/peaks-ops-c.c new file mode 100644 index 0000000000000000000000000000000000000000..45ab1dc694f15d0020044598a09598d81d0c8c92 --- /dev/null +++ b/spa/plugins/audioconvert/peaks-ops-c.c @@ -0,0 +1,50 @@ +/* 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 <math.h> + +#include "peaks-ops.h" + +void peaks_min_max_c(struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float *min, float *max) +{ + uint32_t n; + float t, mi = *min, ma = *max; + for (n = 0; n < n_samples; n++) { + t = src[n]; + mi = fminf(mi, t); + ma = fmaxf(ma, t); + } + *min = mi; + *max = ma; +} + +float peaks_abs_max_c(struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float max) +{ + uint32_t n; + for (n = 0; n < n_samples; n++) + max = fmaxf(fabsf(src[n]), max); + return max; +} diff --git a/spa/plugins/audioconvert/peaks-ops-sse.c b/spa/plugins/audioconvert/peaks-ops-sse.c new file mode 100644 index 0000000000000000000000000000000000000000..7ceb2a8c6e8c33a2e2884cea8245e24b8e574b6a --- /dev/null +++ b/spa/plugins/audioconvert/peaks-ops-sse.c @@ -0,0 +1,122 @@ +/* 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 <math.h> + +#include <xmmintrin.h> + +#include "peaks-ops.h" + +static inline float hmin_ps(__m128 val) +{ + __m128 t = _mm_movehl_ps(val, val); + t = _mm_min_ps(t, val); + val = _mm_shuffle_ps(t, t, 0x55); + val = _mm_min_ss(t, val); + return _mm_cvtss_f32(val); +} + +static inline float hmax_ps(__m128 val) +{ + __m128 t = _mm_movehl_ps(val, val); + t = _mm_max_ps(t, val); + val = _mm_shuffle_ps(t, t, 0x55); + val = _mm_max_ss(t, val); + return _mm_cvtss_f32(val); +} + +void peaks_min_max_sse(struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float *min, float *max) +{ + uint32_t n; + __m128 in; + __m128 mi = _mm_set1_ps(*min); + __m128 ma = _mm_set1_ps(*max); + + for (n = 0; n < n_samples; n++) { + if (SPA_IS_ALIGNED(&src[n], 16)) + break; + in = _mm_set1_ps(src[n]); + mi = _mm_min_ps(mi, in); + ma = _mm_max_ps(ma, in); + } + for (; n + 15 < n_samples; n += 16) { + in = _mm_load_ps(&src[n + 0]); + mi = _mm_min_ps(mi, in); + ma = _mm_max_ps(ma, in); + in = _mm_load_ps(&src[n + 4]); + mi = _mm_min_ps(mi, in); + ma = _mm_max_ps(ma, in); + in = _mm_load_ps(&src[n + 8]); + mi = _mm_min_ps(mi, in); + ma = _mm_max_ps(ma, in); + in = _mm_load_ps(&src[n + 12]); + mi = _mm_min_ps(mi, in); + ma = _mm_max_ps(ma, in); + } + for (; n < n_samples; n++) { + in = _mm_set1_ps(src[n]); + mi = _mm_min_ps(mi, in); + ma = _mm_max_ps(ma, in); + } + *min = hmin_ps(mi); + *max = hmax_ps(ma); +} + +float peaks_abs_max_sse(struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float max) +{ + uint32_t n; + __m128 in; + __m128 ma = _mm_set1_ps(max); + const __m128 mask = _mm_set1_ps(-0.0f); + + for (n = 0; n < n_samples; n++) { + if (SPA_IS_ALIGNED(&src[n], 16)) + break; + in = _mm_set1_ps(src[n]); + in = _mm_andnot_ps(mask, in); + ma = _mm_max_ps(ma, in); + } + for (; n + 15 < n_samples; n += 16) { + in = _mm_load_ps(&src[n + 0]); + in = _mm_andnot_ps(mask, in); + ma = _mm_max_ps(ma, in); + in = _mm_load_ps(&src[n + 4]); + in = _mm_andnot_ps(mask, in); + ma = _mm_max_ps(ma, in); + in = _mm_load_ps(&src[n + 8]); + in = _mm_andnot_ps(mask, in); + ma = _mm_max_ps(ma, in); + in = _mm_load_ps(&src[n + 12]); + in = _mm_andnot_ps(mask, in); + ma = _mm_max_ps(ma, in); + } + for (; n < n_samples; n++) { + in = _mm_set1_ps(src[n]); + in = _mm_andnot_ps(mask, in); + ma = _mm_max_ps(ma, in); + } + return hmax_ps(ma); +} diff --git a/spa/plugins/audioconvert/peaks-ops.c b/spa/plugins/audioconvert/peaks-ops.c new file mode 100644 index 0000000000000000000000000000000000000000..154f17de4594eb250823e6324e3d8356c275b57a --- /dev/null +++ b/spa/plugins/audioconvert/peaks-ops.c @@ -0,0 +1,91 @@ +/* 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 <string.h> +#include <stdio.h> +#include <math.h> +#include <errno.h> + +#include <spa/support/cpu.h> +#include <spa/support/log.h> +#include <spa/utils/defs.h> + +#include "peaks-ops.h" + +typedef void (*peaks_min_max_func_t) (struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float *min, float *max); +typedef float (*peaks_abs_max_func_t) (struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float max); + +#define MAKE(min_max,abs_max,...) \ + { min_max, abs_max, #min_max , __VA_ARGS__ } + +static const struct peaks_info { + peaks_min_max_func_t min_max; + peaks_abs_max_func_t abs_max; + const char *name; + uint32_t cpu_flags; +} peaks_table[] = +{ +#if defined (HAVE_SSE) + MAKE(peaks_min_max_sse, peaks_abs_max_sse, SPA_CPU_FLAG_SSE), +#endif + MAKE(peaks_min_max_c, peaks_abs_max_c), +}; +#undef MAKE + +#define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) + +static const struct peaks_info *find_peaks_info(uint32_t cpu_flags) +{ + size_t i; + for (i = 0; i < SPA_N_ELEMENTS(peaks_table); i++) { + if (!MATCH_CPU_FLAGS(peaks_table[i].cpu_flags, cpu_flags)) + continue; + return &peaks_table[i]; + } + return NULL; +} + +static void impl_peaks_free(struct peaks *peaks) +{ + peaks->min_max = NULL; + peaks->abs_max = NULL; +} + +int peaks_init(struct peaks *peaks) +{ + const struct peaks_info *info; + + info = find_peaks_info(peaks->cpu_flags); + if (info == NULL) + return -ENOTSUP; + + peaks->cpu_flags = info->cpu_flags; + peaks->func_name = info->name; + peaks->free = impl_peaks_free; + peaks->min_max = info->min_max; + peaks->abs_max = info->abs_max; + return 0; +} diff --git a/spa/plugins/audioconvert/peaks-ops.h b/spa/plugins/audioconvert/peaks-ops.h new file mode 100644 index 0000000000000000000000000000000000000000..29da794100d6b9851b81bda2322414e62b3865bc --- /dev/null +++ b/spa/plugins/audioconvert/peaks-ops.h @@ -0,0 +1,72 @@ +/* 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 <string.h> +#include <stdio.h> + +#include <spa/utils/defs.h> + +struct peaks { + uint32_t cpu_flags; + const char *func_name; + + struct spa_log *log; + + uint32_t flags; + + void (*min_max) (struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float *min, float *max); + float (*abs_max) (struct peaks *peaks, const float * SPA_RESTRICT src, + uint32_t n_samples, float max); + + void (*free) (struct peaks *peaks); +}; + +int peaks_init(struct peaks *peaks); + +#define peaks_min_max(peaks,...) (peaks)->min_max(peaks, __VA_ARGS__) +#define peaks_abs_max(peaks,...) (peaks)->abs_max(peaks, __VA_ARGS__) +#define peaks_free(peaks) (peaks)->free(peaks) + +#define DEFINE_MIN_MAX_FUNCTION(arch) \ +void peaks_min_max_##arch(struct peaks *peaks, \ + const float * SPA_RESTRICT src, \ + uint32_t n_samples, float *min, float *max); + +#define DEFINE_ABS_MAX_FUNCTION(arch) \ +float peaks_abs_max_##arch(struct peaks *peaks, \ + const float * SPA_RESTRICT src, \ + uint32_t n_samples, float max); + +#define PEAKS_OPS_MAX_ALIGN 16 + +DEFINE_MIN_MAX_FUNCTION(c); +DEFINE_ABS_MAX_FUNCTION(c); + +#if defined (HAVE_SSE) +DEFINE_MIN_MAX_FUNCTION(sse); +DEFINE_ABS_MAX_FUNCTION(sse); +#endif + +#undef DEFINE_FUNCTION 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-avx.c b/spa/plugins/audioconvert/resample-native-avx.c index b23c0b729dcb4e63c94046149d2863f87ce24405..136d6cb2df1cf966219fbda4808f62be03fc7c75 100644 --- a/spa/plugins/audioconvert/resample-native-avx.c +++ b/spa/plugins/audioconvert/resample-native-avx.c @@ -27,7 +27,7 @@ #include <assert.h> #include <immintrin.h> -static void inner_product_avx(float *d, const float * SPA_RESTRICT s, +static inline void inner_product_avx(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT taps, uint32_t n_taps) { __m256 sy[2] = { _mm256_setzero_ps(), _mm256_setzero_ps() }, ty; @@ -56,7 +56,7 @@ static void inner_product_avx(float *d, const float * SPA_RESTRICT s, _mm_store_ss(d, sx[0]); } -static void inner_product_ip_avx(float *d, const float * SPA_RESTRICT s, +static inline void inner_product_ip_avx(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, uint32_t n_taps) { diff --git a/spa/plugins/audioconvert/resample-native-c.c b/spa/plugins/audioconvert/resample-native-c.c new file mode 100644 index 0000000000000000000000000000000000000000..ce6c57d923ade391af554fa21fef0b2a36ad3ec9 --- /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 inline void inner_product_c(float *d, const float * SPA_RESTRICT s, + const float * SPA_RESTRICT taps, uint32_t n_taps) +{ + float sum = 0.0f; +#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 inline void inner_product_ip_c(float *d, const float * SPA_RESTRICT s, + const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, + uint32_t n_taps) +{ + 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-neon.c b/spa/plugins/audioconvert/resample-native-neon.c index afe68914ae6f882a9b825f4a5b4e8252b0a4950b..079152afdc0c478f1f054073e20723a37e7afe22 100644 --- a/spa/plugins/audioconvert/resample-native-neon.c +++ b/spa/plugins/audioconvert/resample-native-neon.c @@ -26,7 +26,7 @@ #include <arm_neon.h> -static void inner_product_neon(float *d, const float * SPA_RESTRICT s, +static inline void inner_product_neon(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT taps, uint32_t n_taps) { unsigned int remainder = n_taps % 16; @@ -137,7 +137,7 @@ static void inner_product_neon(float *d, const float * SPA_RESTRICT s, #endif } -static void inner_product_ip_neon(float *d, const float * SPA_RESTRICT s, +static inline void inner_product_ip_neon(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, uint32_t n_taps) { diff --git a/spa/plugins/audioconvert/resample-native-sse.c b/spa/plugins/audioconvert/resample-native-sse.c index d0ebe39ecabd02fb905b42249262cb576fb46e4f..fcdb32c0866dfa9aab0cb04afc6d102c31d200ce 100644 --- a/spa/plugins/audioconvert/resample-native-sse.c +++ b/spa/plugins/audioconvert/resample-native-sse.c @@ -26,7 +26,7 @@ #include <xmmintrin.h> -static void inner_product_sse(float *d, const float * SPA_RESTRICT s, +static inline void inner_product_sse(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT taps, uint32_t n_taps) { __m128 sum = _mm_setzero_ps(); @@ -68,7 +68,7 @@ static void inner_product_sse(float *d, const float * SPA_RESTRICT s, _mm_store_ss(d, sum); } -static void inner_product_ip_sse(float *d, const float * SPA_RESTRICT s, +static inline void inner_product_ip_sse(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, uint32_t n_taps) { diff --git a/spa/plugins/audioconvert/resample-native-ssse3.c b/spa/plugins/audioconvert/resample-native-ssse3.c index c39bc610aa8df5d0bef2e0f5fb828dd17f14a36c..ac3675f0396af77c21573f2c7d68edfc3374b046 100644 --- a/spa/plugins/audioconvert/resample-native-ssse3.c +++ b/spa/plugins/audioconvert/resample-native-ssse3.c @@ -26,7 +26,7 @@ #include <tmmintrin.h> -static void inner_product_ssse3(float *d, const float * SPA_RESTRICT s, +static inline void inner_product_ssse3(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT taps, uint32_t n_taps) { __m128 sum = _mm_setzero_ps(); @@ -97,7 +97,7 @@ static void inner_product_ssse3(float *d, const float * SPA_RESTRICT s, _mm_store_ss(d, sum); } -static void inner_product_ip_ssse3(float *d, const float * SPA_RESTRICT s, +static inline void inner_product_ip_ssse3(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, uint32_t n_taps) { diff --git a/spa/plugins/audioconvert/resample-native.c b/spa/plugins/audioconvert/resample-native.c index f522d67e2cf85b607a829db749b909b76faeae58..f6a72703077838db75fccceba728e676e823a0b8 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); @@ -322,7 +304,10 @@ static void impl_native_reset (struct resample *r) if (d == NULL) return; memset(d->hist_mem, 0, r->channels * sizeof(float) * d->n_taps * 2); - d->hist = (d->n_taps / 2) - 1; + if (r->options & RESAMPLE_OPTION_PREFILL) + d->hist = d->n_taps - 1; + else + d->hist = (d->n_taps / 2) - 1; d->phase = 0; } @@ -340,7 +325,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 +333,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 +382,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-sse.c b/spa/plugins/audioconvert/resample-peaks-sse.c deleted file mode 100644 index 13886efdb6772059de34760f32e96e68e3c6b988..0000000000000000000000000000000000000000 --- a/spa/plugins/audioconvert/resample-peaks-sse.c +++ /dev/null @@ -1,96 +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 <math.h> - -#include <xmmintrin.h> - -#include "resample-peaks-impl.h" - -static inline float hmax_ps(__m128 val) -{ - __m128 t = _mm_movehl_ps(val, val); - t = _mm_max_ps(t, val); - val = _mm_shuffle_ps(val, t, 0x55); - val = _mm_max_ss(t, val); - return _mm_cvtss_f32(val); -} - -void resample_peaks_process_sse(struct resample *r, - const void * SPA_RESTRICT src[], uint32_t *in_len, - void * SPA_RESTRICT dst[], uint32_t *out_len) -{ - struct peaks_data *pd = r->data; - uint32_t c, i, o, end, chunk, unrolled, i_count, o_count; - __m128 in, max, mask = _mm_andnot_ps(_mm_set_ps1(-0.0f), - _mm_cmpeq_ps(_mm_setzero_ps(), _mm_setzero_ps())); - - if (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; - - max = _mm_set1_ps(m); - - while (i < *in_len && o < *out_len) { - end = ((uint64_t) (o_count + 1) * r->i_rate) / r->o_rate; - end = end > i_count ? end - i_count : 0; - chunk = SPA_MIN(end, *in_len); - - unrolled = chunk - ((chunk - i) & 3); - - for (; i < unrolled; i+=4) { - in = _mm_loadu_ps(&s[i]); - in = _mm_and_ps(mask, in); - max = _mm_max_ps(in, max); - } - for (; i < chunk; i++) - m = SPA_MAX(fabsf(s[i]), m); - - if (i == end) { - d[o++] = SPA_MAX(hmax_ps(max), m); - m = 0.0f; - max = _mm_set1_ps(m); - o_count++; - } - } - pd->max_f[c] = SPA_MAX(hmax_ps(max), m); - } - - *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.c b/spa/plugins/audioconvert/resample-peaks.c index 9e4c4223dc1546a61119a1cd6bf9c3492f04c93e..c151d60f32a9699deeea4db21a0fc1505b6a1151 100644 --- a/spa/plugins/audioconvert/resample-peaks.c +++ b/spa/plugins/audioconvert/resample-peaks.c @@ -27,14 +27,22 @@ #include <spa/param/audio/format.h> -#include "resample-peaks-impl.h" +#include "peaks-ops.h" +#include "resample.h" + +struct peaks_data { + uint32_t o_count; + uint32_t i_count; + struct peaks peaks; + float max_f[]; +}; -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) +static void resample_peaks_process(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; + uint32_t c, i, o, end, chunk, i_count, o_count; if (SPA_UNLIKELY(r->channels == 0)) return; @@ -48,12 +56,14 @@ static void resample_peaks_process_c(struct resample *r, o = i = 0; while (i < *in_len && o < *out_len) { - end = ((uint64_t) (o_count + 1) * r->i_rate) / r->o_rate; + 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); + m = peaks_abs_max(&pd->peaks, &s[i], chunk - i, m); + + i += chunk; if (i == end) { d[o++] = m; @@ -63,7 +73,6 @@ static void resample_peaks_process_c(struct resample *r, } pd->max_f[c] = m; } - *out_len = o; *in_len = i; pd->o_count = o_count; @@ -75,38 +84,13 @@ static void resample_peaks_process_c(struct resample *r, } } -struct resample_info { - uint32_t format; - uint32_t cpu_flags; - void (*process) (struct resample *r, - const void * SPA_RESTRICT src[], uint32_t *in_len, - void * SPA_RESTRICT dst[], uint32_t *out_len); -}; - -static struct resample_info resample_table[] = -{ -#if defined (HAVE_SSE) - { SPA_AUDIO_FORMAT_F32, SPA_CPU_FLAG_SSE, resample_peaks_process_sse, }, -#endif - { SPA_AUDIO_FORMAT_F32, 0, resample_peaks_process_c, }, -}; - -#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) -{ - size_t i; - for (i = 0; i < SPA_N_ELEMENTS(resample_table); i++) { - if (resample_table[i].format == format && - MATCH_CPU_FLAGS(resample_table[i].cpu_flags, cpu_flags)) { - return &resample_table[i]; - } - } - return NULL; -} - static void impl_peaks_free(struct resample *r) { - free(r->data); + struct peaks_data *d = r->data; + if (d != NULL) { + peaks_free(&d->peaks); + free(d); + } r->data = NULL; } @@ -133,27 +117,32 @@ static void impl_peaks_reset (struct resample *r) int resample_peaks_init(struct resample *r) { struct peaks_data *d; - const struct resample_info *info; + int res; r->free = impl_peaks_free; r->update_rate = impl_peaks_update_rate; - if ((info = find_resample_info(SPA_AUDIO_FORMAT_F32, r->cpu_flags)) == NULL) - return -ENOTSUP; + d = calloc(1, sizeof(struct peaks_data) + sizeof(float) * r->channels); + if (d == NULL) + return -errno; + + d->peaks.log = r->log; + d->peaks.cpu_flags = r->cpu_flags; + if ((res = peaks_init(&d->peaks)) < 0) { + free(d); + return res; + } - r->process = info->process; + r->data = d; + r->process = resample_peaks_process; r->reset = impl_peaks_reset; r->delay = impl_peaks_delay; r->in_len = impl_peaks_in_len; - d = r->data = calloc(1, sizeof(struct peaks_data) + sizeof(float) * r->channels); - if (r->data == NULL) - return -errno; - spa_log_debug(r->log, "peaks %p: in:%d out:%d features:%08x:%08x", r, - r->i_rate, r->o_rate, r->cpu_flags, info->cpu_flags); + r->i_rate, r->o_rate, r->cpu_flags, d->peaks.cpu_flags); - r->cpu_flags = info->cpu_flags; + r->cpu_flags = d->peaks.cpu_flags; d->i_count = d->o_count = 0; return 0; } 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..b1c89d593bf929f9ea8050cb44e0c1c9d4f760cc 100644 --- a/spa/plugins/audioconvert/resample.h +++ b/spa/plugins/audioconvert/resample.h @@ -31,11 +31,15 @@ #define RESAMPLE_DEFAULT_QUALITY 4 struct resample { + struct spa_log *log; +#define RESAMPLE_OPTION_PREFILL (1<<0) + uint32_t options; 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-channelmix.c b/spa/plugins/audioconvert/test-channelmix.c index 705ddf4de34372443c9ae2250351fbafba544896..81abc86d4931083a5c0987db37a5a317b2ab05fd 100644 --- a/spa/plugins/audioconvert/test-channelmix.c +++ b/spa/plugins/audioconvert/test-channelmix.c @@ -22,6 +22,8 @@ * DEALINGS IN THE SOFTWARE. */ +#include "config.h" + #include <string.h> #include <stdio.h> #include <stdlib.h> @@ -32,20 +34,26 @@ #include <spa/support/log-impl.h> #include <spa/debug/mem.h> +static uint32_t cpu_flags; + SPA_LOG_IMPL(logger); #define MATRIX(...) (float[]) { __VA_ARGS__ } +#include "test-helper.h" #include "channelmix-ops.c" + +#define CLOSE_ENOUGH(a,b) (fabs((a)-(b)) < 0.000001f) + static void dump_matrix(struct channelmix *mix, float *coeff) { uint32_t i, j; for (i = 0; i < mix->dst_chan; i++) { for (j = 0; j < mix->src_chan; j++) { - float v = mix->matrix_orig[i][j]; + float v = mix->matrix[i][j]; spa_log_debug(mix->log, "%d %d: %f <-> %f", i, j, v, *coeff); - spa_assert_se(fabs(v - *coeff) < 0.000001); + spa_assert_se(CLOSE_ENOUGH(v, *coeff)); coeff++; } } @@ -65,7 +73,8 @@ static void test_mix(uint32_t src_chan, uint32_t src_mask, uint32_t dst_chan, ui mix.dst_mask = dst_mask; mix.log = &logger.log; - channelmix_init(&mix); + spa_assert_se(channelmix_init(&mix) == 0); + channelmix_set_volume(&mix, 1.0f, false, 0, NULL); dump_matrix(&mix, coeff); } @@ -220,10 +229,87 @@ static void test_7p1_N(void) 0.0, 1.0, 0.707107, 0.0, 0.0, 0.707107, 0.0, 0.707107)); } +static void run_n_m_impl(struct channelmix *mix, const void **src, uint32_t n_samples) +{ + uint32_t dst_chan = mix->dst_chan, i, j; + float dst_c_data[dst_chan][n_samples]; + float dst_x_data[dst_chan][n_samples]; + void *dst_c[dst_chan], *dst_x[dst_chan]; + + for (i = 0; i < dst_chan; i++) { + dst_c[i] = dst_c_data[i]; + dst_x[i] = dst_x_data[i]; + } + + channelmix_f32_n_m_c(mix, dst_c, src, n_samples); + +#if defined(HAVE_SSE) + if (cpu_flags & SPA_CPU_FLAG_SSE) { + channelmix_f32_n_m_sse(mix, dst_x, src, n_samples); + for (i = 0; i < mix->dst_chan; i++) { + for (j = 0; j < n_samples; j++) { + spa_assert_se(CLOSE_ENOUGH(dst_c_data[i][j], dst_x_data[i][j])); + } + } + } +#endif +} + +static void test_n_m_impl(void) +{ + struct channelmix mix; + unsigned int i, j; +#define N_SAMPLES 251 + float src_data[16][N_SAMPLES], *src[16]; + + spa_log_debug(&logger.log, "start"); + + for (i = 0; i < 16; i++) { + for (j = 0; j < N_SAMPLES; j++) + src_data[i][j] = (drand48() - 0.5f) * 2.5f; + src[i] = src_data[i]; + } + + spa_zero(mix); + mix.src_chan = 16; + mix.dst_chan = 12; + mix.log = &logger.log; + mix.cpu_flags = cpu_flags; + spa_assert_se(channelmix_init(&mix) == 0); + channelmix_set_volume(&mix, 1.0f, false, 0, NULL); + + /* identity matrix */ + run_n_m_impl(&mix, (const void**)src, N_SAMPLES); + + /* some zero destination */ + mix.matrix_orig[2][2] = 0.0f; + mix.matrix_orig[7][7] = 0.0f; + channelmix_set_volume(&mix, 1.0f, false, 0, NULL); + run_n_m_impl(&mix, (const void**)src, N_SAMPLES); + + /* random matrix */ + for (i = 0; i < mix.dst_chan; i++) { + for (j = 0; j < mix.src_chan; j++) { + mix.matrix_orig[i][j] = drand48() - 0.5f; + } + } + channelmix_set_volume(&mix, 1.0f, false, 0, NULL); + + run_n_m_impl(&mix, (const void**)src, N_SAMPLES); +} + int main(int argc, char *argv[]) { + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + srand48(SPA_TIMESPEC_TO_NSEC(&ts)); + logger.log.level = SPA_LOG_LEVEL_TRACE; + cpu_flags = get_cpu_flags(); + printf("got CPU flags %d\n", cpu_flags); + test_1_N_MONO(); test_1_N_FC(); test_N_1(); @@ -232,5 +318,7 @@ int main(int argc, char *argv[]) test_5p1_N(); test_7p1_N(); + test_n_m_impl(); + 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-peaks.c b/spa/plugins/audioconvert/test-peaks.c new file mode 100644 index 0000000000000000000000000000000000000000..3f7d093ff908ad4132ddd34338c1015427ac9790 --- /dev/null +++ b/spa/plugins/audioconvert/test-peaks.c @@ -0,0 +1,128 @@ +/* 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/support/log-impl.h> +#include <spa/debug/mem.h> + +SPA_LOG_IMPL(logger); + +static uint32_t cpu_flags; + +#include "test-helper.h" + +#include "peaks-ops.c" + +static void test_impl(void) +{ + struct peaks peaks; + unsigned int i; + float vals[1038]; + float min[2] = { 0.0f, 0.0f }, max[2] = { 0.0f, 0.0f }, absmax[2] = { 0.0f, 0.0f }; + + for (i = 0; i < SPA_N_ELEMENTS(vals); i++) + vals[i] = (drand48() - 0.5f) * 2.5f; + + peaks_min_max_c(&peaks, &vals[1], SPA_N_ELEMENTS(vals) - 1, &min[0], &max[0]); + printf("c peaks min:%f max:%f\n", min[0], max[0]); + + absmax[0] = peaks_abs_max_c(&peaks, &vals[1], SPA_N_ELEMENTS(vals) - 1, 0.0f); + printf("c peaks abs-max:%f\n", absmax[0]); + +#if defined(HAVE_SSE) + if (cpu_flags & SPA_CPU_FLAG_SSE) { + peaks_min_max_sse(&peaks, &vals[1], SPA_N_ELEMENTS(vals) - 1, &min[1], &max[1]); + printf("sse peaks min:%f max:%f\n", min[1], max[1]); + + absmax[1] = peaks_abs_max_sse(&peaks, &vals[1], SPA_N_ELEMENTS(vals) - 1, 0.0f); + printf("sse peaks abs-max:%f\n", absmax[1]); + + spa_assert(min[0] == min[1]); + spa_assert(max[0] == max[1]); + spa_assert(absmax[0] == absmax[1]); + } +#endif + +} + +static void test_min_max(void) +{ + struct peaks peaks; + const float vals[] = { 0.0f, 0.5f, -0.5f, 0.0f, 0.6f, -0.8f, -0.5f, 0.0f }; + float min = 0.0f, max = 0.0f; + + spa_zero(peaks); + peaks.log = &logger.log; + peaks.cpu_flags = cpu_flags; + peaks_init(&peaks); + + peaks_min_max(&peaks, vals, SPA_N_ELEMENTS(vals), &min, &max); + + spa_assert(min == -0.8f); + spa_assert(max == 0.6f); +} + +static void test_abs_max(void) +{ + struct peaks peaks; + const float vals[] = { 0.0f, 0.5f, -0.5f, 0.0f, 0.6f, -0.8f, -0.5f, 0.0f }; + float max = 0.0f; + + spa_zero(peaks); + peaks.log = &logger.log; + peaks.cpu_flags = cpu_flags; + peaks_init(&peaks); + + max = peaks_abs_max(&peaks, vals, SPA_N_ELEMENTS(vals), max); + + spa_assert(max == 0.8f); +} + +int main(int argc, char *argv[]) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + srand48(SPA_TIMESPEC_TO_NSEC(&ts)); + + logger.log.level = SPA_LOG_LEVEL_TRACE; + + cpu_flags = get_cpu_flags(); + printf("got CPU flags %d\n", cpu_flags); + + test_impl(); + + test_min_max(); + test_abs_max(); + + 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..6aba1a0120361f7713f5d439bcba5b9f9a5735e0 100644 --- a/spa/plugins/audiomixer/audiomixer.c +++ b/spa/plugins/audiomixer/audiomixer.c @@ -45,6 +45,9 @@ #define SPA_LOG_TOPIC_DEFAULT log_topic static struct spa_log_topic *log_topic = &SPA_LOG_TOPIC(0, "spa.audiomixer"); +#define DEFAULT_RATE 48000 +#define DEFAULT_CHANNELS 2 + #define MAX_BUFFERS 64 #define MAX_PORTS 128 #define MAX_CHANNELS 64 @@ -353,8 +356,10 @@ static int port_enum_formats(void *object, SPA_AUDIO_FORMAT_U24_32, SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_F64), - SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(44100, 1, INT32_MAX), - SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(2, 1, INT32_MAX)); + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int( + DEFAULT_RATE, 1, INT32_MAX), + SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int( + DEFAULT_CHANNELS, 1, INT32_MAX)); } break; default: @@ -730,8 +735,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 +760,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 +777,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]; + + offs = SPA_MIN(bd->chunk->offset, bd->maxsize); + size = SPA_MIN(bd->maxsize - offs, bd->chunk->size); + maxsize = SPA_MIN(size, maxsize); - 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); + 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); - datas[n_buffers] = inb->buffer->datas[0].data; - 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; } @@ -788,8 +802,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 +812,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..ff58e6a2aa7225617349eb79ef65e026f1a08e9f 100644 --- a/spa/plugins/audiotestsrc/audiotestsrc.c +++ b/spa/plugins/audiotestsrc/audiotestsrc.c @@ -55,6 +55,9 @@ enum wave_type { WAVE_SQUARE, }; +#define DEFAULT_RATE 48000 +#define DEFAULT_CHANNELS 2 + #define DEFAULT_LIVE true #define DEFAULT_WAVE WAVE_SINE #define DEFAULT_FREQ 440.0 @@ -583,8 +586,10 @@ port_enum_formats(struct impl *this, SPA_AUDIO_FORMAT_S32, SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_F64), - SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(44100, 1, INT32_MAX), - SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(2, 1, INT32_MAX)); + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int( + DEFAULT_RATE, 1, INT32_MAX), + SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int( + DEFAULT_CHANNELS, 1, INT32_MAX)); break; default: return 0; @@ -931,7 +936,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..0adc7e36617fe90c3da04321b44cf100b22a749a --- /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, MSG_NOSIGNAL); + 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/audioconvert/resample-peaks-impl.h b/spa/plugins/avb/avb.h similarity index 75% rename from spa/plugins/audioconvert/resample-peaks-impl.h rename to spa/plugins/avb/avb.h index d8b28fb2bab9dc9628f08b831620543845a3fc30..a99a0fed4e48e4ef0993083adf6261682dc5472c 100644 --- a/spa/plugins/audioconvert/resample-peaks-impl.h +++ b/spa/plugins/avb/avb.h @@ -1,6 +1,6 @@ -/* Spa +/* Spa AVB * - * Copyright © 2020 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"), @@ -22,20 +22,18 @@ * DEALINGS IN THE SOFTWARE. */ -#include <math.h> +#ifndef SPA_AVB_H +#define SPA_AVB_H -#include <spa/utils/defs.h> +#include <spa/support/log.h> -#include "resample.h" +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT avb_log_topic +extern struct spa_log_topic *avb_log_topic; -struct peaks_data { - uint32_t o_count; - uint32_t i_count; - float max_f[]; -}; +static inline void avb_log_topic_init(struct spa_log *log) +{ + spa_log_topic_init(log, avb_log_topic); +} -#if defined (HAVE_SSE) -void resample_peaks_process_sse(struct resample *r, - const void * SPA_RESTRICT src[], uint32_t *in_len, - void * SPA_RESTRICT dst[], uint32_t *out_len); -#endif +#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..a7aefc1c615729121d4369473e5dcbb08beba2a5 --- /dev/null +++ b/spa/plugins/bluez5/README-OPUS-A2DP.md @@ -0,0 +1,335 @@ +--- +title: OPUS-A2DP-0.5 specification +author: Pauli Virtanen <pav@iki.fi> +date: Jun 4, 2022 +--- + +# OPUS-A2DP-0.5 specification + +In this file, a way to use Opus as an A2DP vendor codec is specified. + +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_]] + +# Media Codec Capabilities + +The Media Codec Specific Information Elements ([AVDTP v1.3], §8.21.5) +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 | + +All integer fields and multi-byte bitfields are laid out in **little +endian** order. All integer fields are unsigned. + +Each entry may have different meaning when present as a capability. +Below, we indicate this by abbreviations CAP for capability and SEL +for the value selected by SRC. + +Bits in fields marked RFA (Reserved For Additions) shall be set to +zero. + +> **Note** +> +> See `a2dp-codec-caps.h` for definition as C structs. + +## Vendor ID Part + +The fixed value + +| Octet | Bits | Meaning | +|-------|------|-------------------------------| +| 0-3 | 0-7 | A2DP Vendor ID (0x05F1) | +| 4-5 | 0-7 | A2DP Vendor Codec ID (0x1005) | + +> **Note** +> +> The Vendor ID is that of the Linux Foundation, and we are using it +> here unofficially. + +## Channel Configuration + +The channel configuration consists of the channel count, and the count +of coupled streams. The latter indicates which channels are encoded as +left/right pairs, as defined in Sec. 5.1.1 of Opus Ogg Encapsulation [RFC7845]. + +| Octet | Bits | Meaning | +|-------|------|------------------------------------------------------------| +| 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)`. + +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)`. + +> **Note** +> +> The prescription here is identical to [RFC7845] with channel mapping +> `mapping[j] = j`. We do not want to include the mapping table in the +> A2DP capabilities, so it is assumed to be trivial. + +## Audio Location Configuration + +The semantic meaning for each channel is determined by their Audio +Location bitfield. + +| 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 audio location bit values 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 | + +Each bit value is associated with a Channel Order. The bits set in +the bitfield define audio locations for the streams present in the +payload. The set bit with the smallest Channel Order value defines the +audio location for the Channel Index *j=0*, the bit with the next +lowest Channel Order value defines the audio location for the Channel +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. + +> **Note** +> +> The channel audio location specification is similar to the location +> bitfield of the `Audio_Channel_Allocation` LTV structure in Bluetooth +> SIG [Assigned Numbers, Generic Audio] used in the LE Audio, and the +> bitmasks defined above are the same. +> +> The channel ordering differs from LE Audio, and is defined here to be +> compatible with the internal stream ordering in the reference Opus +> Multistream surround encoder Mapping Family 0 and 1 output. This +> allows making use of its surround masking and LFE handling +> capabilities. The stream ordering of the reference Opus surround +> encoder, although being unchanged since its addition in 2013, is an +> internal detail of the encoder. Implementations using the surround +> encoder need to check that the mapping table used by the encoder +> corresponds to the above channel ordering. +> +> For reference, we list the Audio Location bitfield values +> corresponding to the different channel counts in Opus Mapping Family 0 +> and 1 surround encoder output, and the expected mapping table: +> +> | Mapping Family | Channel Count | Audio Location Value | Stream Ordering | Mapping Table | +> |----------------|---------------|----------------------|---------------------------------|--------------------------| +> | 0 | 1 | 0x00000000 | mono | {0} | +> | 0 | 2 | 0x00000003 | FL, FR | {0, 1} | +> | 1 | 1 | 0x00000000 | mono | {0} | +> | 1 | 2 | 0x00000003 | FL, FR | {0, 1} | +> | 1 | 3 | 0x00000007 | FL, FR, FC | {0, 2, 1} | +> | 1 | 4 | 0x00000033 | FL, FR, BL, BR | {0, 1, 2, 3} | +> | 1 | 5 | 0x00000037 | FL, FR, BL, BR, FC | {0, 4, 1, 2, 3} | +> | 1 | 6 | 0x0000003f | FL, FR, BL, BR, FC, LFE | {0, 4, 1, 2, 3, 5} | +> | 1 | 7 | 0x00000d0f | FL, FR, SL, SR, FC, BC, LFE | {0, 4, 1, 2, 3, 5, 6} | +> | 1 | 8 | 0x00000c3f | FL, FR, SL, SR, BL, BR, FC, LFE | {0, 6, 1, 2, 3, 4, 5, 7} | +> +> The Mapping Table in the table indicates the mapping table selected by +> `opus_multistream_surround_encoder_create` (Opus 1.3.1). If the +> encoder outputs a different mapping table in a future Opus encoder +> release, the channel ordering will be incorrect, and the surround +> encoder can not be used. We expect that the probability of the Opus +> encoder authors making such changes is negligible. + +## Limits Configuration + +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. + +> **Note** +> +> This is a nonstandard extension to A2DP. The return direction audio +> data is simply sent back via the underlying L2CAP connection, which +> is bidirectional, in the same format as the forward direction audio. +> This is similar to what aptX-LL and FastStream do. + +# Packet Structure + +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** +> +> 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. Bluetooth [AVDTP v1.3] +2. IETF [RFC3550] +3. IETF [RFC7587] +4. IETF [RFC7845] +5. Bluetooth [Assigned Numbers, Generic Audio] + +[AVDTP v1.3]: https://www.bluetooth.com/specifications/specs/a-v-distribution-transport-protocol-1-3/ +[RFC3550]: https://datatracker.ietf.org/doc/html/rfc3550 +[RFC7587]: https://datatracker.ietf.org/doc/html/rfc7587 +[RFC7845]: https://datatracker.ietf.org/doc/html/rfc7845 +[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..927000de1d2019b6f4e03bf3c154bc3bbb2110b6 100644 --- a/spa/plugins/bluez5/a2dp-codec-aac.c +++ b/spa/plugins/bluez5/a2dp-codec-aac.c @@ -31,9 +31,15 @@ #include <spa/utils/dict.h> #include <fdk-aac/aacenc_lib.h> +#include <fdk-aac/aacdecoder_lib.h> #include "rtp.h" -#include "a2dp-codecs.h" +#include "media-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; @@ -58,7 +65,7 @@ struct impl { int samplesize; }; -static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, +static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { static const a2dp_aac_t a2dp_aac = { @@ -91,7 +98,7 @@ static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, return sizeof(a2dp_aac); } -static const struct a2dp_codec_config +static const struct media_codec_config aac_frequencies[] = { { AAC_SAMPLING_FREQ_48000, 48000, 11 }, { AAC_SAMPLING_FREQ_44100, 44100, 10 }, @@ -107,7 +114,7 @@ aac_frequencies[] = { { AAC_SAMPLING_FREQ_8000, 8000, 0 }, }; -static const struct a2dp_codec_config +static const struct media_codec_config aac_channel_modes[] = { { AAC_CHANNELS_2, 2, 1 }, { AAC_CHANNELS_1, 1, 0 }, @@ -123,9 +130,9 @@ static int get_valid_aac_bitrate(a2dp_aac_t *conf) } } -static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, - const struct a2dp_codec_audio_info *info, + const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE]) { a2dp_aac_t conf; @@ -147,7 +154,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, else return -ENOTSUP; - if ((i = a2dp_codec_select_config(aac_frequencies, + if ((i = media_codec_select_config(aac_frequencies, SPA_N_ELEMENTS(aac_frequencies), AAC_GET_FREQUENCY(conf), info ? info->rate : A2DP_CODEC_DEFAULT_RATE @@ -155,7 +162,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return -ENOTSUP; AAC_SET_FREQUENCY(conf, aac_frequencies[i].config); - if ((i = a2dp_codec_select_config(aac_channel_modes, + if ((i = media_codec_select_config(aac_channel_modes, SPA_N_ELEMENTS(aac_channel_modes), conf.channels, info ? info->channels : A2DP_CODEC_DEFAULT_CHANNELS @@ -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 media_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) { @@ -239,7 +246,7 @@ static int codec_enum_config(const struct a2dp_codec *codec, return *param == NULL ? -EIO : 1; } -static int codec_validate_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { @@ -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 media_codec *codec, uint32_t flags, const struct spa_dict *settings) { struct props *p = calloc(1, sizeof(struct props)); const char *str; @@ -309,7 +316,7 @@ static void codec_clear_props(void *props) free(props); } -static void *codec_init(const struct a2dp_codec *codec, uint32_t flags, +static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { @@ -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,7 +624,13 @@ static int codec_increase_bitpool(void *data) return codec_change_bitrate(this, (this->cur_bitrate * 4) / 3); } -const struct a2dp_codec a2dp_codec_aac = { +static void codec_set_log(struct spa_log *global_log) +{ + log = global_log; + spa_log_topic_init(log, &log_topic); +} + +const struct media_codec a2dp_codec_aac = { .id = SPA_BLUETOOTH_AUDIO_CODEC_AAC, .codec_id = A2DP_CODEC_MPEG24, .name = "aac", @@ -554,12 +646,15 @@ 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( +MEDIA_CODEC_EXPORT_DEF( "aac", &a2dp_codec_aac ); diff --git a/spa/plugins/bluez5/a2dp-codec-aptx.c b/spa/plugins/bluez5/a2dp-codec-aptx.c index 9a80134fd5c08c4d19a5411088c35d30c63c7e1b..6938e479c45732a6247e480b0616ff43c5b9b39d 100644 --- a/spa/plugins/bluez5/a2dp-codec-aptx.c +++ b/spa/plugins/bluez5/a2dp-codec-aptx.c @@ -35,7 +35,7 @@ #include <freeaptx.h> #include "rtp.h" -#include "a2dp-codecs.h" +#include "media-codecs.h" #define APTX_LL_LEVEL1(level) (((level) >> 8) & 0xFF) #define APTX_LL_LEVEL2(level) (((level) >> 0) & 0xFF) @@ -71,19 +71,19 @@ struct msbc_impl { sbc_t msbc; }; -static inline bool codec_is_hd(const struct a2dp_codec *codec) +static inline bool codec_is_hd(const struct media_codec *codec) { return codec->vendor.codec_id == APTX_HD_CODEC_ID && codec->vendor.vendor_id == APTX_HD_VENDOR_ID; } -static inline bool codec_is_ll(const struct a2dp_codec *codec) +static inline bool codec_is_ll(const struct media_codec *codec) { return (codec->id == SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL) || (codec->id == SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX); } -static inline size_t codec_get_caps_size(const struct a2dp_codec *codec) +static inline size_t codec_get_caps_size(const struct media_codec *codec) { if (codec_is_hd(codec)) return sizeof(a2dp_aptx_hd_t); @@ -93,7 +93,7 @@ static inline size_t codec_get_caps_size(const struct a2dp_codec *codec) return sizeof(a2dp_aptx_t); } -static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, +static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { size_t actual_conf_size = codec_get_caps_size(codec); @@ -119,7 +119,7 @@ static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, return actual_conf_size; } -static const struct a2dp_codec_config +static const struct media_codec_config aptx_frequencies[] = { { APTX_SAMPLING_FREQ_48000, 48000, 3 }, { APTX_SAMPLING_FREQ_44100, 44100, 2 }, @@ -127,9 +127,9 @@ aptx_frequencies[] = { { APTX_SAMPLING_FREQ_16000, 16000, 0 }, }; -static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, - const struct a2dp_codec_audio_info *info, + const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE]) { a2dp_aptx_t conf; @@ -145,7 +145,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, codec->vendor.codec_id != conf.info.codec_id) return -ENOTSUP; - if ((i = a2dp_codec_select_config(aptx_frequencies, + if ((i = media_codec_select_config(aptx_frequencies, SPA_N_ELEMENTS(aptx_frequencies), conf.frequency, info ? info->rate : A2DP_CODEC_DEFAULT_RATE @@ -163,9 +163,9 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return actual_conf_size; } -static int codec_select_config_ll(const struct a2dp_codec *codec, uint32_t flags, +static int codec_select_config_ll(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, - const struct a2dp_codec_audio_info *info, + const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE]) { a2dp_aptx_ll_ext_t conf = { 0 }; @@ -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 media_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) { @@ -315,7 +315,7 @@ static int codec_get_block_size(void *data) return this->codesize; } -static void *codec_init(const struct a2dp_codec *codec, uint32_t flags, +static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { @@ -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 media_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) { @@ -479,7 +479,7 @@ static int msbc_enum_config(const struct a2dp_codec *codec, return *param == NULL ? -EIO : 1; } -static int msbc_validate_config(const struct a2dp_codec *codec, uint32_t flags, +static int msbc_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { @@ -508,7 +508,7 @@ static int msbc_get_block_size(void *data) return MSBC_DECODED_SIZE; } -static void *msbc_init(const struct a2dp_codec *codec, uint32_t flags, +static void *msbc_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { @@ -610,7 +610,7 @@ static int msbc_decode(void *data, } -const struct a2dp_codec a2dp_codec_aptx = { +const struct media_codec a2dp_codec_aptx = { .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX, .codec_id = A2DP_CODEC_VENDOR, .vendor = { .vendor_id = APTX_VENDOR_ID, @@ -633,7 +633,7 @@ const struct a2dp_codec a2dp_codec_aptx = { }; -const struct a2dp_codec a2dp_codec_aptx_hd = { +const struct media_codec a2dp_codec_aptx_hd = { .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD, .codec_id = A2DP_CODEC_VENDOR, .vendor = { .vendor_id = APTX_HD_VENDOR_ID, @@ -671,7 +671,7 @@ const struct a2dp_codec a2dp_codec_aptx_hd = { .increase_bitpool = codec_increase_bitpool -const struct a2dp_codec a2dp_codec_aptx_ll_0 = { +const struct media_codec a2dp_codec_aptx_ll_0 = { APTX_LL_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL, .vendor = { .vendor_id = APTX_LL_VENDOR_ID, @@ -680,7 +680,7 @@ const struct a2dp_codec a2dp_codec_aptx_ll_0 = { .endpoint_name = "aptx_ll_0", }; -const struct a2dp_codec a2dp_codec_aptx_ll_1 = { +const struct media_codec a2dp_codec_aptx_ll_1 = { APTX_LL_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL, .vendor = { .vendor_id = APTX_LL_VENDOR_ID2, @@ -690,7 +690,7 @@ const struct a2dp_codec a2dp_codec_aptx_ll_1 = { }; /* Voice channel mSBC, not a real A2DP codec */ -static const struct a2dp_codec aptx_ll_msbc = { +static const struct media_codec aptx_ll_msbc = { .codec_id = A2DP_CODEC_VENDOR, .name = "aptx_ll_msbc", .description = "aptX-LL mSBC", @@ -710,7 +710,12 @@ static const struct a2dp_codec aptx_ll_msbc = { .increase_bitpool = msbc_increase_bitpool, }; -const struct a2dp_codec a2dp_codec_aptx_ll_duplex_0 = { +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 media_codec a2dp_codec_aptx_ll_duplex_0 = { APTX_LL_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX, .vendor = { .vendor_id = APTX_LL_VENDOR_ID, @@ -718,9 +723,10 @@ 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 = { +const struct media_codec a2dp_codec_aptx_ll_duplex_1 = { APTX_LL_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX, .vendor = { .vendor_id = APTX_LL_VENDOR_ID2, @@ -728,9 +734,10 @@ 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( +MEDIA_CODEC_EXPORT_DEF( "aptx", &a2dp_codec_aptx_hd, &a2dp_codec_aptx, 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..a579eadd0682bdd9294588c7a882354346188469 100644 --- a/spa/plugins/bluez5/a2dp-codec-faststream.c +++ b/spa/plugins/bluez5/a2dp-codec-faststream.c @@ -36,7 +36,7 @@ #include <sbc/sbc.h> -#include "a2dp-codecs.h" +#include "media-codecs.h" struct impl { sbc_t sbc; @@ -51,7 +51,7 @@ struct duplex_impl { sbc_t sbc; }; -static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, +static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { const a2dp_faststream_t a2dp_faststream = { @@ -69,20 +69,20 @@ static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, return sizeof(a2dp_faststream); } -static const struct a2dp_codec_config +static const struct media_codec_config frequencies[] = { { FASTSTREAM_SINK_SAMPLING_FREQ_48000, 48000, 1 }, { FASTSTREAM_SINK_SAMPLING_FREQ_44100, 44100, 0 }, }; -static const struct a2dp_codec_config +static const struct media_codec_config duplex_frequencies[] = { { FASTSTREAM_SOURCE_SAMPLING_FREQ_16000, 16000, 0 }, }; -static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, - const struct a2dp_codec_audio_info *info, + const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE]) { a2dp_faststream_t conf; @@ -108,7 +108,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, if (codec->duplex_codec) conf.direction |= FASTSTREAM_DIRECTION_SOURCE; - if ((i = a2dp_codec_select_config(frequencies, + if ((i = media_codec_select_config(frequencies, SPA_N_ELEMENTS(frequencies), conf.sink_frequency, info ? info->rate : A2DP_CODEC_DEFAULT_RATE @@ -116,7 +116,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return -ENOTSUP; conf.sink_frequency = frequencies[i].config; - if ((i = a2dp_codec_select_config(duplex_frequencies, + if ((i = media_codec_select_config(duplex_frequencies, SPA_N_ELEMENTS(duplex_frequencies), conf.source_frequency, 16000 @@ -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 media_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) { @@ -209,7 +209,7 @@ static size_t ceil2(size_t v) return v; } -static void *codec_init(const struct a2dp_codec *codec, uint32_t flags, +static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { @@ -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 media_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) { @@ -411,7 +411,7 @@ static int duplex_enum_config(const struct a2dp_codec *codec, return *param == NULL ? -EIO : 1; } -static int duplex_validate_config(const struct a2dp_codec *codec, uint32_t flags, +static int duplex_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { @@ -441,7 +441,7 @@ static int duplex_get_block_size(void *data) return 0; } -static void *duplex_init(const struct a2dp_codec *codec, uint32_t flags, +static void *duplex_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { @@ -577,7 +577,7 @@ static int duplex_decode(void *data, } /* Voice channel SBC, not a real A2DP codec */ -static const struct a2dp_codec duplex_codec = { +static const struct media_codec duplex_codec = { .codec_id = A2DP_CODEC_VENDOR, .name = "faststream_sbc", .description = "FastStream duplex SBC", @@ -614,20 +614,26 @@ static const struct a2dp_codec duplex_codec = { .reduce_bitpool = codec_reduce_bitpool, \ .increase_bitpool = codec_increase_bitpool -const struct a2dp_codec a2dp_codec_faststream = { +const struct media_codec a2dp_codec_faststream = { FASTSTREAM_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM, .name = "faststream", }; -const struct a2dp_codec a2dp_codec_faststream_duplex = { +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 media_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( +MEDIA_CODEC_EXPORT_DEF( "faststream", &a2dp_codec_faststream, &a2dp_codec_faststream_duplex diff --git a/spa/plugins/bluez5/a2dp-codec-lc3plus.c b/spa/plugins/bluez5/a2dp-codec-lc3plus.c index b50c947468f479e1ad3915095530660519e140ab..d8e1d284e1a6848b2bd4cf07a9faddb6b514584f 100644 --- a/spa/plugins/bluez5/a2dp-codec-lc3plus.c +++ b/spa/plugins/bluez5/a2dp-codec-lc3plus.c @@ -41,7 +41,7 @@ #endif #include "rtp.h" -#include "a2dp-codecs.h" +#include "media-codecs.h" #define BITRATE_MIN 96000 #define BITRATE_MAX 512000 @@ -86,7 +86,7 @@ struct impl { int32_t buf[2][LC3PLUS_MAX_SAMPLES]; }; -static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, +static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { const a2dp_lc3plus_hr_t a2dp_lc3plus_hr = { @@ -102,9 +102,9 @@ static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, return sizeof(a2dp_lc3plus_hr); } -static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, - const struct a2dp_codec_audio_info *info, + const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE]) { a2dp_lc3plus_hr_t conf; @@ -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 media_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, + const void *caps2, size_t caps2_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings) { a2dp_lc3plus_hr_t conf1, conf2; a2dp_lc3plus_hr_t *conf; @@ -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 media_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) { @@ -263,7 +263,7 @@ static int codec_enum_config(const struct a2dp_codec *codec, return *param == NULL ? -EIO : 1; } -static int codec_validate_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { @@ -342,7 +342,7 @@ static bool check_mtu_vs_frame_dms(struct impl *this) return (size_t)this->mtu >= header_size + ceildiv(payload_size, max_fragments); } -static void *codec_init(const struct a2dp_codec *codec, uint32_t flags, +static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { @@ -758,7 +758,7 @@ static int codec_increase_bitpool(void *data) return 0; } -const struct a2dp_codec a2dp_codec_lc3plus_hr = { +const struct media_codec a2dp_codec_lc3plus_hr = { .id = SPA_BLUETOOTH_AUDIO_CODEC_LC3PLUS_HR, .name = "lc3plus_hr", .codec_id = A2DP_CODEC_VENDOR, @@ -782,7 +782,7 @@ const struct a2dp_codec a2dp_codec_lc3plus_hr = { .increase_bitpool = codec_increase_bitpool }; -A2DP_CODEC_EXPORT_DEF( +MEDIA_CODEC_EXPORT_DEF( "lc3plus", &a2dp_codec_lc3plus_hr ); diff --git a/spa/plugins/bluez5/a2dp-codec-ldac.c b/spa/plugins/bluez5/a2dp-codec-ldac.c index ee6bc2ebf48300578696ff568c9f8c069a8f3cf9..6649c772e2c1ba0cf8c94d326b1c504e4599d62c 100644 --- a/spa/plugins/bluez5/a2dp-codec-ldac.c +++ b/spa/plugins/bluez5/a2dp-codec-ldac.c @@ -40,7 +40,7 @@ #endif #include "rtp.h" -#include "a2dp-codecs.h" +#include "media-codecs.h" #define LDACBT_EQMID_AUTO -1 @@ -79,7 +79,7 @@ struct impl { int frame_count; }; -static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE]) +static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { static const a2dp_ldac_t a2dp_ldac = { .info.vendor_id = LDAC_VENDOR_ID, @@ -97,7 +97,7 @@ static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, uint8 return sizeof(a2dp_ldac); } -static const struct a2dp_codec_config +static const struct media_codec_config ldac_frequencies[] = { { LDACBT_SAMPLING_FREQ_044100, 44100, 3 }, { LDACBT_SAMPLING_FREQ_048000, 48000, 2 }, @@ -105,16 +105,16 @@ ldac_frequencies[] = { { LDACBT_SAMPLING_FREQ_096000, 96000, 0 }, }; -static const struct a2dp_codec_config +static const struct media_codec_config ldac_channel_modes[] = { { LDACBT_CHANNEL_MODE_STEREO, 2, 2 }, { LDACBT_CHANNEL_MODE_DUAL_CHANNEL, 2, 1 }, { LDACBT_CHANNEL_MODE_MONO, 1, 0 }, }; -static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, - const struct a2dp_codec_audio_info *info, + const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE]) { a2dp_ldac_t conf; @@ -129,7 +129,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, codec->vendor.codec_id != conf.info.codec_id) return -ENOTSUP; - if ((i = a2dp_codec_select_config(ldac_frequencies, + if ((i = media_codec_select_config(ldac_frequencies, SPA_N_ELEMENTS(ldac_frequencies), conf.frequency, info ? info->rate : A2DP_CODEC_DEFAULT_RATE @@ -137,7 +137,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return -ENOTSUP; conf.frequency = ldac_frequencies[i].config; - if ((i = a2dp_codec_select_config(ldac_channel_modes, + if ((i = media_codec_select_config(ldac_channel_modes, SPA_N_ELEMENTS(ldac_channel_modes), conf.channel_mode, info ? info->channels : A2DP_CODEC_DEFAULT_CHANNELS @@ -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 media_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 media_codec *codec, uint32_t flags, const struct spa_dict *settings) { struct props *p = calloc(1, sizeof(struct props)); const char *str; @@ -385,7 +385,7 @@ static int codec_set_props(void *props, const struct spa_pod *param) return prev_eqmid != p->eqmid; } -static void *codec_init(const struct a2dp_codec *codec, uint32_t flags, +static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { @@ -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); @@ -570,7 +570,7 @@ static int codec_encode(void *data, return src_used; } -const struct a2dp_codec a2dp_codec_ldac = { +const struct media_codec a2dp_codec_ldac = { .id = SPA_BLUETOOTH_AUDIO_CODEC_LDAC, .codec_id = A2DP_CODEC_VENDOR, .vendor = { .vendor_id = LDAC_VENDOR_ID, @@ -598,7 +598,7 @@ const struct a2dp_codec a2dp_codec_ldac = { .increase_bitpool = codec_increase_bitpool, }; -A2DP_CODEC_EXPORT_DEF( +MEDIA_CODEC_EXPORT_DEF( "ldac", &a2dp_codec_ldac ); diff --git a/spa/plugins/bluez5/a2dp-codec-opus.c b/spa/plugins/bluez5/a2dp-codec-opus.c new file mode 100644 index 0000000000000000000000000000000000000000..87d7bd706ec8ec704d21ba09def7fe65d4921ad1 --- /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 "media-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 media_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 media_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 media_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 media_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 media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, + const struct media_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 media_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, + const void *caps2, size_t caps2_size, const struct media_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 media_codec *codec) +{ + return codec->id == 0; +} + +static bool use_surround_encoder(const struct media_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 media_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 & MEDIA_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 media_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 & MEDIA_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 media_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 media_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 & MEDIA_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 media_codec a2dp_codec_opus_05 = { + OPUS_05_COMMON_FULL_DEFS, + .id = SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05, + .name = "opus_05", + .description = "Opus", +}; + +const struct media_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 media_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 media_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 media_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 media_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, +}; + +MEDIA_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..27a57bded3958bce656ef91bd72953315c66c02e 100644 --- a/spa/plugins/bluez5/a2dp-codec-sbc.c +++ b/spa/plugins/bluez5/a2dp-codec-sbc.c @@ -33,7 +33,7 @@ #include <sbc/sbc.h> #include "rtp.h" -#include "a2dp-codecs.h" +#include "media-codecs.h" #define MAX_FRAME_COUNT 16 @@ -51,7 +51,7 @@ struct impl { int max_bitpool; }; -static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, +static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { static const a2dp_sbc_t a2dp_sbc = { @@ -121,7 +121,7 @@ static uint8_t default_bitpool(uint8_t freq, uint8_t mode, bool xq) } -static const struct a2dp_codec_config +static const struct media_codec_config sbc_frequencies[] = { { SBC_SAMPLING_FREQ_48000, 48000, 3 }, { SBC_SAMPLING_FREQ_44100, 44100, 2 }, @@ -129,13 +129,13 @@ sbc_frequencies[] = { { SBC_SAMPLING_FREQ_16000, 16000, 0 }, }; -static const struct a2dp_codec_config +static const struct media_codec_config sbc_xq_frequencies[] = { { SBC_SAMPLING_FREQ_44100, 44100, 1 }, { SBC_SAMPLING_FREQ_48000, 48000, 0 }, }; -static const struct a2dp_codec_config +static const struct media_codec_config sbc_channel_modes[] = { { SBC_CHANNEL_MODE_JOINT_STEREO, 2, 3 }, { SBC_CHANNEL_MODE_STEREO, 2, 2 }, @@ -143,22 +143,22 @@ sbc_channel_modes[] = { { SBC_CHANNEL_MODE_MONO, 1, 0 }, }; -static const struct a2dp_codec_config +static const struct media_codec_config sbc_xq_channel_modes[] = { { SBC_CHANNEL_MODE_DUAL_CHANNEL, 2, 2 }, { SBC_CHANNEL_MODE_JOINT_STEREO, 2, 1 }, { SBC_CHANNEL_MODE_STEREO, 2, 0 }, }; -static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, - const struct a2dp_codec_audio_info *info, + const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE]) { a2dp_sbc_t conf; int bitpool, i; size_t n; - const struct a2dp_codec_config *configs; + const struct media_codec_config *configs; bool xq = false; @@ -176,7 +176,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, configs = sbc_frequencies; n = SPA_N_ELEMENTS(sbc_frequencies); } - if ((i = a2dp_codec_select_config(configs, n, conf.frequency, + if ((i = media_codec_select_config(configs, n, conf.frequency, info ? info->rate : A2DP_CODEC_DEFAULT_RATE )) < 0) return -ENOTSUP; @@ -189,7 +189,7 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, configs = sbc_channel_modes; n = SPA_N_ELEMENTS(sbc_channel_modes); } - if ((i = a2dp_codec_select_config(configs, n, conf.channel_mode, + if ((i = media_codec_select_config(configs, n, conf.channel_mode, info ? info->channels : A2DP_CODEC_DEFAULT_CHANNELS )) < 0) return -ENOTSUP; @@ -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 media_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, + const void *caps2, size_t caps2_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings) { a2dp_sbc_t conf1, conf2; a2dp_sbc_t *conf; @@ -275,7 +275,7 @@ static int codec_caps_preference_cmp(const struct a2dp_codec *codec, const void #undef PREFER_BOOL } -static int codec_validate_config(const struct a2dp_codec *codec, uint32_t flags, +static int codec_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { @@ -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 media_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) { @@ -453,7 +453,7 @@ static int codec_get_block_size(void *data) return this->codesize; } -static void *codec_init(const struct a2dp_codec *codec, uint32_t flags, +static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { @@ -638,7 +638,7 @@ static int codec_decode(void *data, return res; } -const struct a2dp_codec a2dp_codec_sbc = { +const struct media_codec a2dp_codec_sbc = { .id = SPA_BLUETOOTH_AUDIO_CODEC_SBC, .codec_id = A2DP_CODEC_SBC, .name = "sbc", @@ -660,7 +660,7 @@ const struct a2dp_codec a2dp_codec_sbc = { .increase_bitpool = codec_increase_bitpool, }; -const struct a2dp_codec a2dp_codec_sbc_xq = { +const struct media_codec a2dp_codec_sbc_xq = { .id = SPA_BLUETOOTH_AUDIO_CODEC_SBC_XQ, .codec_id = A2DP_CODEC_SBC, .name = "sbc_xq", @@ -682,7 +682,7 @@ const struct a2dp_codec a2dp_codec_sbc_xq = { .increase_bitpool = codec_increase_bitpool, }; -A2DP_CODEC_EXPORT_DEF( +MEDIA_CODEC_EXPORT_DEF( "sbc", &a2dp_codec_sbc, &a2dp_codec_sbc_xq diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index f5c781dfb0ddbcba8d0e88fcccab15e2343cb76a..a47f60ebf962f73929daf1aff4109a075a7cdd6c 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,10 +74,11 @@ 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; -#define DEFAULT_ENABLED_PROFILES (SPA_BT_PROFILE_HSP_HS | SPA_BT_PROFILE_HFP_AG) +#define DEFAULT_ENABLED_PROFILES (SPA_BT_PROFILE_HFP_HF | SPA_BT_PROFILE_HFP_AG) enum spa_bt_profile enabled_profiles; struct spa_source sco; @@ -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,11 +252,14 @@ 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); } #define RFCOMM_MESSAGE_MAX_LENGTH 256 +/* from HF/HS to AG */ SPA_PRINTF_FUNC(2, 3) static ssize_t rfcomm_send_cmd(const struct rfcomm *rfcomm, const char *format, ...) { @@ -272,7 +280,14 @@ static ssize_t rfcomm_send_cmd(const struct rfcomm *rfcomm, const char *format, spa_log_debug(backend->log, "RFCOMM >> %s", message); - message[len] = '\n'; + /* + * The format of an AT command from the HF to the AG shall be: <AT command><cr> + * - HFP 1.8, 4.34.1 + * + * The format for a command from the HS to the AG is thus: AT<cmd>=<value><cr> + * - HSP 1.2, 4.8.1 + */ + message[len] = '\r'; /* `message` is no longer null-terminated */ len = write(rfcomm->source.fd, message, len + 1); @@ -286,6 +301,7 @@ static ssize_t rfcomm_send_cmd(const struct rfcomm *rfcomm, const char *format, return len; } +/* from AG to HF/HS */ SPA_PRINTF_FUNC(2, 3) static ssize_t rfcomm_send_reply(const struct rfcomm *rfcomm, const char *format, ...) { @@ -306,6 +322,18 @@ static ssize_t rfcomm_send_reply(const struct rfcomm *rfcomm, const char *format spa_log_debug(backend->log, "RFCOMM >> %s", &message[2]); + /* + * The format of the OK code from the AG to the HF shall be: <cr><lf>OK<cr><lf> + * The format of the generic ERROR code from the AG to the HF shall be: <cr><lf>ERROR<cr><lf> + * The format of an unsolicited result code from the AG to the HF shall be: <cr><lf><result code><cr><lf> + * - HFP 1.8, 4.34.1 + * + * If the command is processed successfully, the resulting response from the AG to the HS is: <cr><lf>OK<cr><lf> + * If the command is not processed successfully, or is not recognized, + * the resulting response from the AG to the HS is: <cr><lf>ERROR<cr><lf> + * The format for an unsolicited result code (such as RING) from the AG to the HS is: <cr><lf><result code><cr><lf> + * - HSP 1.2, 4.8.1 + */ message[0] = '\r'; message[1] = '\n'; message[len + 2] = '\r'; @@ -740,26 +768,21 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) /* retrieve supported codecs */ /* response has the form AT+BAC=<codecID1>,<codecID2>,<codecIDx> strategy: split the string into tokens */ - static const char separators[] = "=,"; char* token; int cntr = 0; - token = strtok (buf, separators); - while (token != NULL) - { + while ((token = strsep(&buf, "=,"))) { + unsigned int codec_id; + /* skip token 0 i.e. the "AT+BAC=" part */ - if (cntr > 0) { - int codec_id; - sscanf (token, "%u", &codec_id); + if (cntr > 0 && sscanf(token, "%u", &codec_id) == 1) { spa_log_debug(backend->log, "RFCOMM AT+BAC found codec %u", codec_id); if (codec_id == HFP_AUDIO_CODEC_MSBC) { rfcomm->msbc_supported_by_hfp = true; spa_log_debug(backend->log, "RFCOMM headset supports mSBC codec"); } } - /* get next token */ - token = strtok (NULL, separators); cntr++; } @@ -802,13 +825,14 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) } else if (!rfcomm->slc_configured) { spa_log_warn(backend->log, "RFCOMM receive command before SLC completed: %s", buf); rfcomm_send_reply(rfcomm, "ERROR"); - return false; + return true; } else if (sscanf(buf, "AT+BCS=%u", &selected_codec) == 1) { /* parse BCS(=Bluetooth Codec Selection) reply */ bool was_switching_codec = rfcomm->hfp_ag_switching_codec && (rfcomm->device != NULL); 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); @@ -910,29 +934,18 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* buf) { - static const char separators[] = "\r\n:"; - struct impl *backend = rfcomm->backend; unsigned int features; unsigned int gain; unsigned int selected_codec; char* token; - token = strtok(buf, separators); - while (token != NULL) - { - if (spa_strstartswith(token, "+BRSF")) { - /* get next token */ - token = strtok(NULL, separators); - features = atoi(token); + while ((token = strsep(&buf, "\r\n"))) { + if (sscanf(token, "+BRSF:%u", &features) == 1) { if (((features & (SPA_BT_HFP_AG_FEATURE_CODEC_NEGOTIATION)) != 0) && rfcomm->msbc_supported_by_hfp) rfcomm->codec_negotiation_supported = true; - } else if (spa_strstartswith(token, "+BCS") && rfcomm->codec_negotiation_supported) { - /* get next token */ - token = strtok(NULL, separators); - selected_codec = atoi(token); - + } else if (sscanf(token, "+BCS:%u", &selected_codec) == 1 && rfcomm->codec_negotiation_supported) { if (selected_codec != HFP_AUDIO_CODEC_CVSD && selected_codec != HFP_AUDIO_CODEC_MSBC) { spa_log_warn(backend->log, "unsupported codec negotiation: %d", selected_codec); } else { @@ -957,24 +970,13 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* buf) } } } - } else if (spa_strstartswith(token, "+CIND")) { - /* get next token and discard it */ - token = strtok(NULL, separators); - } else if (spa_strstartswith(token, "+VGM")) { - /* get next token */ - token = strtok(NULL, separators); - gain = atoi(token); - + } else if (sscanf(token, "+VGM%*1[:=]%u", &gain) == 1) { if (gain <= SPA_BT_VOLUME_HS_MAX) { rfcomm_emit_volume_changed(rfcomm, SPA_BT_VOLUME_ID_TX, gain); } else { spa_log_debug(backend->log, "RFCOMM receive unsupported VGM gain: %s", token); } - } else if (spa_strstartswith(token, "+VGS")) { - /* get next token */ - token = strtok(NULL, separators); - gain = atoi(token); - + } else if (sscanf(token, "+VGS%*1[:=]%u", &gain) == 1) { if (gain <= SPA_BT_VOLUME_HS_MAX) { rfcomm_emit_volume_changed(rfcomm, SPA_BT_VOLUME_ID_RX, gain); } else { @@ -1033,8 +1035,6 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* buf) break; } } - /* get next token */ - token = strtok(NULL, separators); } return true; @@ -1203,6 +1203,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 +1237,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 +1502,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 +1514,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 +1535,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 +1617,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 +2323,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/bap-codec-caps.h b/spa/plugins/bluez5/bap-codec-caps.h new file mode 100644 index 0000000000000000000000000000000000000000..7e6f69a1327a5856f84da7198e37e93cabdb5c73 --- /dev/null +++ b/spa/plugins/bluez5/bap-codec-caps.h @@ -0,0 +1,113 @@ +/* Spa BAP codec API + * + * Copyright © 2022 Collabora + * + * 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_BLUEZ5_BAP_CODEC_CAPS_H_ +#define SPA_BLUEZ5_BAP_CODEC_CAPS_H_ + +#define BAP_CODEC_LC3 0x06 + +#define LC3_TYPE_FREQ 0x01 +#define LC3_FREQ_8KHZ (1 << 0) +#define LC3_FREQ_11KHZ (1 << 1) +#define LC3_FREQ_16KHZ (1 << 2) +#define LC3_FREQ_22KHZ (1 << 3) +#define LC3_FREQ_24KHZ (1 << 4) +#define LC3_FREQ_32KHZ (1 << 5) +#define LC3_FREQ_44KHZ (1 << 6) +#define LC3_FREQ_48KHZ (1 << 7) +#define LC3_FREQ_ANY (LC3_FREQ_8KHZ | \ + LC3_FREQ_11KHZ | \ + LC3_FREQ_16KHZ | \ + LC3_FREQ_22KHZ | \ + LC3_FREQ_24KHZ | \ + LC3_FREQ_32KHZ | \ + LC3_FREQ_44KHZ | \ + LC3_FREQ_48KHZ) + +#define LC3_TYPE_DUR 0x02 +#define LC3_DUR_7_5 (1 << 0) +#define LC3_DUR_10 (1 << 1) +#define LC3_DUR_ANY (LC3_DUR_7_5 | \ + LC3_DUR_10) + +#define LC3_TYPE_CHAN 0x03 +#define LC3_CHAN_1 (1 << 0) +#define LC3_CHAN_2 (1 << 1) + +#define LC3_TYPE_FRAMELEN 0x04 +#define LC3_TYPE_BLKS 0x05 + +/* LC3 config parameters */ +#define LC3_CONFIG_FREQ_8KHZ 0x01 +#define LC3_CONFIG_FREQ_11KHZ 0x02 +#define LC3_CONFIG_FREQ_16KHZ 0x03 +#define LC3_CONFIG_FREQ_22KHZ 0x04 +#define LC3_CONFIG_FREQ_24KHZ 0x05 +#define LC3_CONFIG_FREQ_32KHZ 0x06 +#define LC3_CONFIG_FREQ_44KHZ 0x07 +#define LC3_CONFIG_FREQ_48KHZ 0x08 + +#define LC3_CONFIG_DURATION_7_5 0x00 +#define LC3_CONFIG_DURATION_10 0x01 + +#define LC3_CONFIG_CHNL_NOT_ALLOWED 0x00000000 +#define LC3_CONFIG_CHNL_FL 0x00000001 /* front left */ +#define LC3_CONFIG_CHNL_FR 0x00000002 /* front right */ +#define LC3_CONFIG_CHNL_FC 0x00000004 /* front center */ +#define LC3_CONFIG_CHNL_LFE 0x00000008 /* LFE */ +#define LC3_CONFIG_CHNL_BL 0x00000010 /* back left */ +#define LC3_CONFIG_CHNL_BR 0x00000020 /* back right */ +#define LC3_CONFIG_CHNL_FLC 0x00000040 /* front left center */ +#define LC3_CONFIG_CHNL_FRC 0x00000080 /* front right center */ +#define LC3_CONFIG_CHNL_BC 0x00000100 /* back center */ +#define LC3_CONFIG_CHNL_LFE2 0x00000200 /* LFE 2 */ +#define LC3_CONFIG_CHNL_SL 0x00000400 /* side left */ +#define LC3_CONFIG_CHNL_SR 0x00000800 /* side right */ +#define LC3_CONFIG_CHNL_TFL 0x00001000 /* top front left */ +#define LC3_CONFIG_CHNL_TFR 0x00002000 /* top front right */ +#define LC3_CONFIG_CHNL_TFC 0x00004000 /* top front center */ +#define LC3_CONFIG_CHNL_TC 0x00008000 /* top center */ +#define LC3_CONFIG_CHNL_TBL 0x00010000 /* top back left */ +#define LC3_CONFIG_CHNL_TBR 0x00020000 /* top back right */ +#define LC3_CONFIG_CHNL_TSL 0x00040000 /* top side left */ +#define LC3_CONFIG_CHNL_TSR 0x00080000 /* top side right */ +#define LC3_CONFIG_CHNL_TBC 0x00100000 /* top back center */ +#define LC3_CONFIG_CHNL_BFC 0x00200000 /* bottom front center */ +#define LC3_CONFIG_CHNL_BFL 0x00400000 /* bottom front left */ +#define LC3_CONFIG_CHNL_BFR 0x00800000 /* bottom front right */ +#define LC3_CONFIG_CHNL_FLW 0x01000000 /* front left wide */ +#define LC3_CONFIG_CHNL_FRW 0x02000000 /* front right wide */ +#define LC3_CONFIG_CHNL_LS 0x04000000 /* left surround */ +#define LC3_CONFIG_CHNL_RS 0x08000000 /* right surround */ + +#define LC3_MAX_CHANNELS 28 + +typedef struct { + uint8_t rate; + uint8_t frame_duration; + uint32_t channels; + uint16_t framelen; + uint8_t n_blks; +} __attribute__ ((packed)) bap_lc3_t; + +#endif diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c new file mode 100644 index 0000000000000000000000000000000000000000..3ce13ce7d5f4fd50f9a7693fd75fed52ecc5e68e --- /dev/null +++ b/spa/plugins/bluez5/bap-codec-lc3.c @@ -0,0 +1,772 @@ +/* Spa BAP LC3 codec + * + * Copyright © 2020 Wim Taymans + * Copyright © 2022 Pauli Virtanen + * Copyright © 2022 Collabora + * + * 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 <bits/stdint-uintn.h> +#include <string.h> +#include <unistd.h> +#include <stddef.h> +#include <errno.h> +#include <arpa/inet.h> +#include <bluetooth/bluetooth.h> + +#include <spa/param/audio/format.h> +#include <spa/param/audio/format-utils.h> + +#include <lc3.h> + +#include "media-codecs.h" +#include "bap-codec-caps.h" + +struct impl { + lc3_encoder_t enc[LC3_MAX_CHANNELS]; + lc3_decoder_t dec[LC3_MAX_CHANNELS]; + + int mtu; + int samplerate; + int channels; + int frame_dus; + int framelen; + int samples; + unsigned int codesize; +}; + +struct ltv { + uint8_t len; + uint8_t type; + uint8_t value[0]; +} __packed; + +static int write_ltv(uint8_t *dest, uint8_t type, void* value, size_t len) +{ + struct ltv *ltv = (struct ltv *)dest; + + ltv->len = len + 1; + ltv->type = type; + memcpy(ltv->value, value, len); + + return len + 2; +} + +static int write_ltv_uint8(uint8_t *dest, uint8_t type, uint8_t value) +{ + return write_ltv(dest, type, &value, sizeof(value)); +} + +static int write_ltv_uint16(uint8_t *dest, uint8_t type, uint16_t value) +{ + return write_ltv(dest, type, &value, sizeof(value)); +} + +static int write_ltv_uint32(uint8_t *dest, uint8_t type, uint32_t value) +{ + return write_ltv(dest, type, &value, sizeof(value)); +} + +static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, + uint8_t caps[A2DP_MAX_CAPS_SIZE]) +{ + uint8_t *data = caps; + uint16_t framelen[2] = {htobs(LC3_MIN_FRAME_BYTES), htobs(LC3_MAX_FRAME_BYTES)}; + + data += write_ltv_uint16(data, LC3_TYPE_FREQ, + htobs(LC3_FREQ_48KHZ | LC3_FREQ_24KHZ | LC3_FREQ_16KHZ | LC3_FREQ_8KHZ)); + data += write_ltv_uint8(data, LC3_TYPE_DUR, LC3_DUR_ANY); + data += write_ltv_uint8(data, LC3_TYPE_CHAN, LC3_CHAN_1 | LC3_CHAN_2); + data += write_ltv(data, LC3_TYPE_FRAMELEN, framelen, sizeof(framelen)); + data += write_ltv_uint8(data, LC3_TYPE_BLKS, 2); + + return data - caps; +} + +static bool parse_capabilities(bap_lc3_t *conf, const uint8_t *data, size_t data_size) +{ + uint16_t framelen_min = 0, framelen_max = 0; + + if (!data_size) + return false; + memset(conf, 0, sizeof(*conf)); + + conf->frame_duration = 0xFF; + + while (data_size > 0) { + struct ltv *ltv = (struct ltv *)data; + + if (ltv->len > data_size) + return false; + + switch (ltv->type) { + case LC3_TYPE_FREQ: + spa_return_val_if_fail(ltv->len == 3, false); + { + uint16_t rate = ltv->value[0] + (ltv->value[1] << 8); + if (rate & LC3_FREQ_48KHZ) + conf->rate = LC3_CONFIG_FREQ_48KHZ; + else if (rate & LC3_FREQ_24KHZ) + conf->rate = LC3_CONFIG_FREQ_24KHZ; + else if (rate & LC3_FREQ_16KHZ) + conf->rate = LC3_CONFIG_FREQ_16KHZ; + else if (rate & LC3_FREQ_8KHZ) + conf->rate = LC3_CONFIG_FREQ_8KHZ; + else + return false; + } + break; + case LC3_TYPE_DUR: + spa_return_val_if_fail(ltv->len == 2, false); + { + uint8_t duration = ltv->value[0]; + if (duration & LC3_DUR_10) + conf->frame_duration = LC3_CONFIG_DURATION_10; + else if (duration & LC3_DUR_7_5) + conf->frame_duration = LC3_CONFIG_DURATION_7_5; + else + return false; + } + break; + case LC3_TYPE_CHAN: + spa_return_val_if_fail(ltv->len == 2, false); + { + uint8_t channels = ltv->value[0]; + /* Only mono or stereo streams are currently supported, + * in both case Audio location is defined as both Front Left + * and Front Right, difference is done by the n_blks parameter. + */ + if ((channels & LC3_CHAN_2) || (channels & LC3_CHAN_1)) + conf->channels = LC3_CONFIG_CHNL_FR | LC3_CONFIG_CHNL_FL; + else + return false; + } + break; + case LC3_TYPE_FRAMELEN: + spa_return_val_if_fail(ltv->len == 5, false); + framelen_min = ltv->value[0] + (ltv->value[1] << 8); + framelen_max = ltv->value[2] + (ltv->value[3] << 8); + break; + case LC3_TYPE_BLKS: + spa_return_val_if_fail(ltv->len == 2, false); + conf->n_blks = ltv->value[0]; + if (!conf->n_blks) + return false; + break; + default: + return false; + } + data_size -= ltv->len + 1; + data += ltv->len + 1; + } + + if (framelen_min < LC3_MIN_FRAME_BYTES || framelen_max > LC3_MAX_FRAME_BYTES) + return false; + if (conf->frame_duration == 0xFF || !conf->rate) + return false; + if (!conf->channels) + conf->channels = LC3_CONFIG_CHNL_FL; + + switch (conf->rate) { + case LC3_CONFIG_FREQ_48KHZ: + if (conf->frame_duration == LC3_CONFIG_DURATION_7_5) + conf->framelen = 117; + else + conf->framelen = 120; + break; + case LC3_CONFIG_FREQ_24KHZ: + if (conf->frame_duration == LC3_CONFIG_DURATION_7_5) + conf->framelen = 45; + else + conf->framelen = 60; + break; + case LC3_CONFIG_FREQ_16KHZ: + if (conf->frame_duration == LC3_CONFIG_DURATION_7_5) + conf->framelen = 30; + else + conf->framelen = 40; + break; + case LC3_CONFIG_FREQ_8KHZ: + if (conf->frame_duration == LC3_CONFIG_DURATION_7_5) + conf->framelen = 26; + else + conf->framelen = 30; + break; + default: + return false; + } + + return true; +} + +static bool parse_conf(bap_lc3_t *conf, const uint8_t *data, size_t data_size) +{ + if (!data_size) + return false; + memset(conf, 0, sizeof(*conf)); + + conf->frame_duration = 0xFF; + + while (data_size > 0) { + struct ltv *ltv = (struct ltv *)data; + + if (ltv->len > data_size) + return false; + + switch (ltv->type) { + case LC3_TYPE_FREQ: + spa_return_val_if_fail(ltv->len == 2, false); + conf->rate = ltv->value[0]; + break; + case LC3_TYPE_DUR: + spa_return_val_if_fail(ltv->len == 2, false); + conf->frame_duration = ltv->value[0]; + break; + case LC3_TYPE_CHAN: + spa_return_val_if_fail(ltv->len == 5, false); + conf->channels = ltv->value[0] + (ltv->value[1] << 8) + (ltv->value[2] << 16) + (ltv->value[3] << 24); + break; + case LC3_TYPE_FRAMELEN: + spa_return_val_if_fail(ltv->len == 3, false); + conf->framelen = ltv->value[0] + (ltv->value[1] << 8); + break; + case LC3_TYPE_BLKS: + spa_return_val_if_fail(ltv->len == 2, false); + conf->n_blks = ltv->value[0]; + if (!conf->n_blks) + return false; + break; + default: + return false; + } + data_size -= ltv->len + 1; + data += ltv->len + 1; + } + + if (conf->frame_duration == 0xFF || !conf->rate) + return false; + + return true; +} + +static int codec_select_config(const struct media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, + const struct media_codec_audio_info *info, + const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE]) +{ + bap_lc3_t conf; + uint8_t *data = config; + + if (caps == NULL) + return -EINVAL; + + if (!parse_capabilities(&conf, caps, caps_size)) + return -ENOTSUP; + + data += write_ltv_uint8(data, LC3_TYPE_FREQ, conf.rate); + data += write_ltv_uint8(data, LC3_TYPE_DUR, conf.frame_duration); + data += write_ltv_uint32(data, LC3_TYPE_CHAN, htobl(conf.channels)); + data += write_ltv_uint16(data, LC3_TYPE_FRAMELEN, htobs(conf.framelen)); + data += write_ltv_uint8(data, LC3_TYPE_BLKS, conf.n_blks); + + return data - config; +} + +static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, + const void *caps2, size_t caps2_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings) +{ + bap_lc3_t conf1, conf2; + bap_lc3_t *conf; + int res1, res2; + int a, b; + + /* 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); + +#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(bap_lc3_t)) ? 1 : 0; + b = (res2 > 0 && (size_t)res2 == sizeof(bap_lc3_t)) ? 1 : 0; + if (!a || !b) + return b - a; + + PREFER_BOOL(conf->channels & LC3_CHAN_2); + PREFER_BOOL(conf->rate & (LC3_CONFIG_FREQ_48KHZ | LC3_CONFIG_FREQ_24KHZ | LC3_CONFIG_FREQ_16KHZ | LC3_CONFIG_FREQ_8KHZ)); + PREFER_BOOL(conf->rate & LC3_CONFIG_FREQ_48KHZ); + + return 0; + +#undef PREFER_EXPR +#undef PREFER_BOOL +} + +static uint8_t channels_to_positions(uint32_t channels, uint8_t n_channels, uint32_t *position) +{ + uint8_t n_positions = 0; + + spa_assert(n_channels <= SPA_AUDIO_MAX_CHANNELS); + + /* First check if stream is configure for Mono, i.e. 1 block for both Front + * Left anf Front Right, + * else map LE Audio locations to PipeWire locations in the ascending order + * which will be used as block order in stream. + */ + if ((channels & (LC3_CONFIG_CHNL_FR | LC3_CONFIG_CHNL_FL)) == (LC3_CONFIG_CHNL_FR | LC3_CONFIG_CHNL_FL) && + n_channels == 1) { + position[0] = SPA_AUDIO_CHANNEL_MONO; + n_positions = 1; + } else { +#define CHANNEL_2_SPACHANNEL(channel,spa_channel) if (channels & channel) position[n_positions++] = spa_channel; + + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_FL, SPA_AUDIO_CHANNEL_FL); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_FR, SPA_AUDIO_CHANNEL_FR); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_FC, SPA_AUDIO_CHANNEL_FC); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_LFE, SPA_AUDIO_CHANNEL_LFE); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_BL, SPA_AUDIO_CHANNEL_RL); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_BR, SPA_AUDIO_CHANNEL_RR); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_FLC, SPA_AUDIO_CHANNEL_FLC); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_FRC, SPA_AUDIO_CHANNEL_FRC); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_BC, SPA_AUDIO_CHANNEL_BC); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_LFE2, SPA_AUDIO_CHANNEL_LFE2); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_SL, SPA_AUDIO_CHANNEL_SL); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_SR, SPA_AUDIO_CHANNEL_SR); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_TFL, SPA_AUDIO_CHANNEL_TFL); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_TFR, SPA_AUDIO_CHANNEL_TFR); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_TFC, SPA_AUDIO_CHANNEL_TFC); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_TC, SPA_AUDIO_CHANNEL_TC); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_TBL, SPA_AUDIO_CHANNEL_TRL); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_TBR, SPA_AUDIO_CHANNEL_TRR); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_TSL, SPA_AUDIO_CHANNEL_TSL); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_TSR, SPA_AUDIO_CHANNEL_TSR); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_TBC, SPA_AUDIO_CHANNEL_TRC); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_BFC, SPA_AUDIO_CHANNEL_BC); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_BFL, SPA_AUDIO_CHANNEL_BLC); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_BFR, SPA_AUDIO_CHANNEL_BRC); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_FLW, SPA_AUDIO_CHANNEL_FLW); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_FRW, SPA_AUDIO_CHANNEL_FRW); + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_LS, SPA_AUDIO_CHANNEL_LLFE); /* is it the right mapping? */ + CHANNEL_2_SPACHANNEL(LC3_CONFIG_CHNL_RS, SPA_AUDIO_CHANNEL_RLFE); /* is it the right mapping? */ + +#undef CHANNEL_2_SPACHANNEL + } + + return n_positions; +} + +static int codec_enum_config(const struct media_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) +{ + bap_lc3_t conf; + struct spa_pod_frame f[2]; + struct spa_pod_choice *choice; + uint32_t position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t i = 0; + uint8_t res; + + if (!parse_conf(&conf, caps, caps_size)) + return -EINVAL; + + if (idx > 0) + return 0; + + 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_S24_32), + 0); + spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0); + + spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0); + choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]); + i = 0; + if (conf.rate & LC3_CONFIG_FREQ_48KHZ) { + if (i++ == 0) + spa_pod_builder_int(b, 48000); + spa_pod_builder_int(b, 48000); + } + if (conf.rate & LC3_CONFIG_FREQ_24KHZ) { + if (i++ == 0) + spa_pod_builder_int(b, 24000); + spa_pod_builder_int(b, 24000); + } + if (conf.rate & LC3_CONFIG_FREQ_16KHZ) { + if (i++ == 0) + spa_pod_builder_int(b, 16000); + spa_pod_builder_int(b, 16000); + } + if (conf.rate & LC3_CONFIG_FREQ_8KHZ) { + if (i++ == 0) + spa_pod_builder_int(b, 8000); + spa_pod_builder_int(b, 8000); + } + if (i == 0) + return -EINVAL; + if (i > 1) + choice->body.type = SPA_CHOICE_Enum; + spa_pod_builder_pop(b, &f[1]); + + res = channels_to_positions(conf.channels, conf.n_blks, position); + if (res == 0) + return -EINVAL; + spa_pod_builder_add(b, + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(res), + SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), + SPA_TYPE_Id, res, position), + 0); + + *param = spa_pod_builder_pop(b, &f[0]); + return *param == NULL ? -EIO : 1; +} + +static int codec_validate_config(const struct media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, + struct spa_audio_info *info) +{ + bap_lc3_t conf; + uint8_t res; + + if (caps == NULL) + return -EINVAL; + + if (!parse_conf(&conf, caps, caps_size)) + return -ENOTSUP; + + spa_zero(*info); + info->media_type = SPA_MEDIA_TYPE_audio; + info->media_subtype = SPA_MEDIA_SUBTYPE_raw; + info->info.raw.format = SPA_AUDIO_FORMAT_S24_32; + + switch (conf.rate) { + case LC3_CONFIG_FREQ_48KHZ: + info->info.raw.rate = 48000U; + break; + case LC3_CONFIG_FREQ_24KHZ: + info->info.raw.rate = 24000U; + break; + case LC3_CONFIG_FREQ_16KHZ: + info->info.raw.rate = 16000U; + break; + case LC3_CONFIG_FREQ_8KHZ: + info->info.raw.rate = 8000U; + break; + default: + return -EINVAL; + } + + res = channels_to_positions(conf.channels, conf.n_blks, info->info.raw.position); + if (res == 0) + return -EINVAL; + info->info.raw.channels = res; + + switch (conf.frame_duration) { + case LC3_CONFIG_DURATION_10: + case LC3_CONFIG_DURATION_7_5: + break; + default: + return -EINVAL; + } + + return 0; +} + +static void codec_get_qos(const struct media_codec *codec, + const void *config, size_t config_size, + struct codec_qos *qos) +{ + bap_lc3_t conf; + + memset(qos, 0, sizeof(*qos)); + + if (!parse_conf(&conf, config, config_size)) + return; + + qos->framing = false; + qos->phy = "2M"; + qos->retransmission = 2; /* default */ + qos->sdu = conf.framelen * conf.n_blks; + qos->latency = 20; /* default */ + qos->delay = 40000U; + qos->interval = (conf.frame_duration == LC3_CONFIG_DURATION_7_5 ? 7500 : 10000); + + switch (conf.rate) { + case LC3_CONFIG_FREQ_8KHZ: + case LC3_CONFIG_FREQ_16KHZ: + case LC3_CONFIG_FREQ_24KHZ: + case LC3_CONFIG_FREQ_32KHZ: + qos->retransmission = 2; + qos->latency = (conf.frame_duration == LC3_CONFIG_DURATION_7_5 ? 8 : 10); + break; + case LC3_CONFIG_FREQ_48KHZ: + qos->retransmission = 5; + qos->latency = (conf.frame_duration == LC3_CONFIG_DURATION_7_5 ? 15 : 20); + break; + } +} + +static void *codec_init(const struct media_codec *codec, uint32_t flags, + void *config, size_t config_len, const struct spa_audio_info *info, + void *props, size_t mtu) +{ + bap_lc3_t conf; + struct impl *this = NULL; + struct spa_audio_info config_info; + int res, ich; + + if (info->media_type != SPA_MEDIA_TYPE_audio || + info->media_subtype != SPA_MEDIA_SUBTYPE_raw || + info->info.raw.format != SPA_AUDIO_FORMAT_S24_32) { + res = -EINVAL; + goto error; + } + + if ((this = calloc(1, sizeof(struct impl))) == NULL) + goto error_errno; + + if ((res = codec_validate_config(codec, flags, config, config_len, &config_info)) < 0) + goto error; + + if (!parse_conf(&conf, config, config_len)) { + res = -ENOTSUP; + goto error; + } + + this->mtu = mtu; + this->samplerate = config_info.info.raw.rate; + this->channels = config_info.info.raw.channels; + this->framelen = conf.framelen; + + switch (conf.frame_duration) { + case LC3_CONFIG_DURATION_10: + this->frame_dus = 10000; + break; + case LC3_CONFIG_DURATION_7_5: + this->frame_dus = 7500; + break; + default: + res = -EINVAL; + goto error; + } + + this->samples = lc3_frame_samples(this->frame_dus, this->samplerate); + if (this->samples < 0) { + res = -EINVAL; + goto error; + } + this->codesize = this->samples * this->channels * sizeof(int32_t); + + if (flags & MEDIA_CODEC_FLAG_SINK) { + for (ich = 0; ich < this->channels; ich++) { + this->enc[ich] = lc3_setup_encoder(this->frame_dus, this->samplerate, 0, calloc(1, lc3_encoder_size(this->frame_dus, this->samplerate))); + if (this->enc[ich] == NULL) { + res = -EINVAL; + goto error; + } + } + } else { + for (ich = 0; ich < this->channels; ich++) { + this->dec[ich] = lc3_setup_decoder(this->frame_dus, this->samplerate, 0, calloc(1, lc3_decoder_size(this->frame_dus, this->samplerate))); + if (this->dec[ich] == NULL) { + res = -EINVAL; + goto error; + } + } + } + + return this; + +error_errno: + res = -errno; + goto error; + +error: + if (this) { + for (ich = 0; ich < this->channels; ich++) { + if (this->enc[ich]) + free(this->enc[ich]); + if (this->dec[ich]) + free(this->dec[ich]); + } + } + free(this); + errno = -res; + return NULL; +} + +static void codec_deinit(void *data) +{ + struct impl *this = data; + int ich; + + for (ich = 0; ich < this->channels; ich++) { + if (this->enc[ich]) + free(this->enc[ich]); + if (this->dec[ich]) + free(this->dec[ich]); + } + free(this); +} + +static int codec_get_block_size(void *data) +{ + struct impl *this = data; + return this->codesize; +} + +static int codec_abr_process (void *data, size_t unsent) +{ + return -ENOTSUP; +} + +static int codec_start_encode (void *data, + void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) +{ + return 0; +} + +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; + int frame_bytes; + int ich, res; + int size, processed; + + frame_bytes = lc3_frame_bytes(this->frame_dus, this->samplerate); + processed = 0; + size = 0; + + if (src_size < (size_t)this->codesize) + goto done; + if (dst_size < (size_t)frame_bytes) + goto done; + + for (ich = 0; ich < this->channels; ich++) { + uint8_t *in = (uint8_t *)src + (ich * 4); + uint8_t *out = (uint8_t *)dst + ich * this->framelen; + res = lc3_encode(this->enc[ich], LC3_PCM_FORMAT_S24, in, this->channels, this->framelen, out); + size += this->framelen; + if (SPA_UNLIKELY(res != 0)) + return -EINVAL; + } + *dst_out = size; + + processed += this->codesize; + +done: + spa_assert(size <= this->mtu); + *need_flush = NEED_FLUSH_ALL; + + return processed; +} + +static SPA_UNUSED int codec_start_decode (void *data, + const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) +{ + return 0; +} + +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 ich, res; + int consumed; + int samples; + + spa_return_val_if_fail((size_t)(this->framelen * this->channels) == src_size, -EINVAL); + consumed = 0; + + samples = lc3_frame_samples(this->frame_dus, this->samplerate); + if (samples == -1) + return -EINVAL; + if (dst_size < this->codesize) + return -EINVAL; + + for (ich = 0; ich < this->channels; ich++) { + uint8_t *in = (uint8_t *)src + ich * this->framelen; + uint8_t *out = (uint8_t *)dst + (ich * 4); + res = lc3_decode(this->dec[ich], in, this->framelen, LC3_PCM_FORMAT_S24, out, this->channels); + if (SPA_UNLIKELY(res < 0)) + return -EINVAL; + consumed += this->framelen; + } + + *dst_out = this->codesize; + + return consumed; +} + +static int codec_reduce_bitpool(void *data) +{ + return -ENOTSUP; +} + +static int codec_increase_bitpool(void *data) +{ + return -ENOTSUP; +} + +const struct media_codec bap_codec_lc3 = { + .id = SPA_BLUETOOTH_AUDIO_CODEC_LC3, + .name = "lc3", + .codec_id = BAP_CODEC_LC3, + .bap = true, + .description = "LC3", + .fill_caps = codec_fill_caps, + .select_config = codec_select_config, + .enum_config = codec_enum_config, + .validate_config = codec_validate_config, + .get_qos = codec_get_qos, + .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, + .start_decode = codec_start_decode, + .decode = codec_decode, + .reduce_bitpool = codec_reduce_bitpool, + .increase_bitpool = codec_increase_bitpool +}; + +MEDIA_CODEC_EXPORT_DEF( + "lc3", + &bap_codec_lc3 +); 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..c7ed335fcbb4d0a889b0d99e190e55016e698235 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -100,7 +100,7 @@ struct spa_bt_monitor { uint32_t id; - const struct a2dp_codec * const * a2dp_codecs; + const struct media_codec * const * media_codecs; /* * Lists of BlueZ objects, kept up-to-date by following DBus events @@ -125,8 +125,14 @@ 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; + struct media_codec_audio_info default_audio_info; + + bool le_audio_supported; }; /* Stream endpoints owned by BlueZ for each device */ @@ -142,6 +148,7 @@ struct spa_bt_remote_endpoint { uint8_t *capabilities; int capabilities_len; bool delay_reporting; + bool acceptor; }; /* @@ -151,7 +158,7 @@ struct spa_bt_remote_endpoint { * with the desired capabilities. * The codec switch struct tracks candidates still to be tried. */ -struct spa_bt_a2dp_codec_switch { +struct spa_bt_media_codec_switch { struct spa_bt_device *device; struct spa_list device_link; @@ -168,10 +175,10 @@ struct spa_bt_a2dp_codec_switch { * Called asynchronously, so endpoint paths instead of pointers (which may be * invalidated in the meantime). */ - const struct a2dp_codec **codecs; + const struct media_codec **codecs; char **paths; - const struct a2dp_codec **codec_iter; /**< outer iterator over codecs */ + const struct media_codec **codec_iter; /**< outer iterator over codecs */ char **path_iter; /**< inner iterator over endpoint paths */ uint16_t retries; @@ -426,10 +433,17 @@ static void register_battery_provider(struct spa_bt_device *device) } } -static int a2dp_codec_to_endpoint(const struct a2dp_codec *codec, - const char * endpoint, +static int media_codec_to_endpoint(const struct media_codec *codec, + enum spa_bt_media_direction direction, char** object_path) { + const char * endpoint; + + if (direction == SPA_BT_MEDIA_SOURCE) + endpoint = codec->bap ? BAP_SOURCE_ENDPOINT : A2DP_SOURCE_ENDPOINT; + else + endpoint = codec->bap ? BAP_SINK_ENDPOINT : A2DP_SINK_ENDPOINT; + *object_path = spa_aprintf("%s/%s", endpoint, codec->endpoint_name ? codec->endpoint_name : codec->name); if (*object_path == NULL) @@ -437,21 +451,31 @@ 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 media_codec *media_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; + const struct media_codec * const * const media_codecs = monitor->media_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 if (spa_strstartswith(endpoint, BAP_SOURCE_ENDPOINT "/")) { + ep_name = endpoint + strlen(BAP_SOURCE_ENDPOINT "/"); + *sink = false; + } else if (spa_strstartswith(endpoint, BAP_SINK_ENDPOINT "/")) { + ep_name = endpoint + strlen(BAP_SINK_ENDPOINT "/"); + *sink = true; + } else { + *sink = true; return NULL; + } - for (i = 0; a2dp_codecs[i]; i++) { - const struct a2dp_codec *codec = a2dp_codecs[i]; + for (i = 0; media_codecs[i]; i++) { + const struct media_codec *codec = media_codecs[i]; const char *codec_ep_name = codec->endpoint_name ? codec->endpoint_name : codec->name; if (spa_streq(ep_name, codec_ep_name)) @@ -460,18 +484,22 @@ static const struct a2dp_codec *a2dp_endpoint_to_codec(struct spa_bt_monitor *mo return NULL; } -static int a2dp_endpoint_to_profile(const char *endpoint) +static int media_endpoint_to_profile(const char *endpoint) { if (spa_strstartswith(endpoint, A2DP_SINK_ENDPOINT "/")) return SPA_BT_PROFILE_A2DP_SOURCE; else if (spa_strstartswith(endpoint, A2DP_SOURCE_ENDPOINT "/")) return SPA_BT_PROFILE_A2DP_SINK; + else if (spa_strstartswith(endpoint, BAP_SINK_ENDPOINT "/")) + return SPA_BT_PROFILE_BAP_SOURCE; + else if (spa_strstartswith(endpoint, BAP_SOURCE_ENDPOINT "/")) + return SPA_BT_PROFILE_BAP_SINK; else return SPA_BT_PROFILE_NULL; } -static bool is_a2dp_codec_enabled(struct spa_bt_monitor *monitor, const struct a2dp_codec *codec) +static bool is_media_codec_enabled(struct spa_bt_monitor *monitor, const struct media_codec *codec) { return spa_dict_lookup(&monitor->enabled_codecs, codec->name) != NULL; } @@ -484,8 +512,9 @@ static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBu uint8_t *pconf = (uint8_t *) config; DBusMessage *r; DBusError err; - int i, size, res; - const struct a2dp_codec *codec; + int size, res; + const struct media_codec *codec; + bool sink; dbus_error_init(&err); @@ -498,17 +527,17 @@ static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBu return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } spa_log_info(monitor->log, "%p: %s select conf %d", monitor, path, size); - for (i = 0; i < size; i++) - spa_log_debug(monitor->log, " %d: %02x", i, cap[i]); + spa_log_hexdump(monitor->log, SPA_LOG_LEVEL_DEBUG, 2, cap, (size_t)size); - codec = a2dp_endpoint_to_codec(monitor, path); + codec = media_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 ? MEDIA_CODEC_FLAG_SINK : 0, cap, size, &monitor->default_audio_info, + &monitor->global_settings, config); else res = -ENOTSUP; @@ -520,8 +549,7 @@ static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBu return DBUS_HANDLER_RESULT_NEED_MEMORY; goto exit_send; } - for (i = 0; i < size; i++) - spa_log_debug(monitor->log, " %d: %02x", i, pconf[i]); + spa_log_hexdump(monitor->log, SPA_LOG_LEVEL_DEBUG, 2, pconf, (size_t)size); if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; @@ -538,6 +566,171 @@ static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBu return DBUS_HANDLER_RESULT_HANDLED; } +static void append_basic_variant_dict_entry(DBusMessageIter *dict, const char* key, int variant_type_int, const char* variant_type_str, void* variant); +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 struct spa_bt_remote_endpoint *remote_endpoint_find(struct spa_bt_monitor *monitor, const char *path); + +static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMessage *m, void *userdata) +{ + struct spa_bt_monitor *monitor = userdata; + const char *path; + const char *object_path; + DBusMessageIter args, props, iter; + DBusMessage *r = NULL; + int size, res; + const struct media_codec *codec; + bool sink; + + if (!dbus_message_iter_init(m, &args) || !spa_streq(dbus_message_get_signature(m), "a{sv}")) { + spa_log_error(monitor->log, "Invalid signature for method SelectProperties()"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + dbus_message_iter_recurse(&args, &props); + if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + path = dbus_message_get_path(m); + + codec = media_endpoint_to_codec(monitor, path, &sink); + if (!codec) { + res = -ENOTSUP; + spa_log_error(monitor->log, "Unsupported codec: %d (%s)", + res, spa_strerror(res)); + if ((r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", + "Unsupported codec")) == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + goto exit_send; + } + + /* Read transport properties */ + while (dbus_message_iter_get_arg_type(&props) == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter value, entry; + int var; + + dbus_message_iter_recurse(&props, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + var = dbus_message_iter_get_arg_type(&value); + + if (spa_streq(key, "Capabilities")) { + DBusMessageIter array, dict; + uint8_t config[A2DP_MAX_CAPS_SIZE], *cap; + uint8_t *pconf = (uint8_t *) config; + + if (r) { + spa_log_warn(monitor->log, "Multiple Capabilities entries, skipped"); + goto next_entry; + } + + if (var != DBUS_TYPE_ARRAY) { + spa_log_error(monitor->log, "Property %s of wrong type %c", key, (char)var); + if ((r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", + "Invalid property")) == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + goto exit_send; + } + + dbus_message_iter_recurse(&value, &array); + var = dbus_message_iter_get_arg_type(&array); + if (var != DBUS_TYPE_BYTE) { + spa_log_error(monitor->log, "%s is an array of wrong type %c", key, (char)var); + if ((r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", + "Invalid property")) == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + goto exit_send; + } + + dbus_message_iter_get_fixed_array(&array, &cap, &size); + spa_log_info(monitor->log, "%p: %s select properties %d", monitor, path, size); + spa_log_hexdump(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', cap, (size_t)size); + + /* 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); + + if (res < 0 || res != size) { + spa_log_error(monitor->log, "can't select config: %d (%s)", + res, spa_strerror(res)); + if ((r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", + "Unable to select properties")) == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + goto exit_send; + } + spa_log_info(monitor->log, "%p: selected conf %d", monitor, size); + spa_log_hexdump(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', pconf, (size_t)size); + + if ((r = dbus_message_new_method_return(m)) == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + dbus_message_iter_init_append(r, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &dict); + append_basic_array_variant_dict_entry(&dict, "Capabilities", "ay", "y", DBUS_TYPE_BYTE, &config, size); + + if (codec->get_qos) + { + struct codec_qos qos; + dbus_bool_t framing; + + codec->get_qos(codec, config, size, &qos); + + append_basic_variant_dict_entry(&dict, "Interval", DBUS_TYPE_UINT32, "u", &qos.interval); + framing = (qos.framing ? TRUE : FALSE); + append_basic_variant_dict_entry(&dict, "Framing", DBUS_TYPE_BOOLEAN, "b", &framing); + append_basic_variant_dict_entry(&dict, "PHY", DBUS_TYPE_STRING, "s", &qos.phy); + append_basic_variant_dict_entry(&dict, "SDU", DBUS_TYPE_UINT16, "q", &qos.sdu); + append_basic_variant_dict_entry(&dict, "Retransmissions", DBUS_TYPE_BYTE, "y", &qos.retransmission); + append_basic_variant_dict_entry(&dict, "Latency", DBUS_TYPE_UINT16, "q", &qos.latency); + append_basic_variant_dict_entry(&dict, "Delay", DBUS_TYPE_UINT32, "u", &qos.delay); + } + + dbus_message_iter_close_container(&iter, &dict); + } else if (spa_streq(key, "Endpoint")) { + dbus_message_iter_get_basic(&value, &object_path); + + if (codec->bap) { + struct spa_bt_remote_endpoint *ep; + + ep = remote_endpoint_find(monitor, object_path); + if (!ep) { + spa_log_warn(monitor->log, "Unable to find remote endpoint for %s", object_path); + goto next_entry; + } + + /* Call of SelectProperties means that local device acts as an initiator + * and therefor remote endpoint is an acceptor + */ + ep->acceptor = true; + } + } + +next_entry: + dbus_message_iter_next(&props); + } + +exit_send: + if (r) { + if (!dbus_connection_send(conn, r, NULL)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + dbus_message_unref(r); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + static struct spa_bt_adapter *adapter_find(struct spa_bt_monitor *monitor, const char *path) { struct spa_bt_adapter *d; @@ -676,6 +869,12 @@ static int adapter_update_props(struct spa_bt_adapter *adapter, if (profile && (adapter->profiles & profile) == 0) { spa_log_debug(monitor->log, "adapter %p: add UUID=%s", adapter, uuid); adapter->profiles |= profile; + } else if (strcasecmp(uuid, SPA_BT_UUID_PACS) == 0 && + (adapter->profiles & SPA_BT_PROFILE_BAP_SINK) == 0) { + spa_log_debug(monitor->log, "adapter %p: add UUID=%s", adapter, SPA_BT_UUID_BAP_SINK); + adapter->profiles |= SPA_BT_PROFILE_BAP_SINK; + spa_log_debug(monitor->log, "adapter %p: add UUID=%s", adapter, SPA_BT_UUID_BAP_SOURCE); + adapter->profiles |= SPA_BT_PROFILE_BAP_SOURCE; } dbus_message_iter_next(&iter); } @@ -750,7 +949,7 @@ static int adapter_init_modalias(struct spa_bt_monitor *monitor, struct spa_bt_a if (str == NULL) goto fail; snprintf(path, sizeof(path), "/sys/class/bluetooth/%s/device/modalias", str); - if ((f = fopen(path, "rb")) == NULL) { + if ((f = fopen(path, "rbe")) == NULL) { res = -errno; goto fail; } @@ -830,6 +1029,11 @@ static uint32_t adapter_connectable_profiles(struct spa_bt_adapter *adapter) if (profiles & SPA_BT_PROFILE_A2DP_SOURCE) mask |= SPA_BT_PROFILE_A2DP_SINK; + if (profiles & SPA_BT_PROFILE_BAP_SINK) + mask |= SPA_BT_PROFILE_BAP_SOURCE; + if (profiles & SPA_BT_PROFILE_BAP_SOURCE) + mask |= SPA_BT_PROFILE_BAP_SINK; + if (profiles & SPA_BT_PROFILE_HSP_AG) mask |= SPA_BT_PROFILE_HSP_HS; if (profiles & SPA_BT_PROFILE_HSP_HS) @@ -898,7 +1102,7 @@ static struct spa_bt_device *device_create(struct spa_bt_monitor *monitor, const static int device_stop_timer(struct spa_bt_device *device); -static void a2dp_codec_switch_free(struct spa_bt_a2dp_codec_switch *sw); +static void media_codec_switch_free(struct spa_bt_media_codec_switch *sw); static void device_clear_sub(struct spa_bt_device *device) { @@ -909,7 +1113,7 @@ static void device_clear_sub(struct spa_bt_device *device) static void device_free(struct spa_bt_device *device) { struct spa_bt_remote_endpoint *ep, *tep; - struct spa_bt_a2dp_codec_switch *sw; + struct spa_bt_media_codec_switch *sw; struct spa_bt_transport *t, *tt; struct spa_bt_monitor *monitor = device->monitor; @@ -939,7 +1143,7 @@ static void device_free(struct spa_bt_device *device) } spa_list_consume(sw, &device->codec_switch_list, device_link) - a2dp_codec_switch_free(sw); + media_codec_switch_free(sw); spa_list_remove(&device->link); free(device->path); @@ -1201,6 +1405,10 @@ static int reconnect_device_profiles(struct spa_bt_device *device) device_try_connect_profile(device, SPA_BT_UUID_A2DP_SINK); if (reconnect & SPA_BT_PROFILE_A2DP_SOURCE) device_try_connect_profile(device, SPA_BT_UUID_A2DP_SOURCE); + if (reconnect & SPA_BT_PROFILE_BAP_SINK) + device_try_connect_profile(device, SPA_BT_UUID_BAP_SINK); + if (reconnect & SPA_BT_PROFILE_BAP_SOURCE) + device_try_connect_profile(device, SPA_BT_UUID_BAP_SOURCE); return reconnect; } @@ -1288,8 +1496,8 @@ int spa_bt_device_check_profiles(struct spa_bt_device *device, bool force) uint32_t connectable_profiles = device->adapter ? adapter_connectable_profiles(device->adapter) : 0; uint32_t direction_masks[3] = { - SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_HEADSET_HEAD_UNIT, - SPA_BT_PROFILE_A2DP_SOURCE, + SPA_BT_PROFILE_MEDIA_SINK | SPA_BT_PROFILE_HEADSET_HEAD_UNIT, + SPA_BT_PROFILE_MEDIA_SOURCE, SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY, }; bool direction_connected = false; @@ -1340,9 +1548,9 @@ static void device_set_connected(struct spa_bt_device *device, int connected) spa_bt_device_check_profiles(device, false); else { /* Stop codec switch on disconnect */ - struct spa_bt_a2dp_codec_switch *sw; + struct spa_bt_media_codec_switch *sw; spa_list_consume(sw, &device->codec_switch_list, device_link) - a2dp_codec_switch_free(sw); + media_codec_switch_free(sw); if (device->reconnect_state != BT_DEVICE_RECONNECT_INIT) device_stop_timer(device); @@ -1507,12 +1715,12 @@ static int device_update_props(struct spa_bt_device *device, profile = spa_bt_profile_from_uuid(uuid); - /* Only add A2DP profiles if HSP/HFP backed is none. + /* Only add A2DP/BAP profiles if HSP/HFP backed is none. * This allows BT device to connect instantly instead of waiting for * profile timeout, because all available profiles are connected. */ if (monitor->backend_selection != BACKEND_NONE || (monitor->backend_selection == BACKEND_NONE && - profile & (SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_A2DP_SOURCE))) { + profile & (SPA_BT_PROFILE_MEDIA_SINK | SPA_BT_PROFILE_MEDIA_SOURCE))) { if (profile && (device->profiles & profile) == 0) { spa_log_debug(monitor->log, "device %p: add UUID=%s", device, uuid); device->profiles |= profile; @@ -1543,7 +1751,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_media_codec(struct spa_bt_device *device, const struct media_codec *codec, bool sink) { struct spa_bt_monitor *monitor = device->monitor; struct spa_bt_remote_endpoint *ep; @@ -1556,7 +1764,7 @@ bool spa_bt_device_supports_a2dp_codec(struct spa_bt_device *device, const struc }; size_t i; - if (!is_a2dp_codec_enabled(device->monitor, codec)) + if (!is_media_codec_enabled(device->monitor, codec)) return false; if (!device->adapter->application_registered) { @@ -1579,42 +1787,53 @@ 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) { - if (a2dp_codec_check_caps(codec, ep->codec, ep->capabilities, ep->capabilities_len, - &ep->monitor->default_audio_info)) + const enum spa_bt_profile profile = spa_bt_profile_from_uuid(ep->uuid); + enum spa_bt_profile expected; + + if (codec->bap) + expected = sink ? SPA_BT_PROFILE_BAP_SINK : SPA_BT_PROFILE_BAP_SOURCE; + else + expected = sink ? SPA_BT_PROFILE_A2DP_SINK : SPA_BT_PROFILE_A2DP_SOURCE; + + if (profile != expected) + continue; + + if (media_codec_check_caps(codec, ep->codec, ep->capabilities, ep->capabilities_len, + &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 media_codec **spa_bt_device_get_supported_media_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; - const struct a2dp_codec **supported_codecs; + const struct media_codec * const * const media_codecs = monitor->media_codecs; + const struct media_codec **supported_codecs; size_t i, j, size; *count = 0; size = 8; - supported_codecs = malloc(size * sizeof(const struct a2dp_codec *)); + supported_codecs = malloc(size * sizeof(const struct media_codec *)); if (supported_codecs == NULL) return NULL; j = 0; - for (i = 0; a2dp_codecs[i] != NULL; ++i) { - if (spa_bt_device_supports_a2dp_codec(device, a2dp_codecs[i])) { - supported_codecs[j] = a2dp_codecs[i]; + for (i = 0; media_codecs[i] != NULL; ++i) { + if (spa_bt_device_supports_media_codec(device, media_codecs[i], sink)) { + supported_codecs[j] = media_codecs[i]; ++j; } if (j >= size) { - const struct a2dp_codec **p; + const struct media_codec **p; size = size * 2; #ifdef HAVE_REALLOCARRRAY - p = reallocarray(supported_codecs, size, sizeof(const struct a2dp_codec *)); + p = reallocarray(supported_codecs, size, sizeof(const struct media_codec *)); #else - p = realloc(supported_codecs, size * sizeof(const struct a2dp_codec *)); + p = realloc(supported_codecs, size * sizeof(const struct media_codec *)); #endif if (p == NULL) { free(supported_codecs); @@ -1718,7 +1937,7 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en else if (spa_streq(key, "Capabilities")) { DBusMessageIter iter; uint8_t *value; - int i, len; + int len; if (!check_iter_signature(&it[1], "ay")) goto next; @@ -1727,8 +1946,7 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en dbus_message_iter_get_fixed_array(&iter, &value, &len); spa_log_debug(monitor->log, "remote_endpoint %p: %s=%d", remote_endpoint, key, len); - for (i = 0; i < len; i++) - spa_log_debug(monitor->log, " %d: %02x", i, value[i]); + spa_log_hexdump(monitor->log, SPA_LOG_LEVEL_DEBUG, 2, value, (size_t)len); free(remote_endpoint->capabilities); remote_endpoint->capabilities_len = 0; @@ -1819,6 +2037,7 @@ struct spa_bt_transport *spa_bt_transport_create(struct spa_bt_monitor *monitor, t->delay = SPA_BT_UNKNOWN_DELAY; t->user_data = SPA_PTROFF(t, sizeof(struct spa_bt_transport), void); spa_hook_list_init(&t->listener_list); + spa_list_init(&t->bap_transport_linked); spa_list_append(&monitor->transport_list, &t->link); @@ -1898,6 +2117,8 @@ void spa_bt_transport_free(struct spa_bt_transport *transport) if (device && device->connected_profiles != prev_connected) spa_bt_device_emit_profiles_changed(device, device->profiles, prev_connected); + spa_list_remove(&transport->bap_transport_linked); + free(transport->endpoint_path); free(transport->path); free(transport); @@ -2157,10 +2378,10 @@ int64_t spa_bt_transport_get_delay_nsec(struct spa_bt_transport *t) /* Fallback values when device does not provide information */ - if (t->a2dp_codec == NULL) + if (t->media_codec == NULL) return 30 * SPA_NSEC_PER_MSEC; - switch (t->a2dp_codec->id) { + switch (t->media_codec->id) { case SPA_BLUETOOTH_AUDIO_CODEC_SBC: case SPA_BLUETOOTH_AUDIO_CODEC_SBC_XQ: return 200 * SPA_NSEC_PER_MSEC; @@ -2176,6 +2397,7 @@ int64_t spa_bt_transport_get_delay_nsec(struct spa_bt_transport *t) case SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX: case SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM: case SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX: + case SPA_BLUETOOTH_AUDIO_CODEC_LC3: return 40 * SPA_NSEC_PER_MSEC; default: break; @@ -2216,6 +2438,12 @@ static int transport_update_props(struct spa_bt_transport *transport, case SPA_BT_PROFILE_A2DP_SINK: transport->profile = SPA_BT_PROFILE_A2DP_SOURCE; break; + case SPA_BT_PROFILE_BAP_SOURCE: + transport->profile = SPA_BT_PROFILE_BAP_SINK; + break; + case SPA_BT_PROFILE_BAP_SINK: + transport->profile = SPA_BT_PROFILE_BAP_SOURCE; + break; default: spa_log_warn(monitor->log, "unknown profile %s", value); break; @@ -2236,6 +2464,16 @@ static int transport_update_props(struct spa_bt_transport *transport, spa_log_warn(monitor->log, "could not find device %s", value); } } + else if (spa_streq(key, "Endpoint")) { + struct spa_bt_remote_endpoint *ep = remote_endpoint_find(monitor, value); + if (!ep) { + spa_log_warn(monitor->log, "Unable to find remote endpoint for %s", value); + goto next; + } + + // If the remote endpoint is an acceptor this transport is an initiator + transport->bap_initiator = ep->acceptor; + } } else if (spa_streq(key, "Codec")) { uint8_t value; @@ -2251,7 +2489,7 @@ static int transport_update_props(struct spa_bt_transport *transport, else if (spa_streq(key, "Configuration")) { DBusMessageIter iter; uint8_t *value; - int i, len; + int len; if (!check_iter_signature(&it[1], "ay")) goto next; @@ -2260,8 +2498,7 @@ static int transport_update_props(struct spa_bt_transport *transport, dbus_message_iter_get_fixed_array(&iter, &value, &len); spa_log_debug(monitor->log, "transport %p: %s=%d", transport, key, len); - for (i = 0; i < len; i++) - spa_log_debug(monitor->log, " %d: %02x", i, value[i]); + spa_log_hexdump(monitor->log, SPA_LOG_LEVEL_DEBUG, 2, value, (size_t)len); free(transport->configuration); transport->configuration_len = 0; @@ -2309,6 +2546,47 @@ static int transport_update_props(struct spa_bt_transport *transport, transport->delay = value; spa_bt_transport_emit_delay_changed(transport); } + else if (spa_streq(key, "PresentationDelay")) { + uint32_t value; + + if (type != DBUS_TYPE_UINT32) + goto next; + dbus_message_iter_get_basic(&it[1], &value); + + spa_log_debug(monitor->log, "transport %p: %s=%02x", transport, key, value); + + transport->delay = value / 100; + spa_bt_transport_emit_delay_changed(transport); + } + else if (spa_streq(key, "Links")) { + DBusMessageIter iter; + + if (!check_iter_signature(&it[1], "ao")) + goto next; + + dbus_message_iter_recurse(&it[1], &iter); + while (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INVALID) { + const char *transport_path; + struct spa_bt_transport *t; + + dbus_message_iter_get_basic(&iter, &transport_path); + + spa_log_debug(monitor->log, "transport %p: Linked with=%s", transport, transport_path); + t = spa_bt_transport_find(monitor, transport_path); + if (!t) { + spa_log_warn(monitor->log, "Unable to find linked transport"); + dbus_message_iter_next(&iter); + continue; + } + + if (spa_list_is_empty(&t->bap_transport_linked)) + spa_list_append(&transport->bap_transport_linked, &t->bap_transport_linked); + else if (spa_list_is_empty(&transport->bap_transport_linked)) + spa_list_append(&t->bap_transport_linked, &transport->bap_transport_linked); + + dbus_message_iter_next(&iter); + } + } next: dbus_message_iter_next(props_iter); } @@ -2392,10 +2670,27 @@ static int transport_acquire(void *data, bool optional) { struct spa_bt_transport *transport = data; struct spa_bt_monitor *monitor = transport->monitor; - DBusMessage *m, *r; + DBusMessage *m, *r = NULL; DBusError err; int ret = 0; const char *method = optional ? "TryAcquire" : "Acquire"; + struct spa_bt_transport *t_linked; + + /* For LE Audio, multiple transport from the same device may share the same + * stream (CIS) and group (CIG) but for different direction, e.g. a speaker and + * a microphone. In this case they are linked. + * If one of them has already been acquired this function should not call Acquire + * or TryAcquire but re-use values from the previously acquired transport. + */ + spa_list_for_each(t_linked, &transport->bap_transport_linked, bap_transport_linked) { + if (t_linked->acquired && t_linked->device == transport->device) { + transport->fd = t_linked->fd; + transport->read_mtu = t_linked->read_mtu; + transport->write_mtu = t_linked->write_mtu; + spa_log_debug(monitor->log, "transport %p: linked transport %s", transport, t_linked->path); + goto done; + } + } m = dbus_message_new_method_call(BLUEZ_SERVICE, transport->path, @@ -2439,6 +2734,7 @@ static int transport_acquire(void *data, bool optional) ret = -EIO; goto finish; } +done: spa_log_debug(monitor->log, "transport %p: %s %s, fd %d MTU %d:%d", transport, method, transport->path, transport->fd, transport->read_mtu, transport->write_mtu); @@ -2447,7 +2743,8 @@ static int transport_acquire(void *data, bool optional) transport_sync_volume(transport); finish: - dbus_message_unref(r); + if (r) + dbus_message_unref(r); return ret; } @@ -2458,12 +2755,30 @@ static int transport_release(void *data) DBusMessage *m, *r; DBusError err; bool is_idle = (transport->state == SPA_BT_TRANSPORT_STATE_IDLE); + struct spa_bt_transport *t_linked; + bool linked = false; spa_log_debug(monitor->log, "transport %p: Release %s", transport, transport->path); spa_bt_player_set_state(transport->device->adapter->dummy_player, SPA_BT_PLAYER_STOPPED); + /* For LE Audio, multiple transport stream (CIS) can be linked together (CIG). + * If they are part of the same device they re-use the same fd, and call to + * release should be done for the last one only. + */ + spa_list_for_each(t_linked, &transport->bap_transport_linked, bap_transport_linked) { + if (t_linked->acquired && t_linked->device == transport->device) { + linked = true; + break; + } + } + if (linked) { + spa_log_info(monitor->log, "Linked transport %s released", transport->path); + transport->fd = -1; + return 0; + } + close(transport->fd); transport->fd = -1; @@ -2511,23 +2826,21 @@ 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 media_codec_switch_reply(DBusPendingCall *pending, void *userdata); -static void a2dp_codec_switch_reply(DBusPendingCall *pending, void *userdata); +static int media_codec_switch_cmp(const void *a, const void *b); -static int a2dp_codec_switch_cmp(const void *a, const void *b); +static struct spa_bt_media_codec_switch *media_codec_switch_cmp_sw; /* global for qsort */ -static struct spa_bt_a2dp_codec_switch *a2dp_codec_switch_cmp_sw; /* global for qsort */ +static int media_codec_switch_start_timer(struct spa_bt_media_codec_switch *sw, uint64_t timeout); -static int a2dp_codec_switch_start_timer(struct spa_bt_a2dp_codec_switch *sw, uint64_t timeout); +static int media_codec_switch_stop_timer(struct spa_bt_media_codec_switch *sw); -static int a2dp_codec_switch_stop_timer(struct spa_bt_a2dp_codec_switch *sw); - -static void a2dp_codec_switch_free(struct spa_bt_a2dp_codec_switch *sw) +static void media_codec_switch_free(struct spa_bt_media_codec_switch *sw) { char **p; - a2dp_codec_switch_stop_timer(sw); + media_codec_switch_stop_timer(sw); if (sw->pending != NULL) { dbus_pending_call_cancel(sw->pending); @@ -2546,7 +2859,7 @@ static void a2dp_codec_switch_free(struct spa_bt_a2dp_codec_switch *sw) free(sw); } -static void a2dp_codec_switch_next(struct spa_bt_a2dp_codec_switch *sw) +static void media_codec_switch_next(struct spa_bt_media_codec_switch *sw) { spa_assert(*sw->codec_iter != NULL && *sw->path_iter != NULL); @@ -2559,60 +2872,62 @@ static void a2dp_codec_switch_next(struct spa_bt_a2dp_codec_switch *sw) sw->retries = CODEC_SWITCH_RETRIES; } -static bool a2dp_codec_switch_process_current(struct spa_bt_a2dp_codec_switch *sw) +static bool media_codec_switch_process_current(struct spa_bt_media_codec_switch *sw) { struct spa_bt_remote_endpoint *ep; struct spa_bt_transport *t; - const struct a2dp_codec *codec; + const struct media_codec *codec; uint8_t config[A2DP_MAX_CAPS_SIZE]; - char *local_endpoint_base; + enum spa_bt_media_direction direction; 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 */ codec = *sw->codec_iter; - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: consider codec %s for remote endpoint %s", + spa_log_debug(sw->device->monitor->log, "media codec switch %p: consider codec %s for remote endpoint %s", sw, (*sw->codec_iter)->name, *sw->path_iter); ep = device_remote_endpoint_find(sw->device, *sw->path_iter); if (ep == NULL || ep->capabilities == NULL || ep->uuid == NULL) { - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: endpoint %s not valid, try next", + spa_log_debug(sw->device->monitor->log, "media codec switch %p: endpoint %s not valid, try next", sw, *sw->path_iter); goto next; } /* Setup and check compatible configuration */ if (ep->codec != codec->codec_id) { - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: different codec, try next", sw); + spa_log_debug(sw->device->monitor->log, "media codec switch %p: different codec, try next", sw); goto next; } if (!(sw->profile & spa_bt_profile_from_uuid(ep->uuid))) { - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: wrong uuid (%s) for profile, try next", + spa_log_debug(sw->device->monitor->log, "media codec switch %p: wrong uuid (%s) for profile, try next", sw, ep->uuid); goto next; } - if (sw->profile & SPA_BT_PROFILE_A2DP_SINK) { - local_endpoint_base = A2DP_SOURCE_ENDPOINT; - } else if (sw->profile & SPA_BT_PROFILE_A2DP_SOURCE) { - local_endpoint_base = A2DP_SINK_ENDPOINT; + if ((sw->profile & SPA_BT_PROFILE_A2DP_SINK) || (sw->profile & SPA_BT_PROFILE_BAP_SINK) ) { + direction = SPA_BT_MEDIA_SOURCE; + sink = false; + } else if ((sw->profile & SPA_BT_PROFILE_A2DP_SOURCE) || (sw->profile & SPA_BT_PROFILE_BAP_SOURCE) ) { + direction = SPA_BT_MEDIA_SINK; + sink = true; } else { - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: bad profile (%d), try next", + spa_log_debug(sw->device->monitor->log, "media codec switch %p: bad profile (%d), try next", sw, sw->profile); goto next; } - if (a2dp_codec_to_endpoint(codec, local_endpoint_base, &local_endpoint) < 0) { - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: no endpoint for codec %s, try next", + if (media_codec_to_endpoint(codec, direction, &local_endpoint) < 0) { + spa_log_debug(sw->device->monitor->log, "media codec switch %p: no endpoint for codec %s, try next", sw, codec->name); goto next; } @@ -2624,59 +2939,58 @@ static bool a2dp_codec_switch_process_current(struct spa_bt_a2dp_codec_switch *s if (t->device->adapter != sw->device->adapter) continue; if (spa_streq(t->endpoint_path, local_endpoint)) { - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: endpoint %s in use, try next", + spa_log_debug(sw->device->monitor->log, "media codec switch %p: endpoint %s in use, try next", sw, local_endpoint); goto next; } } - res = codec->select_config(codec, 0, ep->capabilities, ep->capabilities_len, + res = codec->select_config(codec, sink ? MEDIA_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", + spa_log_debug(sw->device->monitor->log, "media codec switch %p: incompatible capabilities (%d), try next", sw, res); goto next; } config_size = res; - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: configuration %d", sw, config_size); + spa_log_debug(sw->device->monitor->log, "media codec switch %p: configuration %d", sw, config_size); for (i = 0; i < config_size; i++) - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: %d: %02x", sw, i, config[i]); + spa_log_debug(sw->device->monitor->log, "media codec switch %p: %d: %02x", sw, i, config[i]); /* org.bluez.MediaEndpoint1.SetConfiguration on remote endpoint */ m = dbus_message_new_method_call(BLUEZ_SERVICE, ep->path, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SetConfiguration"); if (m == NULL) { - spa_log_debug(sw->device->monitor->log, "a2dp codec switch %p: dbus allocation failure, try next", sw); + spa_log_debug(sw->device->monitor->log, "media codec switch %p: dbus allocation failure, try next", sw); goto next; } spa_bt_device_update_last_bluez_action_time(sw->device); - spa_log_info(sw->device->monitor->log, "a2dp codec switch %p: trying codec %s for endpoint %s, local endpoint %s", + spa_log_info(sw->device->monitor->log, "media codec switch %p: trying codec %s for endpoint %s, local endpoint %s", sw, codec->name, ep->path, local_endpoint); 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); dbus_ret = dbus_connection_send_with_reply(sw->device->monitor->conn, m, &sw->pending, -1); if (!dbus_ret || sw->pending == NULL) { - spa_log_error(sw->device->monitor->log, "a2dp codec switch %p: dbus call failure, try next", sw); + spa_log_error(sw->device->monitor->log, "media codec switch %p: dbus call failure, try next", sw); dbus_message_unref(m); goto next; } - dbus_ret = dbus_pending_call_set_notify(sw->pending, a2dp_codec_switch_reply, sw, NULL); + dbus_ret = dbus_pending_call_set_notify(sw->pending, media_codec_switch_reply, sw, NULL); dbus_message_unref(m); if (!dbus_ret) { - spa_log_error(sw->device->monitor->log, "a2dp codec switch %p: dbus set notify failure", sw); + spa_log_error(sw->device->monitor->log, "media codec switch %p: dbus set notify failure", sw); goto next; } @@ -2688,7 +3002,7 @@ next: return false; } -static void a2dp_codec_switch_process(struct spa_bt_a2dp_codec_switch *sw) +static void media_codec_switch_process(struct spa_bt_media_codec_switch *sw) { while (*sw->codec_iter != NULL && *sw->path_iter != NULL) { struct timespec ts; @@ -2700,60 +3014,60 @@ static void a2dp_codec_switch_process(struct spa_bt_a2dp_codec_switch *sw) threshold = sw->device->last_bluez_action_time + BLUEZ_ACTION_RATE_MSEC * SPA_NSEC_PER_MSEC; if (now < threshold) { /* Wait for timeout */ - a2dp_codec_switch_start_timer(sw, threshold - now); + media_codec_switch_start_timer(sw, threshold - now); return; } if (sw->path_iter == sw->paths && (*sw->codec_iter)->caps_preference_cmp) { /* Sort endpoints according to codec preference, when at a new codec. */ - a2dp_codec_switch_cmp_sw = sw; - qsort(sw->paths, sw->num_paths, sizeof(char *), a2dp_codec_switch_cmp); + media_codec_switch_cmp_sw = sw; + qsort(sw->paths, sw->num_paths, sizeof(char *), media_codec_switch_cmp); } - if (a2dp_codec_switch_process_current(sw)) { + if (media_codec_switch_process_current(sw)) { /* Wait for dbus reply */ return; } - a2dp_codec_switch_next(sw); + media_codec_switch_next(sw); }; /* Didn't find any suitable endpoint. Report failure. */ - spa_log_info(sw->device->monitor->log, "a2dp codec switch %p: failed to get an endpoint", sw); + spa_log_info(sw->device->monitor->log, "media codec switch %p: failed to get an endpoint", sw); spa_bt_device_emit_codec_switched(sw->device, -ENODEV); spa_bt_device_check_profiles(sw->device, false); - a2dp_codec_switch_free(sw); + media_codec_switch_free(sw); } -static bool a2dp_codec_switch_goto_active(struct spa_bt_a2dp_codec_switch *sw) +static bool media_codec_switch_goto_active(struct spa_bt_media_codec_switch *sw) { struct spa_bt_device *device = sw->device; - struct spa_bt_a2dp_codec_switch *active_sw; + struct spa_bt_media_codec_switch *active_sw; - active_sw = spa_list_first(&device->codec_switch_list, struct spa_bt_a2dp_codec_switch, device_link); + active_sw = spa_list_first(&device->codec_switch_list, struct spa_bt_media_codec_switch, device_link); if (active_sw != sw) { - struct spa_bt_a2dp_codec_switch *t; + struct spa_bt_media_codec_switch *t; /* This codec switch has been canceled. Switch to the newest one. */ spa_log_debug(sw->device->monitor->log, - "a2dp codec switch %p: canceled, go to new switch", sw); + "media codec switch %p: canceled, go to new switch", sw); spa_list_for_each_safe(sw, t, &device->codec_switch_list, device_link) { if (sw != active_sw) - a2dp_codec_switch_free(sw); + media_codec_switch_free(sw); } - a2dp_codec_switch_process(active_sw); + media_codec_switch_process(active_sw); return false; } return true; } -static void a2dp_codec_switch_timer_event(struct spa_source *source) +static void media_codec_switch_timer_event(struct spa_source *source) { - struct spa_bt_a2dp_codec_switch *sw = source->data; + struct spa_bt_media_codec_switch *sw = source->data; struct spa_bt_device *device = sw->device; struct spa_bt_monitor *monitor = device->monitor; uint64_t exp; @@ -2761,19 +3075,19 @@ static void a2dp_codec_switch_timer_event(struct spa_source *source) if (spa_system_timerfd_read(monitor->main_system, source->fd, &exp) < 0) spa_log_warn(monitor->log, "error reading timerfd: %s", strerror(errno)); - spa_log_debug(monitor->log, "a2dp codec switch %p: rate limit timer event", sw); + spa_log_debug(monitor->log, "media codec switch %p: rate limit timer event", sw); - a2dp_codec_switch_stop_timer(sw); + media_codec_switch_stop_timer(sw); - if (!a2dp_codec_switch_goto_active(sw)) + if (!media_codec_switch_goto_active(sw)) return; - a2dp_codec_switch_process(sw); + media_codec_switch_process(sw); } -static void a2dp_codec_switch_reply(DBusPendingCall *pending, void *user_data) +static void media_codec_switch_reply(DBusPendingCall *pending, void *user_data) { - struct spa_bt_a2dp_codec_switch *sw = user_data; + struct spa_bt_media_codec_switch *sw = user_data; struct spa_bt_device *device = sw->device; DBusMessage *r; @@ -2785,7 +3099,7 @@ static void a2dp_codec_switch_reply(DBusPendingCall *pending, void *user_data) spa_bt_device_update_last_bluez_action_time(device); - if (!a2dp_codec_switch_goto_active(sw)) { + if (!media_codec_switch_goto_active(sw)) { if (r != NULL) dbus_message_unref(r); return; @@ -2793,14 +3107,14 @@ static void a2dp_codec_switch_reply(DBusPendingCall *pending, void *user_data) if (r == NULL) { spa_log_error(sw->device->monitor->log, - "a2dp codec switch %p: empty reply from dbus, trying next", + "media codec switch %p: empty reply from dbus, trying next", sw); goto next; } if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_debug(sw->device->monitor->log, - "a2dp codec switch %p: failed (%s), trying next", + "media codec switch %p: failed (%s), trying next", sw, dbus_message_get_error_name(r)); dbus_message_unref(r); goto next; @@ -2809,34 +3123,34 @@ static void a2dp_codec_switch_reply(DBusPendingCall *pending, void *user_data) dbus_message_unref(r); /* Success */ - spa_log_info(sw->device->monitor->log, "a2dp codec switch %p: success", sw); + spa_log_info(sw->device->monitor->log, "media codec switch %p: success", sw); spa_bt_device_emit_codec_switched(sw->device, 0); spa_bt_device_check_profiles(sw->device, false); - a2dp_codec_switch_free(sw); + media_codec_switch_free(sw); return; next: if (sw->retries > 0) --sw->retries; else - a2dp_codec_switch_next(sw); + media_codec_switch_next(sw); - a2dp_codec_switch_process(sw); + media_codec_switch_process(sw); return; } -static int a2dp_codec_switch_start_timer(struct spa_bt_a2dp_codec_switch *sw, uint64_t timeout) +static int media_codec_switch_start_timer(struct spa_bt_media_codec_switch *sw, uint64_t timeout) { struct spa_bt_monitor *monitor = sw->device->monitor; struct itimerspec ts; spa_assert(sw->timer.data == NULL); - spa_log_debug(monitor->log, "a2dp codec switch %p: starting rate limit timer", sw); + spa_log_debug(monitor->log, "media codec switch %p: starting rate limit timer", sw); if (sw->timer.data == NULL) { sw->timer.data = sw; - sw->timer.func = a2dp_codec_switch_timer_event; + sw->timer.func = media_codec_switch_timer_event; sw->timer.fd = spa_system_timerfd_create(monitor->main_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); sw->timer.mask = SPA_IO_IN; @@ -2851,7 +3165,7 @@ static int a2dp_codec_switch_start_timer(struct spa_bt_a2dp_codec_switch *sw, ui return 0; } -static int a2dp_codec_switch_stop_timer(struct spa_bt_a2dp_codec_switch *sw) +static int media_codec_switch_stop_timer(struct spa_bt_media_codec_switch *sw) { struct spa_bt_monitor *monitor = sw->device->monitor; struct itimerspec ts; @@ -2859,7 +3173,7 @@ static int a2dp_codec_switch_stop_timer(struct spa_bt_a2dp_codec_switch *sw) if (sw->timer.data == NULL) return 0; - spa_log_debug(monitor->log, "a2dp codec switch %p: stopping rate limit timer", sw); + spa_log_debug(monitor->log, "media codec switch %p: stopping rate limit timer", sw); spa_loop_remove_source(monitor->main_loop, &sw->timer); ts.it_value.tv_sec = 0; @@ -2872,12 +3186,13 @@ static int a2dp_codec_switch_stop_timer(struct spa_bt_a2dp_codec_switch *sw) return 0; } -static int a2dp_codec_switch_cmp(const void *a, const void *b) +static int media_codec_switch_cmp(const void *a, const void *b) { - struct spa_bt_a2dp_codec_switch *sw = a2dp_codec_switch_cmp_sw; - const struct a2dp_codec *codec = *sw->codec_iter; + struct spa_bt_media_codec_switch *sw = media_codec_switch_cmp_sw; + const struct media_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 +3201,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,17 +3213,23 @@ 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); + if (codec->bap) + flags = spa_streq(ep1->uuid, SPA_BT_UUID_BAP_SOURCE) ? MEDIA_CODEC_FLAG_SINK : 0; + else + flags = spa_streq(ep1->uuid, SPA_BT_UUID_A2DP_SOURCE) ? MEDIA_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 */ -int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a2dp_codec * const *codecs) +int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct media_codec * const *codecs) { - struct spa_bt_a2dp_codec_switch *sw; + struct spa_bt_media_codec_switch *sw; struct spa_bt_remote_endpoint *ep; struct spa_bt_transport *t; - const struct a2dp_codec *preferred_codec = NULL; + const struct media_codec *preferred_codec = NULL; size_t i, j, num_codecs, num_eps; if (!device->adapter->application_registered) { @@ -2913,7 +3238,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_media_codec(device, codecs[i], true)) { preferred_codec = codecs[i]; break; } @@ -2925,7 +3250,7 @@ int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a */ if (spa_list_is_empty(&device->codec_switch_list) && preferred_codec != NULL) { spa_list_for_each(t, &device->transport_list, device_link) { - if (t->a2dp_codec != preferred_codec) + if (t->media_codec != preferred_codec) continue; if ((device->connected_profiles & t->profile) != t->profile) @@ -2938,7 +3263,7 @@ int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a /* Setup and start iteration */ - sw = calloc(1, sizeof(struct spa_bt_a2dp_codec_switch)); + sw = calloc(1, sizeof(struct spa_bt_media_codec_switch)); if (sw == NULL) return -ENOMEM; @@ -2950,17 +3275,17 @@ int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a while (codecs[num_codecs] != NULL) ++num_codecs; - sw->codecs = calloc(num_codecs + 1, sizeof(const struct a2dp_codec *)); + sw->codecs = calloc(num_codecs + 1, sizeof(const struct media_codec *)); sw->paths = calloc(num_eps + 1, sizeof(char *)); sw->num_paths = num_eps; if (sw->codecs == NULL || sw->paths == NULL) { - a2dp_codec_switch_free(sw); + media_codec_switch_free(sw); return -ENOMEM; } for (i = 0, j = 0; i < num_codecs; ++i) { - if (is_a2dp_codec_enabled(device->monitor, codecs[i])) { + if (is_media_codec_enabled(device->monitor, codecs[i])) { sw->codecs[j] = codecs[i]; ++j; } @@ -2971,7 +3296,7 @@ int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a spa_list_for_each(ep, &device->remote_endpoint_list, device_link) { sw->paths[i] = strdup(ep->path); if (sw->paths[i] == NULL) { - a2dp_codec_switch_free(sw); + media_codec_switch_free(sw); return -ENOMEM; } ++i; @@ -2997,13 +3322,13 @@ int spa_bt_device_ensure_a2dp_codec(struct spa_bt_device *device, const struct a * to wait to pass in any case, so we don't cancel it either. */ spa_log_debug(sw->device->monitor->log, - "a2dp codec switch %p: already in progress, canceling previous", + "media codec switch %p: already in progress, canceling previous", sw); spa_list_prepend(&device->codec_switch_list, &sw->device_link); } else { spa_list_prepend(&device->codec_switch_list, &sw->device_link); - a2dp_codec_switch_process(sw); + media_codec_switch_process(sw); } return 0; @@ -3029,8 +3354,9 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, DBusMessageIter it[2]; DBusMessage *r; struct spa_bt_transport *transport; - const struct a2dp_codec *codec; + const struct media_codec *codec; int profile; + bool sink; if (!dbus_message_has_signature(m, "oa{sv}")) { spa_log_warn(monitor->log, "invalid SetConfiguration() signature"); @@ -3038,8 +3364,8 @@ 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); + profile = media_endpoint_to_profile(endpoint); + codec = media_endpoint_to_codec(monitor, endpoint, &sink); if (codec == NULL) { spa_log_warn(monitor->log, "unknown SetConfiguration() codec"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; @@ -3080,7 +3406,7 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, free(transport->endpoint_path); transport->endpoint_path = strdup(endpoint); transport->profile = profile; - transport->a2dp_codec = codec; + transport->media_codec = codec; transport_update_props(transport, &it[1], NULL); if (transport->device == NULL || transport->device->adapter == NULL) { @@ -3100,7 +3426,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 ? MEDIA_CODEC_FLAG_SINK : 0, transport->configuration, transport->configuration_len, &info) < 0) { spa_log_error(monitor->log, "invalid transport configuration"); @@ -3222,6 +3548,8 @@ static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, voi res = endpoint_set_configuration(c, path, m, userdata); else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SelectConfiguration")) res = endpoint_select_configuration(c, m, userdata); + else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SelectProperties")) + res = endpoint_select_properties(c, m, userdata); else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "ClearConfiguration")) res = endpoint_clear_configuration(c, m, userdata); else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "Release")) @@ -3256,10 +3584,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 +3595,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); @@ -3281,42 +3609,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; + const char *path, enum spa_bt_media_direction direction, + const char *uuid, const struct media_codec *codec) { + 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 = (direction == SPA_BT_MEDIA_SINK); - ret = a2dp_codec_to_endpoint(codec, endpoint, &object_path); + ret = media_codec_to_endpoint(codec, direction, &object_path); if (ret < 0) - return ret; + goto error; - caps_size = codec->fill_caps(codec, 0, caps); - if (caps_size < 0) - return caps_size; + ret = caps_size = codec->fill_caps(codec, sink ? MEDIA_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,10 +3655,14 @@ 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, - const struct a2dp_codec *codec, const char *endpoint) +static int register_media_endpoint(struct spa_bt_monitor *monitor, + const struct media_codec *codec, enum spa_bt_media_direction direction) { int ret; char* object_path = NULL; @@ -3338,7 +3670,7 @@ static int register_a2dp_endpoint(struct spa_bt_monitor *monitor, .message_function = endpoint_handler, }; - ret = a2dp_codec_to_endpoint(codec, endpoint, &object_path); + ret = media_codec_to_endpoint(codec, direction, &object_path); if (ret < 0) return ret; @@ -3359,7 +3691,7 @@ static int register_a2dp_endpoint(struct spa_bt_monitor *monitor, static int adapter_register_endpoints(struct spa_bt_adapter *a) { struct spa_bt_monitor *monitor = a->monitor; - const struct a2dp_codec * const * const a2dp_codecs = monitor->a2dp_codecs; + const struct media_codec * const * const media_codecs = monitor->media_codecs; int i; int err = 0; @@ -3370,28 +3702,28 @@ static int adapter_register_endpoints(struct spa_bt_adapter *a) * It doesn't make sense to register codecs other than SBC * as bluez5 will probably use SBC anyway and we have no control over it * let's incentivize users to upgrade their bluez5 daemon - * if they want proper a2dp codec support + * if they want proper media codec support * */ spa_log_warn(monitor->log, "Using legacy bluez5 API for A2DP - only SBC will be supported. " "Please upgrade bluez5."); - for (i = 0; a2dp_codecs[i]; i++) { - const struct a2dp_codec *codec = a2dp_codecs[i]; + for (i = 0; media_codecs[i]; i++) { + const struct media_codec *codec = media_codecs[i]; - if (!is_a2dp_codec_enabled(monitor, codec)) + if (!is_media_codec_enabled(monitor, codec)) continue; if (!(codec->codec_id == A2DP_CODEC_SBC && spa_streq(codec->name, "sbc"))) continue; if ((err = bluez_register_endpoint(monitor, a->path, - A2DP_SOURCE_ENDPOINT, + SPA_BT_MEDIA_SOURCE, SPA_BT_UUID_A2DP_SOURCE, codec))) goto out; if ((err = bluez_register_endpoint(monitor, a->path, - A2DP_SINK_ENDPOINT, + SPA_BT_MEDIA_SINK, SPA_BT_UUID_A2DP_SINK, codec))) goto out; @@ -3413,10 +3745,9 @@ static int adapter_register_endpoints(struct spa_bt_adapter *a) return err; } -static void append_a2dp_object(DBusMessageIter *iter, const char *endpoint, +static void append_media_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 +3762,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); @@ -3452,7 +3779,7 @@ static void append_a2dp_object(DBusMessageIter *iter, const char *endpoint, static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage *m, void *user_data) { struct spa_bt_monitor *monitor = user_data; - const struct a2dp_codec * const * const a2dp_codecs = monitor->a2dp_codecs; + const struct media_codec * const * const media_codecs = monitor->media_codecs; const char *path, *interface, *member; char *endpoint; DBusMessage *r; @@ -3486,34 +3813,56 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage * dbus_message_iter_init_append(r, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{oa{sa{sv}}}", &array); - for (i = 0; a2dp_codecs[i]; i++) { - const struct a2dp_codec *codec = a2dp_codecs[i]; + for (i = 0; media_codecs[i]; i++) { + const struct media_codec *codec = media_codecs[i]; uint8_t caps[A2DP_MAX_CAPS_SIZE]; int caps_size, ret; uint16_t codec_id = codec->codec_id; - if (!is_a2dp_codec_enabled(monitor, codec)) + if (!is_media_codec_enabled(monitor, codec)) continue; caps_size = codec->fill_caps(codec, 0, caps); if (caps_size < 0) continue; + if (codec->bap && !monitor->le_audio_supported) { + /* The legacy bluez5 api doesn't support LE Audio + * It doesn't make sense to register unsupported codecs as it prevents + * registration of A2DP codecs + * let's incentivize users to upgrade their bluez5 daemon + * if they want proper media codec support + * */ + spa_log_warn(monitor->log, "Trying to use legacy bluez5 API for LE Audio - only A2DP will be supported. " + "Please upgrade bluez5."); + continue; + } + if (codec->decode != NULL) { - ret = a2dp_codec_to_endpoint(codec, A2DP_SINK_ENDPOINT, &endpoint); + caps_size = codec->fill_caps(codec, MEDIA_CODEC_FLAG_SINK, caps); + if (caps_size < 0) + continue; + + ret = media_codec_to_endpoint(codec, SPA_BT_MEDIA_SINK, &endpoint); if (ret == 0) { - spa_log_info(monitor->log, "register A2DP sink codec %s: %s", a2dp_codecs[i]->name, endpoint); - append_a2dp_object(&array, endpoint, SPA_BT_UUID_A2DP_SINK, + spa_log_info(monitor->log, "register media sink codec %s: %s", media_codecs[i]->name, endpoint); + append_media_object(&array, endpoint, + codec->bap ? SPA_BT_UUID_BAP_SINK : SPA_BT_UUID_A2DP_SINK, codec_id, caps, caps_size); free(endpoint); } } if (codec->encode != NULL) { - ret = a2dp_codec_to_endpoint(codec, A2DP_SOURCE_ENDPOINT, &endpoint); + caps_size = codec->fill_caps(codec, 0, caps); + if (caps_size < 0) + continue; + + ret = media_codec_to_endpoint(codec, SPA_BT_MEDIA_SOURCE, &endpoint); if (ret == 0) { - spa_log_info(monitor->log, "register A2DP source codec %s: %s", a2dp_codecs[i]->name, endpoint); - append_a2dp_object(&array, endpoint, SPA_BT_UUID_A2DP_SOURCE, + spa_log_info(monitor->log, "register media source codec %s: %s", media_codecs[i]->name, endpoint); + append_media_object(&array, endpoint, + codec->bap ? SPA_BT_UUID_BAP_SOURCE : SPA_BT_UUID_A2DP_SOURCE, codec_id, caps, caps_size); free(endpoint); } @@ -3566,28 +3915,28 @@ finish: static int register_media_application(struct spa_bt_monitor * monitor) { - const struct a2dp_codec * const * const a2dp_codecs = monitor->a2dp_codecs; + const struct media_codec * const * const media_codecs = monitor->media_codecs; const DBusObjectPathVTable vtable_object_manager = { .message_function = object_manager_handler, }; - spa_log_info(monitor->log, "Registering media application object: " A2DP_OBJECT_MANAGER_PATH); + spa_log_info(monitor->log, "Registering media application object: " MEDIA_OBJECT_MANAGER_PATH); if (!dbus_connection_register_object_path(monitor->conn, - A2DP_OBJECT_MANAGER_PATH, + MEDIA_OBJECT_MANAGER_PATH, &vtable_object_manager, monitor)) return -EIO; - for (int i = 0; a2dp_codecs[i]; i++) { - const struct a2dp_codec *codec = a2dp_codecs[i]; + for (int i = 0; media_codecs[i]; i++) { + const struct media_codec *codec = media_codecs[i]; - if (!is_a2dp_codec_enabled(monitor, codec)) + if (!is_media_codec_enabled(monitor, codec)) continue; if (codec->encode != NULL) - register_a2dp_endpoint(monitor, codec, A2DP_SOURCE_ENDPOINT); + register_media_endpoint(monitor, codec, SPA_BT_MEDIA_SOURCE); if (codec->decode != NULL) - register_a2dp_endpoint(monitor, codec, A2DP_SINK_ENDPOINT); + register_media_endpoint(monitor, codec, SPA_BT_MEDIA_SINK); } return 0; @@ -3595,34 +3944,34 @@ static int register_media_application(struct spa_bt_monitor * monitor) static void unregister_media_application(struct spa_bt_monitor * monitor) { - const struct a2dp_codec * const * const a2dp_codecs = monitor->a2dp_codecs; + const struct media_codec * const * const media_codecs = monitor->media_codecs; int ret; char *object_path = NULL; - for (int i = 0; a2dp_codecs[i]; i++) { - const struct a2dp_codec *codec = a2dp_codecs[i]; + for (int i = 0; media_codecs[i]; i++) { + const struct media_codec *codec = media_codecs[i]; - if (!is_a2dp_codec_enabled(monitor, codec)) + if (!is_media_codec_enabled(monitor, codec)) continue; - ret = a2dp_codec_to_endpoint(codec, A2DP_SOURCE_ENDPOINT, &object_path); + ret = media_codec_to_endpoint(codec, SPA_BT_MEDIA_SOURCE, &object_path); if (ret == 0) { dbus_connection_unregister_object_path(monitor->conn, object_path); free(object_path); } - ret = a2dp_codec_to_endpoint(codec, A2DP_SINK_ENDPOINT, &object_path); + ret = media_codec_to_endpoint(codec, SPA_BT_MEDIA_SINK, &object_path); if (ret == 0) { dbus_connection_unregister_object_path(monitor->conn, object_path); free(object_path); } } - dbus_connection_unregister_object_path(monitor->conn, A2DP_OBJECT_MANAGER_PATH); + dbus_connection_unregister_object_path(monitor->conn, MEDIA_OBJECT_MANAGER_PATH); } static int adapter_register_application(struct spa_bt_adapter *a) { - const char *object_manager_path = A2DP_OBJECT_MANAGER_PATH; + const char *object_manager_path = MEDIA_OBJECT_MANAGER_PATH; struct spa_bt_monitor *monitor = a->monitor; DBusMessage *m; DBusMessageIter i, d; @@ -3718,6 +4067,48 @@ static void reselect_backend(struct spa_bt_monitor *monitor, bool silent) backend ? backend->name : "none"); } +static int media_update_props(struct spa_bt_monitor *monitor, + DBusMessageIter *props_iter, + DBusMessageIter *invalidated_iter) +{ + while (dbus_message_iter_get_arg_type(props_iter) != DBUS_TYPE_INVALID) { + DBusMessageIter it[2]; + const char *key; + + dbus_message_iter_recurse(props_iter, &it[0]); + dbus_message_iter_get_basic(&it[0], &key); + dbus_message_iter_next(&it[0]); + dbus_message_iter_recurse(&it[0], &it[1]); + + if (spa_streq(key, "SupportedUUIDs")) { + DBusMessageIter iter; + + if (!check_iter_signature(&it[1], "as")) + goto next; + + dbus_message_iter_recurse(&it[1], &iter); + + while (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INVALID) { + const char *uuid; + + dbus_message_iter_get_basic(&iter, &uuid); + + if (spa_streq(uuid, SPA_BT_UUID_BAP_SINK)) { + monitor->le_audio_supported = true; + spa_log_info(monitor->log, "LE Audio supported"); + } + dbus_message_iter_next(&iter); + } + } + else + spa_log_debug(monitor->log, "media: unhandled key %s", key); + + next: + dbus_message_iter_next(props_iter); + } + return 0; +} + static void interface_added(struct spa_bt_monitor *monitor, DBusConnection *conn, const char *object_path, @@ -3791,6 +4182,9 @@ static void interface_added(struct spa_bt_monitor *monitor, if (d) spa_bt_device_emit_profiles_changed(d, d->profiles, d->connected_profiles); } + else if (spa_streq(interface_name, BLUEZ_MEDIA_INTERFACE)) { + media_update_props(monitor, props_iter, NULL); + } } static void interfaces_added(struct spa_bt_monitor *monitor, DBusMessageIter *arg_iter) @@ -4162,7 +4556,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us transport_update_props(transport, &it[1], NULL); } - } + } fail: dbus_error_free(&err); @@ -4280,6 +4674,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 +4706,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); @@ -4328,7 +4728,7 @@ static int impl_clear(struct spa_handle *handle) spa_bt_quirks_destroy(monitor->quirks); - free_a2dp_codecs(monitor->a2dp_codecs); + free_media_codecs(monitor->media_codecs); return 0; } @@ -4364,6 +4764,10 @@ int spa_bt_profiles_from_json_array(const char *str) profiles |= SPA_BT_PROFILE_A2DP_SINK; } else if (spa_streq(role_name, "a2dp_source")) { profiles |= SPA_BT_PROFILE_A2DP_SOURCE; + } else if (spa_streq(role_name, "bap_sink")) { + profiles |= SPA_BT_PROFILE_BAP_SINK; + } else if (spa_streq(role_name, "bap_source")) { + profiles |= SPA_BT_PROFILE_BAP_SOURCE; } } @@ -4372,7 +4776,7 @@ int spa_bt_profiles_from_json_array(const char *str) static int parse_codec_array(struct spa_bt_monitor *this, const struct spa_dict *info) { - const struct a2dp_codec * const * const a2dp_codecs = this->a2dp_codecs; + const struct media_codec * const * const media_codecs = this->media_codecs; const char *str; struct spa_dict_item *codecs; struct spa_json it, it_array; @@ -4383,7 +4787,7 @@ static int parse_codec_array(struct spa_bt_monitor *this, const struct spa_dict /* Parse bluez5.codecs property to a dict of enabled codecs */ num_codecs = 0; - while (a2dp_codecs[num_codecs]) + while (media_codecs[num_codecs]) ++num_codecs; codecs = calloc(num_codecs, sizeof(struct spa_dict_item)); @@ -4405,8 +4809,8 @@ static int parse_codec_array(struct spa_bt_monitor *this, const struct spa_dict while (spa_json_get_string(&it_array, codec_name, sizeof(codec_name)) > 0) { int i; - for (i = 0; a2dp_codecs[i]; ++i) { - const struct a2dp_codec *codec = a2dp_codecs[i]; + for (i = 0; media_codecs[i]; ++i) { + const struct media_codec *codec = media_codecs[i]; if (!spa_streq(codec->name, codec_name)) continue; @@ -4428,16 +4832,16 @@ static int parse_codec_array(struct spa_bt_monitor *this, const struct spa_dict spa_dict_qsort(&this->enabled_codecs); - for (i = 0; a2dp_codecs[i]; ++i) { - const struct a2dp_codec *codec = a2dp_codecs[i]; - if (!is_a2dp_codec_enabled(this, codec)) + for (i = 0; media_codecs[i]; ++i) { + const struct media_codec *codec = media_codecs[i]; + if (!is_media_codec_enabled(this, codec)) spa_log_debug(this->log, "disabling codec %s", codec->name); } return 0; fallback: - for (i = 0; a2dp_codecs[i]; ++i) { - const struct a2dp_codec *codec = a2dp_codecs[i]; + for (i = 0; media_codecs[i]; ++i) { + const struct media_codec *codec = media_codecs[i]; spa_log_debug(this->log, "enabling codec %s", codec->name); codecs[i].key = codec->name; codecs[i].value = "true"; @@ -4447,6 +4851,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, @@ -4483,14 +4907,14 @@ impl_init(const struct spa_handle_factory *factory, return -EINVAL; } - this->a2dp_codecs = NULL; + this->media_codecs = NULL; this->quirks = NULL; this->conn = NULL; this->dbus_connection = NULL; - this->a2dp_codecs = load_a2dp_codecs(this->plugin_loader, this->log); - if (this->a2dp_codecs == NULL) { - spa_log_error(this->log, "failed to load required A2DP codec plugins"); + this->media_codecs = load_media_codecs(this->plugin_loader, this->log); + if (this->media_codecs == NULL) { + spa_log_error(this->log, "failed to load required media codec plugins"); res = -EIO; goto fail; } @@ -4540,6 +4964,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; @@ -4585,15 +5011,15 @@ impl_init(const struct spa_handle_factory *factory, return 0; fail: - if (this->a2dp_codecs) - free_a2dp_codecs(this->a2dp_codecs); + if (this->media_codecs) + free_media_codecs(this->media_codecs); if (this->quirks) spa_bt_quirks_destroy(this->quirks); if (this->conn) dbus_connection_unref(this->conn); if (this->dbus_connection) spa_dbus_connection_destroy(this->dbus_connection); - this->a2dp_codecs = NULL; + this->media_codecs = NULL; this->quirks = NULL; this->conn = NULL; this->dbus_connection = NULL; diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 6c45a028b25a98478137eba40871490dab7bd165..7c3b70a2b2f3a7c30f9ee8a68b4e6761cc91e234 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -51,7 +51,7 @@ #include <spa/debug/pod.h> #include "defs.h" -#include "a2dp-codecs.h" +#include "media-codecs.h" static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.device"); #undef SPA_LOG_TOPIC_DEFAULT @@ -73,6 +73,7 @@ enum { DEVICE_PROFILE_AG = 1, DEVICE_PROFILE_A2DP = 2, DEVICE_PROFILE_HSP_HFP = 3, + DEVICE_PROFILE_BAP = 4, }; struct props { @@ -140,11 +141,11 @@ struct impl { unsigned int save_profile:1; uint32_t prev_bt_connected_profiles; - const struct a2dp_codec **supported_codecs; + const struct media_codec **supported_codecs; size_t supported_codec_count; - struct dynamic_node dyn_a2dp_source; - struct dynamic_node dyn_a2dp_sink; + struct dynamic_node dyn_media_source; + struct dynamic_node dyn_media_sink; struct dynamic_node dyn_sco_source; struct dynamic_node dyn_sco_sink; @@ -167,9 +168,9 @@ static void init_node(struct impl *this, struct node *node, uint32_t id) } } -static void get_a2dp_codecs(struct impl *this, enum spa_bluetooth_audio_codec id, const struct a2dp_codec **codecs, size_t size) +static void get_media_codecs(struct impl *this, enum spa_bluetooth_audio_codec id, const struct media_codec **codecs, size_t size) { - const struct a2dp_codec * const *c; + const struct media_codec * const *c; spa_assert(size > 0); spa_assert(this->supported_codecs); @@ -184,18 +185,18 @@ static void get_a2dp_codecs(struct impl *this, enum spa_bluetooth_audio_codec id *codecs = NULL; } -static const struct a2dp_codec *get_supported_a2dp_codec(struct impl *this, enum spa_bluetooth_audio_codec id, size_t *idx) +static const struct media_codec *get_supported_media_codec(struct impl *this, enum spa_bluetooth_audio_codec id, size_t *idx) { - const struct a2dp_codec *a2dp_codec = NULL; + const struct media_codec *media_codec = NULL; size_t i; for (i = 0; i < this->supported_codec_count; ++i) { if (this->supported_codecs[i]->id == id) { - a2dp_codec = this->supported_codecs[i]; + media_codec = this->supported_codecs[i]; if (idx) *idx = i; } } - return a2dp_codec; + return media_codec; } static unsigned int get_hfp_codec(enum spa_bluetooth_audio_codec id) @@ -245,10 +246,10 @@ static const char *get_hfp_codec_name(unsigned int codec) static const char *get_codec_name(struct spa_bt_transport *t, bool a2dp_duplex) { - if (t->a2dp_codec != NULL) { - if (a2dp_duplex && t->a2dp_codec->duplex_codec) - return t->a2dp_codec->duplex_codec->name; - return t->a2dp_codec->name; + if (t->media_codec != NULL) { + if (a2dp_duplex && t->media_codec->duplex_codec) + return t->media_codec->duplex_codec->name; + return t->media_codec->name; } return get_hfp_codec_name(t->codec); } @@ -301,6 +302,8 @@ static void emit_info(struct impl *this, bool full); static float get_soft_volume_boost(struct node *node) { + const struct media_codec *codec = node->transport ? node->transport->media_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 +313,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 +341,58 @@ 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_BAP && 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; @@ -381,16 +408,16 @@ static const struct spa_bt_transport_events transport_events = { static void get_channels(struct spa_bt_transport *t, bool a2dp_duplex, uint32_t *n_channels, uint32_t *channels) { - const struct a2dp_codec *codec; + const struct media_codec *codec; struct spa_audio_info info = { 0 }; - if (!a2dp_duplex || !t->a2dp_codec || !t->a2dp_codec->duplex_codec) { + if (!a2dp_duplex || !t->media_codec || !t->media_codec->duplex_codec) { *n_channels = t->n_channels; memcpy(channels, t->channels, t->n_channels * sizeof(uint32_t)); return; } - codec = t->a2dp_codec->duplex_codec; + codec = t->media_codec->duplex_codec; if (!codec->validate_config || codec->validate_config(codec, 0, @@ -469,6 +496,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; @@ -487,7 +516,7 @@ static struct spa_bt_transport *find_transport(struct impl *this, int profile, e spa_list_for_each(t, &device->transport_list, device_link) { bool codec_ok = codec == 0 || - (t->a2dp_codec != NULL && t->a2dp_codec->id == codec) || + (t->media_codec != NULL && t->media_codec->id == codec) || get_hfp_codec_id(t->codec) == codec; if ((t->profile & device->connected_profiles) && @@ -649,15 +678,15 @@ static int emit_nodes(struct impl *this) 1, SPA_NAME_API_BLUEZ5_SCO_SINK, false); } } - if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) { + if (this->bt_dev->connected_profiles & (SPA_BT_PROFILE_A2DP_SOURCE)) { t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE, 0); if (t) { - this->props.codec = t->a2dp_codec->id; - emit_dynamic_node(&this->dyn_a2dp_source, this, t, + this->props.codec = t->media_codec->id; + emit_dynamic_node(&this->dyn_media_source, this, t, 2, SPA_NAME_API_BLUEZ5_A2DP_SOURCE, false); - if (t->a2dp_codec->duplex_codec) { - emit_dynamic_node(&this->dyn_a2dp_sink, this, t, + if (t->media_codec->duplex_codec) { + emit_dynamic_node(&this->dyn_media_sink, this, t, 3, SPA_NAME_API_BLUEZ5_A2DP_SINK, true); } } @@ -667,11 +696,11 @@ static int emit_nodes(struct impl *this) if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) { t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE, 0); if (t) { - this->props.codec = t->a2dp_codec->id; - emit_dynamic_node(&this->dyn_a2dp_source, this, t, + this->props.codec = t->media_codec->id; + emit_dynamic_node(&this->dyn_media_source, this, t, DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_A2DP_SOURCE, false); - if (t->a2dp_codec->duplex_codec) { + if (t->media_codec->duplex_codec) { emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_A2DP_SINK, true); } @@ -681,17 +710,45 @@ static int emit_nodes(struct impl *this) if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) { t = find_transport(this, SPA_BT_PROFILE_A2DP_SINK, this->props.codec); if (t) { - this->props.codec = t->a2dp_codec->id; + this->props.codec = t->media_codec->id; emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_A2DP_SINK, false); - if (t->a2dp_codec->duplex_codec) { + if (t->media_codec->duplex_codec) { emit_node(this, t, DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_A2DP_SOURCE, true); } } } - if (get_supported_a2dp_codec(this, this->props.codec, NULL) == NULL) + if (get_supported_media_codec(this, this->props.codec, NULL) == NULL) + this->props.codec = 0; + break; + case DEVICE_PROFILE_BAP: + if (this->bt_dev->connected_profiles & (SPA_BT_PROFILE_BAP_SOURCE)) { + t = find_transport(this, SPA_BT_PROFILE_BAP_SOURCE, 0); + if (t) { + this->props.codec = t->media_codec->id; + if (t->bap_initiator) + emit_node(this, t, DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_MEDIA_SOURCE, false); + else + emit_dynamic_node(&this->dyn_media_source, this, t, + DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_MEDIA_SOURCE, false); + } + } + + if (this->bt_dev->connected_profiles & (SPA_BT_PROFILE_BAP_SINK)) { + t = find_transport(this, SPA_BT_PROFILE_BAP_SINK, this->props.codec); + if (t) { + this->props.codec = t->media_codec->id; + if (t->bap_initiator) + emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_MEDIA_SINK, false); + else + emit_dynamic_node(&this->dyn_media_sink, this, t, + DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_MEDIA_SINK, false); + } + } + + if (get_supported_media_codec(this, this->props.codec, NULL) == NULL) this->props.codec = 0; break; case DEVICE_PROFILE_HSP_HFP: @@ -739,8 +796,8 @@ static void emit_info(struct impl *this, bool full) static void emit_remove_nodes(struct impl *this) { - remove_dynamic_node (&this->dyn_a2dp_source); - remove_dynamic_node (&this->dyn_a2dp_sink); + remove_dynamic_node (&this->dyn_media_source); + remove_dynamic_node (&this->dyn_media_sink); remove_dynamic_node (&this->dyn_sco_source); remove_dynamic_node (&this->dyn_sco_sink); @@ -773,6 +830,7 @@ static int set_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_a if (this->profile == profile && (this->profile != DEVICE_PROFILE_A2DP || codec == this->props.codec) && + (this->profile != DEVICE_PROFILE_BAP || codec == this->props.codec) && (this->profile != DEVICE_PROFILE_HSP_HFP || codec == this->props.codec)) return 0; @@ -785,19 +843,20 @@ static int set_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_a this->props.codec = codec; /* - * A2DP: ensure there's a transport with the selected codec (0 means any). + * A2DP/BAP: ensure there's a transport with the selected codec (0 means any). * Don't try to switch codecs when the device is in the A2DP source role, since * devices do not appear to like that. */ - if (profile == DEVICE_PROFILE_A2DP && !(this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE)) { + if ((profile == DEVICE_PROFILE_A2DP || profile == DEVICE_PROFILE_BAP) + && !(this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE)) { int ret; - const struct a2dp_codec *codecs[64]; + const struct media_codec *codecs[64]; - get_a2dp_codecs(this, codec, codecs, SPA_N_ELEMENTS(codecs)); + get_media_codecs(this, codec, codecs, SPA_N_ELEMENTS(codecs)); this->switching_codec = true; - ret = spa_bt_device_ensure_a2dp_codec(this->bt_dev, codecs); + ret = spa_bt_device_ensure_media_codec(this->bt_dev, codecs); if (ret < 0) { if (ret != -ENOTSUP) spa_log_error(this->log, "failed to switch codec (%d), setting basic profile", ret); @@ -846,6 +905,8 @@ static void codec_switched(void *userdata, int status) spa_log_error(this->log, "failed to switch codec (%d), setting fallback profile", status); if (this->profile == DEVICE_PROFILE_A2DP && this->props.codec != 0) { this->props.codec = 0; + } else if (this->profile == DEVICE_PROFILE_BAP && this->props.codec != 0) { + this->props.codec = 0; } else if (this->profile == DEVICE_PROFILE_HSP_HFP && this->props.codec != 0) { this->props.codec = 0; } else { @@ -885,10 +946,10 @@ static void profiles_changed(void *userdata, uint32_t prev_profiles, uint32_t pr if (this->switching_codec) return; - if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) { + if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_MEDIA_SINK) { free(this->supported_codecs); - this->supported_codecs = spa_bt_device_get_supported_a2dp_codecs( - this->bt_dev, &this->supported_codec_count); + this->supported_codecs = spa_bt_device_get_supported_media_codecs( + this->bt_dev, &this->supported_codec_count, true); } switch (this->profile) { @@ -898,16 +959,17 @@ static void profiles_changed(void *userdata, uint32_t prev_profiles, uint32_t pr break; case DEVICE_PROFILE_AG: nodes_changed = (connected_change & (SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY | - SPA_BT_PROFILE_A2DP_SOURCE)); + SPA_BT_PROFILE_MEDIA_SOURCE)); spa_log_debug(this->log, "profiles changed: AG nodes changed: %d", nodes_changed); break; case DEVICE_PROFILE_A2DP: - if (get_supported_a2dp_codec(this, this->props.codec, NULL) == NULL) + case DEVICE_PROFILE_BAP: + if (get_supported_media_codec(this, this->props.codec, NULL) == NULL) this->props.codec = 0; - nodes_changed = (connected_change & (SPA_BT_PROFILE_A2DP_SINK | - SPA_BT_PROFILE_A2DP_SOURCE)); - spa_log_debug(this->log, "profiles changed: A2DP nodes changed: %d", + nodes_changed = (connected_change & (SPA_BT_PROFILE_MEDIA_SINK | + SPA_BT_PROFILE_MEDIA_SOURCE)); + spa_log_debug(this->log, "profiles changed: media nodes changed: %d", nodes_changed); break; case DEVICE_PROFILE_HSP_HFP: @@ -992,15 +1054,16 @@ static uint32_t profile_direction_mask(struct impl *this, uint32_t index, enum s struct spa_bt_device *device = this->bt_dev; uint32_t mask; bool have_output = false, have_input = false; - const struct a2dp_codec *a2dp_codec; + const struct media_codec *media_codec; switch (index) { case DEVICE_PROFILE_A2DP: - if (device->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) + case DEVICE_PROFILE_BAP: + if (device->connected_profiles & SPA_BT_PROFILE_MEDIA_SINK) have_output = true; - a2dp_codec = get_supported_a2dp_codec(this, codec, NULL); - if (a2dp_codec && a2dp_codec->duplex_codec) + media_codec = get_supported_media_codec(this, codec, NULL); + if (media_codec && media_codec->duplex_codec) have_input = true; break; case DEVICE_PROFILE_HSP_HFP: @@ -1033,6 +1096,7 @@ static uint32_t get_profile_from_index(struct impl *this, uint32_t index, uint32 return index; } else if (index != SPA_ID_INVALID) { const struct spa_type_info *info; + uint32_t profile; *codec = index - 3; *next = SPA_ID_INVALID; @@ -1041,7 +1105,14 @@ static uint32_t get_profile_from_index(struct impl *this, uint32_t index, uint32 if (info->type > *codec) *next = SPA_MIN(info->type + 3, *next); - return get_hfp_codec(*codec) ? DEVICE_PROFILE_HSP_HFP : DEVICE_PROFILE_A2DP; + if (get_hfp_codec(*codec)) + profile = DEVICE_PROFILE_HSP_HFP; + else if (*codec == SPA_BLUETOOTH_AUDIO_CODEC_LC3) + profile = DEVICE_PROFILE_BAP; + else + profile = DEVICE_PROFILE_A2DP; + + return profile; } *next = SPA_ID_INVALID; @@ -1053,8 +1124,8 @@ static uint32_t get_index_from_profile(struct impl *this, uint32_t profile, enum if (profile == DEVICE_PROFILE_OFF || profile == DEVICE_PROFILE_AG) return profile; - if (profile == DEVICE_PROFILE_A2DP) { - if (codec == 0 || (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE)) + if (profile == DEVICE_PROFILE_A2DP || profile == DEVICE_PROFILE_BAP) { + if (codec == 0 || (this->bt_dev->connected_profiles & SPA_BT_PROFILE_MEDIA_SOURCE)) return profile; return codec + 3; @@ -1102,11 +1173,11 @@ 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->supported_codecs = spa_bt_device_get_supported_media_codecs( + 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 */ + /* Prefer BAP, then A2DP, then HFP, then null, but select AG if the device + appears not to have BAP_SINK, A2DP_SINK or any HEAD_UNIT profile */ /* If default profile is set to HSP/HFP, first try those and exit if found. */ if (this->bt_dev->settings != NULL) { @@ -1117,16 +1188,20 @@ static void set_initial_profile(struct impl *this) return; } - for (i = SPA_BT_PROFILE_A2DP_SINK; i <= SPA_BT_PROFILE_A2DP_SOURCE; i <<= 1) { + for (i = SPA_BT_PROFILE_BAP_SINK; i <= SPA_BT_PROFILE_A2DP_SOURCE; i <<= 1) { if (!(this->bt_dev->connected_profiles & i)) continue; t = find_transport(this, i, 0); if (t) { - this->profile = (i == SPA_BT_PROFILE_A2DP_SOURCE) ? - DEVICE_PROFILE_AG : DEVICE_PROFILE_A2DP; - this->props.codec = t->a2dp_codec->id; - spa_log_debug(this->log, "initial profile A2DP profile:%d codec:%d", + if (i == SPA_BT_PROFILE_A2DP_SOURCE || i == SPA_BT_PROFILE_BAP_SOURCE) + this->profile = DEVICE_PROFILE_AG; + else if (i == SPA_BT_PROFILE_BAP_SINK) + this->profile = DEVICE_PROFILE_BAP; + else + this->profile = DEVICE_PROFILE_A2DP; + this->props.codec = t->media_codec->id; + spa_log_debug(this->log, "initial profile media profile:%d codec:%d", this->profile, this->props.codec); return; } @@ -1186,19 +1261,19 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * n_sink++; if (codec) { size_t idx; - const struct a2dp_codec *a2dp_codec = get_supported_a2dp_codec(this, codec, &idx); - if (a2dp_codec == NULL) { + const struct media_codec *media_codec = get_supported_media_codec(this, codec, &idx); + if (media_codec == NULL) { errno = EINVAL; return NULL; } - name_and_codec = spa_aprintf("%s-%s", name, a2dp_codec->name); + name_and_codec = spa_aprintf("%s-%s", name, media_codec->name); name = name_and_codec; - if (profile == SPA_BT_PROFILE_A2DP_SINK && !a2dp_codec->duplex_codec) { + if (profile == SPA_BT_PROFILE_A2DP_SINK && !media_codec->duplex_codec) { desc_and_codec = spa_aprintf(_("High Fidelity Playback (A2DP Sink, codec %s)"), - a2dp_codec->description); + media_codec->description); } else { desc_and_codec = spa_aprintf(_("High Fidelity Duplex (A2DP Source/Sink, codec %s)"), - a2dp_codec->description); + media_codec->description); } desc = desc_and_codec; @@ -1213,6 +1288,52 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * } break; } + case DEVICE_PROFILE_BAP: + { + uint32_t profile = device->connected_profiles & + (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE); + size_t idx; + const struct media_codec *media_codec; + + if (profile == 0) + return NULL; + + if (!codec) { + errno = EINVAL; + return NULL; + } + + if (profile & (SPA_BT_PROFILE_BAP_SINK)) + n_sink++; + if (profile & (SPA_BT_PROFILE_BAP_SOURCE)) + n_source++; + + name = spa_bt_profile_name(profile); + + media_codec = get_supported_media_codec(this, codec, &idx); + if (media_codec == NULL) { + errno = EINVAL; + return NULL; + } + name_and_codec = spa_aprintf("%s-%s", name, media_codec->name); + name = name_and_codec; + switch (profile) { + case SPA_BT_PROFILE_BAP_SINK: + desc_and_codec = spa_aprintf(_("High Fidelity Playback (BAP Sink, codec %s)"), + media_codec->description); + break; + case SPA_BT_PROFILE_BAP_SOURCE: + desc_and_codec = spa_aprintf(_("High Fidelity Input (BAP Source, codec %s)"), + media_codec->description); + break; + default: + desc_and_codec = spa_aprintf(_("High Fidelity Duplex (BAP Source/Sink, codec %s)"), + media_codec->description); + } + desc = desc_and_codec; + priority = 128 + this->supported_codec_count - idx; /* order as in codec list */ + break; + } case DEVICE_PROFILE_HSP_HFP: { /* make this device profile visible only if there is a head unit */ @@ -1301,16 +1422,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 +1440,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 +1504,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 +1573,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; @@ -1459,7 +1631,8 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, spa_pod_builder_array(b, sizeof(uint32_t), SPA_TYPE_Id, node->n_channels, node->channels); - if (this->profile == DEVICE_PROFILE_A2DP && dev == DEVICE_ID_SINK) { + if ((this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP) && + dev == DEVICE_ID_SINK) { spa_pod_builder_prop(b, SPA_PROP_latencyOffsetNsec, 0); spa_pod_builder_long(b, node->latency_offset); } @@ -1472,8 +1645,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) { @@ -1483,7 +1655,7 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, return spa_pod_builder_pop(b, &f[0]); } -static bool iterate_supported_a2dp_codecs(struct impl *this, int *j, const struct a2dp_codec **codec) +static bool iterate_supported_media_codecs(struct impl *this, int *j, const struct media_codec **codec) { int i; @@ -1505,12 +1677,12 @@ static struct spa_pod *build_prop_info(struct impl *this, struct spa_pod_builder { struct spa_pod_frame f[2]; struct spa_pod_choice *choice; - const struct a2dp_codec *codec; + const struct media_codec *codec; size_t n; int j; -#define FOR_EACH_A2DP_CODEC(j, codec) \ - for (j = -1; iterate_supported_a2dp_codecs(this, &j, &codec);) +#define FOR_EACH_MEDIA_CODEC(j, codec) \ + for (j = -1; iterate_supported_media_codecs(this, &j, &codec);) #define FOR_EACH_HFP_CODEC(j) \ for (j = HFP_AUDIO_CODEC_MSBC; j >= HFP_AUDIO_CODEC_CVSD; --j) \ if (spa_bt_device_supports_hfp_codec(this->bt_dev, j) == 1) @@ -1531,8 +1703,8 @@ static struct spa_pod *build_prop_info(struct impl *this, struct spa_pod_builder spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); choice = (struct spa_pod_choice *)spa_pod_builder_frame(b, &f[1]); n = 0; - if (this->profile == DEVICE_PROFILE_A2DP) { - FOR_EACH_A2DP_CODEC(j, codec) { + if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP) { + FOR_EACH_MEDIA_CODEC(j, codec) { if (n == 0) spa_pod_builder_int(b, codec->id); spa_pod_builder_int(b, codec->id); @@ -1551,8 +1723,8 @@ static struct spa_pod *build_prop_info(struct impl *this, struct spa_pod_builder spa_pod_builder_pop(b, &f[1]); spa_pod_builder_prop(b, SPA_PROP_INFO_labels, 0); spa_pod_builder_push_struct(b, &f[1]); - if (this->profile == DEVICE_PROFILE_A2DP) { - FOR_EACH_A2DP_CODEC(j, codec) { + if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP) { + FOR_EACH_MEDIA_CODEC(j, codec) { spa_pod_builder_int(b, codec->id); spa_pod_builder_string(b, codec->description); } @@ -1565,7 +1737,7 @@ static struct spa_pod *build_prop_info(struct impl *this, struct spa_pod_builder spa_pod_builder_pop(b, &f[1]); return spa_pod_builder_pop(b, &f[0]); -#undef FOR_EACH_A2DP_CODEC +#undef FOR_EACH_MEDIA_CODEC #undef FOR_EACH_HFP_CODEC } @@ -1611,6 +1783,7 @@ static int impl_enum_params(void *object, int seq, case DEVICE_PROFILE_OFF: case DEVICE_PROFILE_AG: case DEVICE_PROFILE_A2DP: + case DEVICE_PROFILE_BAP: case DEVICE_PROFILE_HSP_HFP: param = build_profile(this, &b, id, result.index, profile, codec, false); if (param == NULL) @@ -1640,9 +1813,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 +1826,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; @@ -1949,7 +2120,7 @@ static int impl_set_param(void *object, if (codec_id == SPA_ID_INVALID) return 0; - if (this->profile == DEVICE_PROFILE_A2DP) { + if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP) { size_t j; for (j = 0; j < this->supported_codec_count; ++j) { if (this->supported_codecs[j]->id == codec_id) { diff --git a/spa/plugins/bluez5/codec-loader.c b/spa/plugins/bluez5/codec-loader.c index cce3c4bc65aac7736f782fd8b6625c40aa08b89b..357d0cba9e798ab196b4cdee0781848414b99bf9 100644 --- a/spa/plugins/bluez5/codec-loader.c +++ b/spa/plugins/bluez5/codec-loader.c @@ -29,7 +29,7 @@ #include "defs.h" #include "codec-loader.h" -#define A2DP_CODEC_LIB_BASE "bluez5/libspa-codec-bluez5-" +#define MEDIA_CODEC_LIB_BASE "bluez5/libspa-codec-bluez5-" /* AVDTP allows 0x3E endpoints, can't have more codecs than that */ #define MAX_CODECS 0x3E @@ -40,7 +40,7 @@ static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.codecs"); #define SPA_LOG_TOPIC_DEFAULT &log_topic struct impl { - const struct a2dp_codec *codecs[MAX_CODECS + 1]; + const struct media_codec *codecs[MAX_CODECS + 1]; struct spa_handle *handles[MAX_HANDLES]; size_t n_codecs; size_t n_handles; @@ -48,9 +48,10 @@ struct impl { struct spa_log *log; }; -static int codec_order(const struct a2dp_codec *c) +static int codec_order(const struct media_codec *c) { static const enum spa_bluetooth_audio_codec order[] = { + SPA_BLUETOOTH_AUDIO_CODEC_LC3, SPA_BLUETOOTH_AUDIO_CODEC_LDAC, SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD, SPA_BLUETOOTH_AUDIO_CODEC_APTX, @@ -63,6 +64,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) @@ -73,8 +79,8 @@ static int codec_order(const struct a2dp_codec *c) static int codec_order_cmp(const void *a, const void *b) { - const struct a2dp_codec * const *ca = a; - const struct a2dp_codec * const *cb = b; + const struct media_codec * const *ca = a; + const struct media_codec * const *cb = b; int ia = codec_order(*ca); int ib = codec_order(*cb); if (*ca == *cb) @@ -82,7 +88,7 @@ static int codec_order_cmp(const void *a, const void *b) return (ia == ib) ? (*ca < *cb ? -1 : 1) : ia - ib; } -static int load_a2dp_codecs_from(struct impl *impl, const char *factory_name, const char *libname) +static int load_media_codecs_from(struct impl *impl, const char *factory_name, const char *libname) { struct spa_handle *handle = NULL; void *iface; @@ -103,7 +109,7 @@ static int load_a2dp_codecs_from(struct impl *impl, const char *factory_name, co spa_log_debug(impl->log, "loading codecs from %s", factory_name); - if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Bluez5CodecA2DP, &iface)) < 0) { + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Bluez5CodecMedia, &iface)) < 0) { spa_log_warn(impl->log, "Bluetooth codec plugin %s has no codec interface", factory_name); goto fail; @@ -111,15 +117,15 @@ static int load_a2dp_codecs_from(struct impl *impl, const char *factory_name, co bluez5_codec_a2dp = iface; - if (bluez5_codec_a2dp->iface.version != SPA_VERSION_BLUEZ5_CODEC_A2DP) { + if (bluez5_codec_a2dp->iface.version != SPA_VERSION_BLUEZ5_CODEC_MEDIA) { spa_log_warn(impl->log, "codec plugin %s has incompatible ABI version (%d != %d)", - factory_name, bluez5_codec_a2dp->iface.version, SPA_VERSION_BLUEZ5_CODEC_A2DP); + factory_name, bluez5_codec_a2dp->iface.version, SPA_VERSION_BLUEZ5_CODEC_MEDIA); res = -ENOENT; goto fail; } for (i = 0; bluez5_codec_a2dp->codecs[i]; ++i) { - const struct a2dp_codec *c = bluez5_codec_a2dp->codecs[i]; + const struct media_codec *c = bluez5_codec_a2dp->codecs[i]; size_t j; if (impl->n_codecs >= MAX_CODECS) { @@ -129,14 +135,17 @@ static int load_a2dp_codecs_from(struct impl *impl, const char *factory_name, co /* Don't load duplicate endpoints */ for (j = 0; j < impl->n_codecs; ++j) { - const struct a2dp_codec *c2 = impl->codecs[j]; + const struct media_codec *c2 = impl->codecs[j]; const char *ep1 = c->endpoint_name ? c->endpoint_name : c->name; const char *ep2 = c2->endpoint_name ? c2->endpoint_name : c2->name; if (spa_streq(ep1, ep2)) goto next_codec; } - spa_log_debug(impl->log, "loaded A2DP codec %s from %s", c->name, factory_name); + spa_log_debug(impl->log, "loaded media 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; @@ -158,21 +167,23 @@ fail: return res; } -const struct a2dp_codec * const *load_a2dp_codecs(struct spa_plugin_loader *loader, struct spa_log *log) +const struct media_codec * const *load_media_codecs(struct spa_plugin_loader *loader, struct spa_log *log) { struct impl *impl; bool has_sbc; size_t i; const struct { const char *factory; const char *lib; } plugins[] = { -#define A2DP_CODEC_FACTORY_LIB(basename) \ - { A2DP_CODEC_FACTORY_NAME(basename), A2DP_CODEC_LIB_BASE basename } - A2DP_CODEC_FACTORY_LIB("aac"), - A2DP_CODEC_FACTORY_LIB("aptx"), - A2DP_CODEC_FACTORY_LIB("faststream"), - A2DP_CODEC_FACTORY_LIB("ldac"), - A2DP_CODEC_FACTORY_LIB("sbc"), - A2DP_CODEC_FACTORY_LIB("lc3plus") -#undef A2DP_CODEC_FACTORY_LIB +#define MEDIA_CODEC_FACTORY_LIB(basename) \ + { MEDIA_CODEC_FACTORY_NAME(basename), MEDIA_CODEC_LIB_BASE basename } + MEDIA_CODEC_FACTORY_LIB("aac"), + MEDIA_CODEC_FACTORY_LIB("aptx"), + MEDIA_CODEC_FACTORY_LIB("faststream"), + MEDIA_CODEC_FACTORY_LIB("ldac"), + MEDIA_CODEC_FACTORY_LIB("sbc"), + MEDIA_CODEC_FACTORY_LIB("lc3plus"), + MEDIA_CODEC_FACTORY_LIB("opus"), + MEDIA_CODEC_FACTORY_LIB("lc3") +#undef MEDIA_CODEC_FACTORY_LIB }; impl = calloc(sizeof(struct impl), 1); @@ -185,7 +196,7 @@ const struct a2dp_codec * const *load_a2dp_codecs(struct spa_plugin_loader *load spa_log_topic_init(impl->log, &log_topic); for (i = 0; i < SPA_N_ELEMENTS(plugins); ++i) - load_a2dp_codecs_from(impl, plugins[i].factory, plugins[i].lib); + load_media_codecs_from(impl, plugins[i].factory, plugins[i].lib); has_sbc = false; for (i = 0; i < impl->n_codecs; ++i) @@ -194,19 +205,19 @@ const struct a2dp_codec * const *load_a2dp_codecs(struct spa_plugin_loader *load if (!has_sbc) { spa_log_error(impl->log, "failed to load A2DP SBC codec from plugins"); - free_a2dp_codecs(impl->codecs); + free_media_codecs(impl->codecs); errno = ENOENT; return NULL; } - qsort(impl->codecs, impl->n_codecs, sizeof(const struct a2dp_codec *), codec_order_cmp); + qsort(impl->codecs, impl->n_codecs, sizeof(const struct media_codec *), codec_order_cmp); return impl->codecs; } -void free_a2dp_codecs(const struct a2dp_codec * const *a2dp_codecs) +void free_media_codecs(const struct media_codec * const *media_codecs) { - struct impl *impl = SPA_CONTAINER_OF(a2dp_codecs, struct impl, codecs); + struct impl *impl = SPA_CONTAINER_OF(media_codecs, struct impl, codecs); size_t i; for (i = 0; i < impl->n_handles; ++i) diff --git a/spa/plugins/bluez5/codec-loader.h b/spa/plugins/bluez5/codec-loader.h index 5422cc45802cab2b04cd8c4c7786eb6d69c8dad2..b77d9c49f4b0ef696198291e2fd0b5fd07784132 100644 --- a/spa/plugins/bluez5/codec-loader.h +++ b/spa/plugins/bluez5/codec-loader.h @@ -31,9 +31,9 @@ #include <spa/support/plugin-loader.h> #include "a2dp-codec-caps.h" -#include "a2dp-codecs.h" +#include "media-codecs.h" -const struct a2dp_codec * const *load_a2dp_codecs(struct spa_plugin_loader *loader, struct spa_log *log); -void free_a2dp_codecs(const struct a2dp_codec * const *a2dp_codecs); +const struct media_codec * const *load_media_codecs(struct spa_plugin_loader *loader, struct spa_log *log); +void free_media_codecs(const struct media_codec * const *media_codecs); #endif 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..1bb7850da67f91578936d40fcc848868cd46661b 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -134,13 +134,16 @@ 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 SPA_BT_UUID_PACS "00001850-0000-1000-8000-00805f9b34fb" +#define SPA_BT_UUID_BAP_SINK "00002bc9-0000-1000-8000-00805f9b34fb" +#define SPA_BT_UUID_BAP_SOURCE "00002bcb-0000-1000-8000-00805f9b34fb" #define PROFILE_HSP_AG "/Profile/HSPAG" #define PROFILE_HSP_HS "/Profile/HSPHS" @@ -158,9 +161,12 @@ extern "C" { #define HFP_AUDIO_CODEC_CVSD 0x01 #define HFP_AUDIO_CODEC_MSBC 0x02 -#define A2DP_OBJECT_MANAGER_PATH "/MediaEndpoint" -#define A2DP_SINK_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSink" -#define A2DP_SOURCE_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSource" +#define MEDIA_OBJECT_MANAGER_PATH "/MediaEndpoint" +#define A2DP_SINK_ENDPOINT MEDIA_OBJECT_MANAGER_PATH "/A2DPSink" +#define A2DP_SOURCE_ENDPOINT MEDIA_OBJECT_MANAGER_PATH "/A2DPSource" + +#define BAP_SINK_ENDPOINT MEDIA_OBJECT_MANAGER_PATH "/BAPSink" +#define BAP_SOURCE_ENDPOINT MEDIA_OBJECT_MANAGER_PATH "/BAPSource" #define SPA_BT_UNKNOWN_DELAY 0 @@ -172,19 +178,30 @@ extern "C" { #define MSBC_ENCODED_SIZE 60 /* 2 bytes header + 57 mSBC payload + 1 byte padding */ #define MSBC_PAYLOAD_SIZE 57 +enum spa_bt_media_direction { + SPA_BT_MEDIA_SOURCE, + SPA_BT_MEDIA_SINK, +}; + enum spa_bt_profile { SPA_BT_PROFILE_NULL = 0, - SPA_BT_PROFILE_A2DP_SINK = (1 << 0), - SPA_BT_PROFILE_A2DP_SOURCE = (1 << 1), - SPA_BT_PROFILE_HSP_HS = (1 << 2), - SPA_BT_PROFILE_HSP_AG = (1 << 3), - SPA_BT_PROFILE_HFP_HF = (1 << 4), - SPA_BT_PROFILE_HFP_AG = (1 << 5), + SPA_BT_PROFILE_BAP_SINK = (1 << 0), + SPA_BT_PROFILE_BAP_SOURCE = (1 << 1), + SPA_BT_PROFILE_A2DP_SINK = (1 << 2), + SPA_BT_PROFILE_A2DP_SOURCE = (1 << 3), + SPA_BT_PROFILE_HSP_HS = (1 << 4), + SPA_BT_PROFILE_HSP_AG = (1 << 5), + SPA_BT_PROFILE_HFP_HF = (1 << 6), + SPA_BT_PROFILE_HFP_AG = (1 << 7), SPA_BT_PROFILE_A2DP_DUPLEX = (SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_A2DP_SOURCE), + SPA_BT_PROFILE_BAP_DUPLEX = (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE), SPA_BT_PROFILE_HEADSET_HEAD_UNIT = (SPA_BT_PROFILE_HSP_HS | SPA_BT_PROFILE_HFP_HF), SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY = (SPA_BT_PROFILE_HSP_AG | SPA_BT_PROFILE_HFP_AG), SPA_BT_PROFILE_HEADSET_AUDIO = (SPA_BT_PROFILE_HEADSET_HEAD_UNIT | SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY), + + SPA_BT_PROFILE_MEDIA_SINK = (SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_BAP_SINK), + SPA_BT_PROFILE_MEDIA_SOURCE = (SPA_BT_PROFILE_A2DP_SOURCE | SPA_BT_PROFILE_BAP_SOURCE), }; static inline enum spa_bt_profile spa_bt_profile_from_uuid(const char *uuid) @@ -203,6 +220,10 @@ static inline enum spa_bt_profile spa_bt_profile_from_uuid(const char *uuid) return SPA_BT_PROFILE_HFP_HF; else if (strcasecmp(uuid, SPA_BT_UUID_HFP_AG) == 0) return SPA_BT_PROFILE_HFP_AG; + else if (strcasecmp(uuid, SPA_BT_UUID_BAP_SINK) == 0) + return SPA_BT_PROFILE_BAP_SINK; + else if (strcasecmp(uuid, SPA_BT_UUID_BAP_SOURCE) == 0) + return SPA_BT_PROFILE_BAP_SOURCE; else return 0; } @@ -303,6 +324,12 @@ static inline const char *spa_bt_profile_name (enum spa_bt_profile profile) { return "headset-audio-gateway"; case SPA_BT_PROFILE_HEADSET_AUDIO: return "headset-audio"; + case SPA_BT_PROFILE_BAP_SOURCE: + return "bap-source"; + case SPA_BT_PROFILE_BAP_SINK: + return "bap-sink"; + case SPA_BT_PROFILE_BAP_DUPLEX: + return "bap-duplex"; default: break; } @@ -412,7 +439,7 @@ static inline enum spa_bt_form_factor spa_bt_form_factor_from_class(uint32_t blu return SPA_BT_FORM_FACTOR_UNKNOWN; } -struct spa_bt_a2dp_codec_switch; +struct spa_bt_media_codec_switch; struct spa_bt_transport; struct spa_bt_device_events { @@ -482,16 +509,16 @@ struct spa_bt_device { DBusPendingCall *battery_pending_call; }; -struct a2dp_codec; +struct media_codec; struct spa_bt_device *spa_bt_device_find(struct spa_bt_monitor *monitor, const char *path); struct spa_bt_device *spa_bt_device_find_by_address(struct spa_bt_monitor *monitor, const char *remote_address, const char *local_address); int spa_bt_device_add_profile(struct spa_bt_device *device, enum spa_bt_profile 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); +int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct media_codec * const *codecs); +bool spa_bt_device_supports_media_codec(struct spa_bt_device *device, const struct media_codec *codec, bool sink); +const struct media_codec **spa_bt_device_get_supported_media_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); @@ -570,11 +597,13 @@ struct spa_bt_transport { struct spa_list device_link; enum spa_bt_profile profile; enum spa_bt_transport_state state; - const struct a2dp_codec *a2dp_codec; + const struct media_codec *media_codec; unsigned int codec; void *configuration; int configuration_len; char *endpoint_path; + bool bap_initiator; + struct spa_list bap_transport_linked; uint32_t n_channels; uint32_t channels[64]; diff --git a/spa/plugins/bluez5/a2dp-codecs.c b/spa/plugins/bluez5/media-codecs.c similarity index 86% rename from spa/plugins/bluez5/a2dp-codecs.c rename to spa/plugins/bluez5/media-codecs.c index c78b2f261f3e4f8aed5c92f0fc7278dfee2d0b5a..6f64e82e403ea6b6ddbc24188c3b0d3c8f9abf6d 100644 --- a/spa/plugins/bluez5/a2dp-codecs.c +++ b/spa/plugins/bluez5/media-codecs.c @@ -10,9 +10,9 @@ #include <spa/utils/string.h> -#include "a2dp-codecs.h" +#include "media-codecs.h" -int a2dp_codec_select_config(const struct a2dp_codec_config configs[], size_t n, +int media_codec_select_config(const struct media_codec_config configs[], size_t n, uint32_t cap, int preferred_value) { size_t i; @@ -60,9 +60,10 @@ int a2dp_codec_select_config(const struct a2dp_codec_config configs[], size_t n, return res; } -bool a2dp_codec_check_caps(const struct a2dp_codec *codec, unsigned int codec_id, +bool media_codec_check_caps(const struct media_codec *codec, unsigned int codec_id, const void *caps, size_t caps_size, - const struct a2dp_codec_audio_info *info) + const struct media_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; @@ -97,7 +98,7 @@ impl_get_interface(struct spa_handle *handle, const char *type, void **interface this = (struct impl *) handle; - if (spa_streq(type, SPA_TYPE_INTERFACE_Bluez5CodecA2DP)) + if (spa_streq(type, SPA_TYPE_INTERFACE_Bluez5CodecMedia)) *interface = &this->bluez5_codec_a2dp; else return -ENOENT; @@ -135,10 +136,10 @@ impl_init(const struct spa_handle_factory *factory, this = (struct impl *) handle; - this->bluez5_codec_a2dp.codecs = codec_plugin_a2dp_codecs; + this->bluez5_codec_a2dp.codecs = codec_plugin_media_codecs; this->bluez5_codec_a2dp.iface = SPA_INTERFACE_INIT( - SPA_TYPE_INTERFACE_Bluez5CodecA2DP, - SPA_VERSION_BLUEZ5_CODEC_A2DP, + SPA_TYPE_INTERFACE_Bluez5CodecMedia, + SPA_VERSION_BLUEZ5_CODEC_MEDIA, NULL, this); @@ -146,7 +147,7 @@ impl_init(const struct spa_handle_factory *factory, } static const struct spa_interface_info impl_interfaces[] = { - {SPA_TYPE_INTERFACE_Bluez5CodecA2DP,}, + {SPA_TYPE_INTERFACE_Bluez5CodecMedia,}, }; static int diff --git a/spa/plugins/bluez5/a2dp-codecs.h b/spa/plugins/bluez5/media-codecs.h similarity index 61% rename from spa/plugins/bluez5/a2dp-codecs.h rename to spa/plugins/bluez5/media-codecs.h index bd041b29efda980c721b5c9959c115e7313d22e9..0bdf914d731000f36d3cc5ae992bcab4819d097b 100644 --- a/spa/plugins/bluez5/a2dp-codecs.h +++ b/spa/plugins/bluez5/media-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" @@ -41,27 +42,28 @@ * when any of the structs or semantics change. */ -#define SPA_TYPE_INTERFACE_Bluez5CodecA2DP SPA_TYPE_INFO_INTERFACE_BASE "Bluez5:Codec:A2DP:Private" +#define SPA_TYPE_INTERFACE_Bluez5CodecMedia SPA_TYPE_INFO_INTERFACE_BASE "Bluez5:Codec:Media:Private" -#define SPA_VERSION_BLUEZ5_CODEC_A2DP 1 +#define SPA_VERSION_BLUEZ5_CODEC_MEDIA 5 struct spa_bluez5_codec_a2dp { struct spa_interface iface; - const struct a2dp_codec * const *codecs; /**< NULL terminated array */ + const struct media_codec * const *codecs; /**< NULL terminated array */ }; -#define A2DP_CODEC_FACTORY_NAME(basename) (SPA_NAME_API_CODEC_BLUEZ5_A2DP "." basename) +#define MEDIA_CODEC_FACTORY_NAME(basename) (SPA_NAME_API_CODEC_BLUEZ5_MEDIA "." basename) #ifdef CODEC_PLUGIN -#define A2DP_CODEC_EXPORT_DEF(basename,...) \ - const char *codec_plugin_factory_name = A2DP_CODEC_FACTORY_NAME(basename); \ - static const struct a2dp_codec * const codec_plugin_a2dp_codec_list[] = { __VA_ARGS__, NULL }; \ - const struct a2dp_codec * const * const codec_plugin_a2dp_codecs = codec_plugin_a2dp_codec_list; +#define MEDIA_CODEC_EXPORT_DEF(basename,...) \ + const char *codec_plugin_factory_name = MEDIA_CODEC_FACTORY_NAME(basename); \ + static const struct media_codec * const codec_plugin_media_codec_list[] = { __VA_ARGS__, NULL }; \ + const struct media_codec * const * const codec_plugin_media_codecs = codec_plugin_media_codec_list; -extern const struct a2dp_codec * const * const codec_plugin_a2dp_codecs; +extern const struct media_codec * const * const codec_plugin_media_codecs; extern const char *codec_plugin_factory_name; #endif +#define MEDIA_CODEC_FLAG_SINK (1 << 0) #define A2DP_CODEC_DEFAULT_RATE 48000 #define A2DP_CODEC_DEFAULT_CHANNELS 2 @@ -72,16 +74,28 @@ enum { NEED_FLUSH_FRAGMENT = 2, }; -struct a2dp_codec_audio_info { +struct media_codec_audio_info { uint32_t rate; uint32_t channels; }; -struct a2dp_codec { +struct codec_qos { + uint32_t interval; + bool framing; + char *phy; + uint16_t sdu; + uint8_t retransmission; + uint16_t latency; + uint32_t delay; +}; + +struct media_codec { enum spa_bluetooth_audio_codec id; uint8_t codec_id; a2dp_vendor_codec_t vendor; + bool bap; + const char *name; const char *description; const char *endpoint_name; /**< Endpoint name. If NULL, same as name */ @@ -89,36 +103,42 @@ struct a2dp_codec { const size_t send_buf_size; - const struct a2dp_codec *duplex_codec; /**< Codec for non-standard A2DP duplex channel */ + const struct media_codec *duplex_codec; /**< Codec for non-standard A2DP duplex channel */ - int (*fill_caps) (const struct a2dp_codec *codec, uint32_t flags, + struct spa_log *log; + + int (*fill_caps) (const struct media_codec *codec, uint32_t flags, uint8_t caps[A2DP_MAX_CAPS_SIZE]); - int (*select_config) (const struct a2dp_codec *codec, uint32_t flags, + int (*select_config) (const struct media_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 media_codec_audio_info *info, + const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE]); + int (*enum_config) (const struct media_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, + int (*validate_config) (const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info); + void (*get_qos)(const struct media_codec *codec, + const void *config, size_t config_size, + struct codec_qos *qos); /** qsort comparison sorting caps in order of preference for the codec. * Used in codec switching to select best remote endpoints. * 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 media_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, + const void *caps2, size_t caps2_size, const struct media_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 media_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); int (*set_props) (void *props, const struct spa_pod *param); - void *(*init) (const struct a2dp_codec *codec, uint32_t flags, void *config, size_t config_size, + void *(*init) (const struct media_codec *codec, uint32_t flags, void *config, size_t config_size, const struct spa_audio_info *info, void *props, size_t mtu); void (*deinit) (void *data); @@ -144,18 +164,21 @@ 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 { +struct media_codec_config { uint32_t config; int value; unsigned int priority; }; -int a2dp_codec_select_config(const struct a2dp_codec_config configs[], size_t n, +int media_codec_select_config(const struct media_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); +bool media_codec_check_caps(const struct media_codec *codec, unsigned int codec_id, + const void *caps, size_t caps_size, const struct media_codec_audio_info *info, + const struct spa_dict *global_settings); #endif diff --git a/spa/plugins/bluez5/a2dp-sink.c b/spa/plugins/bluez5/media-sink.c similarity index 88% rename from spa/plugins/bluez5/a2dp-sink.c rename to spa/plugins/bluez5/media-sink.c index 35a2a60d3a145d68dc397b8718d91ab4f9c6405c..255bd7a434fee489582bbe53cf717df9879ee48a 100644 --- a/spa/plugins/bluez5/a2dp-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -1,4 +1,4 @@ -/* Spa A2DP Sink +/* Spa Media Sink * * Copyright © 2018 Wim Taymans * @@ -53,9 +53,9 @@ #include "defs.h" #include "rtp.h" -#include "a2dp-codecs.h" +#include "media-codecs.h" -static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.sink.a2dp"); +static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.sink.media"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic @@ -136,6 +136,9 @@ struct impl { unsigned int started:1; unsigned int following:1; + unsigned int is_output:1; + + unsigned int is_duplex:1; struct spa_source source; int timerfd; @@ -150,7 +153,7 @@ struct impl { uint64_t next_time; uint64_t last_error; - const struct a2dp_codec *codec; + const struct media_codec *codec; bool codec_props_changed; void *codec_props; void *codec_data; @@ -170,6 +173,10 @@ struct impl { uint8_t tmp_buffer[BUFFER_SIZE]; uint32_t tmp_buffer_used; uint32_t fd_buffer_size; + + /* Times */ + uint64_t start_time; + uint64_t total_samples; }; #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == 0) @@ -616,6 +623,27 @@ static void enable_flush(struct impl *this, bool enabled, uint64_t timeout) this->flush_timerfd, 0, &ts, NULL); } +static uint64_t get_next_bap_timeout(struct impl *this) +{ + struct port *port = &this->port; + uint64_t playback_time = 0, elapsed_time = 0, next_time = 0; + struct timespec now; + uint64_t now_time; + + spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); + now_time = SPA_TIMESPEC_TO_NSEC(&now); + if (this->start_time == 0) + this->start_time = now_time; + + playback_time = (this->total_samples * SPA_NSEC_PER_SEC) / port->current_format.info.raw.rate; + if (now_time > this->start_time) + elapsed_time = now_time - this->start_time; + if (elapsed_time < playback_time) + next_time = playback_time - elapsed_time; + + return next_time; +} + static int flush_data(struct impl *this, uint64_t now_time) { int written; @@ -692,6 +720,7 @@ again: port->ready_offset = 0; } total_frames += n_frames; + this->total_samples += n_frames; spa_log_trace(this->log, "%p: written %u frames", this, total_frames); } @@ -728,54 +757,69 @@ again: return written; } else if (written > 0) { - /* - * We cannot write all data we have at once, since this can exceed - * device buffers. We'll want a limited number of "excess" - * samples. This is an issue for the "low-latency" A2DP codecs. - * - * Flushing the rest of the data (if any) is delayed after a timeout, - * selected on an average-rate basis: - * - * npackets = quantum / packet_samples - * write_end_time = npackets * timeout - * max_excess = quantum - sample_rate * write_end_time - * packet_time = packet_samples / sample_rate - * => 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_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) ? - (packet_time * (quantum - max_excess) / quantum) : 0; - - if (this->need_flush == NEED_FLUSH_FRAGMENT) { - reset_buffer(this); - this->fragment = true; - this->fragment_timeout = (packet_samples > 0) ? timeout : this->fragment_timeout; - goto again; - } - if (this->fragment_timeout > 0) { - timeout = this->fragment_timeout; - this->fragment_timeout = 0; - } + if (this->codec->bap) { + uint64_t timeout = get_next_bap_timeout(this); - reset_buffer(this); - if (now_time - this->last_error > SPA_NSEC_PER_SEC) { - if (get_transport_unused_size(this) == (int)this->fd_buffer_size) { - spa_log_trace(this->log, "%p: increase bitpool", this); - this->codec->increase_bitpool(this->codec_data); + reset_buffer(this); + if (!spa_list_is_empty(&port->ready)) { + spa_log_debug(this->log, "%p: flush after %d ns", this, (unsigned int)timeout); + if (timeout == 0) + goto again; + else + enable_flush(this, true, timeout); + } else { + enable_flush(this, false, 0); } - this->last_error = now_time; - } - if (!spa_list_is_empty(&port->ready)) { - spa_log_trace(this->log, "%p: flush after %d ns", this, (int)timeout); - if (timeout == 0) - goto again; - else - enable_flush(this, true, timeout); } else { - enable_flush(this, false, 0); + /* + * We cannot write all data we have at once, since this can exceed + * device buffers. We'll want a limited number of "excess" + * samples. This is an issue for the "low-latency" A2DP codecs. + * + * Flushing the rest of the data (if any) is delayed after a timeout, + * selected on an average-rate basis: + * + * npackets = quantum / packet_samples + * write_end_time = npackets * timeout + * max_excess = quantum - sample_rate * write_end_time + * packet_time = packet_samples / sample_rate + * => timeout = (quantum - max_excess)/quantum * packet_time + */ + uint64_t max_excess = 2*256; + 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) ? + (packet_time * (quantum - max_excess) / quantum) : 0; + + if (this->need_flush == NEED_FLUSH_FRAGMENT) { + reset_buffer(this); + this->fragment = true; + this->fragment_timeout = (packet_samples > 0) ? timeout : this->fragment_timeout; + goto again; + } + if (this->fragment_timeout > 0) { + timeout = this->fragment_timeout; + this->fragment_timeout = 0; + } + + reset_buffer(this); + if (now_time - this->last_error > SPA_NSEC_PER_SEC) { + if (get_transport_unused_size(this) == (int)this->fd_buffer_size) { + spa_log_trace(this->log, "%p: increase bitpool", this); + this->codec->increase_bitpool(this->codec_data); + } + this->last_error = now_time; + } + if (!spa_list_is_empty(&port->ready)) { + spa_log_trace(this->log, "%p: flush after %d ns", this, (int)timeout); + if (timeout == 0) + goto again; + else + enable_flush(this, true, timeout); + } else { + enable_flush(this, false, 0); + } } } else { @@ -786,7 +830,7 @@ again: return 0; } -static void a2dp_on_flush(struct spa_source *source) +static void media_on_flush(struct spa_source *source) { struct impl *this = source->data; @@ -807,7 +851,7 @@ static void a2dp_on_flush(struct spa_source *source) flush_data(this, this->current_time); } -static void a2dp_on_flush_timeout(struct spa_source *source) +static void media_on_flush_timeout(struct spa_source *source) { struct impl *this = source->data; uint64_t exp; @@ -825,7 +869,7 @@ static void a2dp_on_flush_timeout(struct spa_source *source) flush_data(this, this->current_time); } -static void a2dp_on_timeout(struct spa_source *source) +static void media_on_timeout(struct spa_source *source) { struct impl *this = source->data; struct port *port = &this->port; @@ -843,7 +887,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)) { @@ -883,10 +927,11 @@ static void a2dp_on_timeout(struct spa_source *source) static int do_start(struct impl *this) { - int i, res, val, size; + int res, val, size; struct port *port; socklen_t len; uint8_t *conf; + uint32_t flags; if (this->started) return 0; @@ -895,7 +940,7 @@ static int do_start(struct impl *this) this->following = is_following(this); - spa_log_debug(this->log, "%p: start following:%d", this, this->following); + spa_log_debug(this->log, "%p: start following:%d", this, this->following); if ((res = spa_bt_transport_acquire(this->transport, false)) < 0) return res; @@ -905,10 +950,16 @@ static int do_start(struct impl *this) conf = this->transport->configuration; size = this->transport->configuration_len; - for (i = 0; i < size; i++) - spa_log_debug(this->log, " %d: %02x", i, conf[i]); + spa_log_debug(this->log, "Transport configuration:"); + spa_log_hexdump(this->log, SPA_LOG_LEVEL_DEBUG, 2, conf, (size_t)size); - this->codec_data = this->codec->init(this->codec, 0, + if (this->codec->bap) + flags = MEDIA_CODEC_FLAG_SINK; + else + flags = this->is_duplex ? MEDIA_CODEC_FLAG_SINK : 0; + + this->codec_data = this->codec->init(this->codec, + flags, this->transport->configuration, this->transport->configuration_len, &port->current_format, @@ -917,8 +968,9 @@ static int do_start(struct impl *this) if (this->codec_data == NULL) return -EIO; - spa_log_info(this->log, "%p: using A2DP codec %s, delay:%"PRIi64" ms", this, this->codec->description, - (int64_t)(spa_bt_transport_get_delay_nsec(this->transport) / SPA_NSEC_PER_MSEC)); + spa_log_info(this->log, "%p: using %s codec %s, delay:%"PRIi64" ms", this, + this->codec->bap ? "BAP" : "A2DP", this->codec->description, + (int64_t)(spa_bt_transport_get_delay_nsec(this->transport) / SPA_NSEC_PER_MSEC)); this->seqnum = 0; @@ -929,8 +981,7 @@ static int do_start(struct impl *this) return -EIO; } - spa_log_debug(this->log, "%p: block_size %d", this, - this->block_size); + spa_log_debug(this->log, "%p: block_size %d", this, this->block_size); val = this->codec->send_buf_size > 0 /* The kernel doubles the SO_SNDBUF option value set by setsockopt(). */ @@ -960,21 +1011,21 @@ static int do_start(struct impl *this) this->source.data = this; this->source.fd = this->timerfd; - this->source.func = a2dp_on_timeout; + this->source.func = media_on_timeout; this->source.mask = SPA_IO_IN; this->source.rmask = 0; spa_loop_add_source(this->data_loop, &this->source); this->flush_timer_source.data = this; this->flush_timer_source.fd = this->flush_timerfd; - this->flush_timer_source.func = a2dp_on_flush_timeout; + this->flush_timer_source.func = media_on_flush_timeout; this->flush_timer_source.mask = SPA_IO_IN; this->flush_timer_source.rmask = 0; spa_loop_add_source(this->data_loop, &this->flush_timer_source); this->flush_source.data = this; this->flush_source.fd = this->transport->fd; - this->flush_source.func = a2dp_on_flush; + this->flush_source.func = media_on_flush; this->flush_source.mask = 0; this->flush_source.rmask = 0; spa_loop_add_source(this->data_loop, &this->flush_source); @@ -995,6 +1046,9 @@ static int do_remove_source(struct spa_loop *loop, struct impl *this = user_data; struct itimerspec ts; + this->start_time = 0; + this->total_samples = 0; + if (this->source.loop) spa_loop_remove_source(this->data_loop, &this->source); ts.it_value.tv_sec = 0; @@ -1076,8 +1130,10 @@ static void emit_node_info(struct impl *this, bool full) { struct spa_dict_item node_info_items[] = { { SPA_KEY_DEVICE_API, "bluez5" }, - { SPA_KEY_MEDIA_CLASS, "Audio/Sink" }, - { SPA_KEY_NODE_DRIVER, "true" }, + { SPA_KEY_MEDIA_CLASS, this->is_output ? "Audio/Sink" : "Stream/Input/Audio" }, + { "media.name", ((this->transport && this->transport->device->name) ? + this->transport->device->name : this->codec->bap ? "BAP" : "A2DP" ) }, + { SPA_KEY_NODE_DRIVER, this->is_output ? "true" : "false" }, }; uint64_t old = full ? this->info.change_mask : 0; if (full) @@ -1195,6 +1251,7 @@ impl_node_port_enum_params(void *object, int seq, return -EIO; if ((res = this->codec->enum_config(this->codec, + this->is_duplex ? MEDIA_CODEC_FLAG_SINK : 0, this->transport->configuration, this->transport->configuration_len, id, result.index, &b, ¶m)) != 1) @@ -1457,8 +1514,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 +1754,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); @@ -1704,15 +1764,31 @@ impl_init(const struct spa_handle_factory *factory, spa_log_error(this->log, "a transport is needed"); return -EINVAL; } - if (this->transport->a2dp_codec == NULL) { + if (this->transport->media_codec == NULL) { spa_log_error(this->log, "a transport codec is needed"); return -EINVAL; } - this->codec = this->transport->a2dp_codec; + + this->codec = this->transport->media_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 ? MEDIA_CODEC_FLAG_SINK : 0, this->transport->device->settings); + if (this->codec->bap) + this->is_output = this->transport->bap_initiator; + else + this->is_output = true; + reset_props(this, &this->props); spa_bt_transport_add_listener(this->transport, @@ -1752,12 +1828,22 @@ impl_enum_interface_info(const struct spa_handle_factory *factory, 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 the a2dp" }, + { SPA_KEY_FACTORY_DESCRIPTION, "Play audio with the media" }, { SPA_KEY_FACTORY_USAGE, SPA_KEY_API_BLUEZ5_TRANSPORT"=<transport>" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); +const struct spa_handle_factory spa_media_sink_factory = { + SPA_VERSION_HANDLE_FACTORY, + SPA_NAME_API_BLUEZ5_MEDIA_SINK, + &info, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; + +/* Retained for backward compatibility: */ const struct spa_handle_factory spa_a2dp_sink_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_BLUEZ5_A2DP_SINK, diff --git a/spa/plugins/bluez5/a2dp-source.c b/spa/plugins/bluez5/media-source.c similarity index 78% rename from spa/plugins/bluez5/a2dp-source.c rename to spa/plugins/bluez5/media-source.c index e5618ee272680a33d89e7624f141d264ade426a6..c03d767b1d87275f764e2a7e55c1a8bfe3c93a72 100644 --- a/spa/plugins/bluez5/a2dp-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -1,4 +1,4 @@ -/* Spa A2DP Source +/* Spa Media Source * * Copyright © 2018 Wim Taymans * Copyright © 2019 Collabora Ltd. @@ -54,24 +54,22 @@ #include "defs.h" #include "rtp.h" -#include "a2dp-codecs.h" +#include "media-codecs.h" -static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.source.a2dp"); +static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.source.media"); #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,27 +137,34 @@ 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; - const struct a2dp_codec *codec; + uint64_t current_time; + uint64_t next_time; + + const struct media_codec *codec; bool codec_props_changed; void *codec_props; void *codec_data; 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) +static void media_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; + spa_bt_decode_buffer_write_packet(&port->buffer, decoded); - /* process the buffer if IO does not have any */ - if (io != NULL && io->status != SPA_STATUS_HAVE_DATA) { - struct buffer *b; + dt = SPA_TIMESPEC_TO_NSEC(&this->now); + this->now = now; + dt = SPA_TIMESPEC_TO_NSEC(&this->now) - dt; - if (io->buffer_id < port->n_buffers) - recycle_buffer(this, port, io->buffer_id); + 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)); - b = spa_list_first(&port->ready, struct buffer, link); - spa_list_remove(&b->link); - b->outstanding = true; - - 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: @@ -628,7 +533,7 @@ static int set_duplex_timeout(struct impl *this, uint64_t timeout) this->duplex_timerfd, 0, &ts, NULL); } -static void a2dp_on_duplex_timeout(struct spa_source *source) +static void media_on_duplex_timeout(struct spa_source *source) { struct impl *this = source->data; uint64_t exp; @@ -638,13 +543,83 @@ static void a2dp_on_duplex_timeout(struct spa_source *source) set_duplex_timeout(this, this->duplex_timeout); - a2dp_on_ready_read(source); + media_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 media_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; struct port *port = &this->port; + uint32_t flags; if (this->transport_acquired) return 0; @@ -656,7 +631,13 @@ static int transport_start(struct impl *this) this->transport_acquired = true; - this->codec_data = this->codec->init(this->codec, 0, + if (this->codec->bap) + flags = 0; + else + flags = this->is_duplex ? 0 : MEDIA_CODEC_FLAG_SINK; + + this->codec_data = this->codec->init(this->codec, + flags, this->transport->configuration, this->transport->configuration_len, &port->current_format, @@ -665,7 +646,8 @@ static int transport_start(struct impl *this) if (this->codec_data == NULL) return -EIO; - spa_log_info(this->log, "%p: using A2DP codec %s", this, this->codec->description); + spa_log_info(this->log, "%p: using %s codec %s", this, + this->codec->bap ? "BAP" : "A2DP", this->codec->description); val = fcntl(this->transport->fd, F_GETFL); if (fcntl(this->transport->fd, F_SETFL, val | O_NONBLOCK) < 0) @@ -683,15 +665,21 @@ 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.func = media_on_ready_read; this->source.mask = SPA_IO_IN; this->source.rmask = 0; spa_loop_add_source(this->data_loop, &this->source); @@ -704,20 +692,31 @@ 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; + this->source.func = media_on_duplex_timeout; this->source.mask = SPA_IO_IN; 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 = media_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,15 +728,15 @@ 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) + this->is_duplex || this->codec->bap) res = transport_start(this); this->started = true; @@ -753,6 +752,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 +761,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 +792,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 +847,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 : this->codec->bap ? "BAP" : "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 +975,7 @@ impl_node_port_enum_params(void *object, int seq, return -EIO; if ((res = this->codec->enum_config(this->codec, + this->is_duplex ? 0 : MEDIA_CODEC_FLAG_SINK, this->transport->configuration, this->transport->configuration_len, id, result.index, &b, ¶m)) != 1) @@ -991,11 +999,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 +1029,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 +1073,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 +1230,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 +1260,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 +1352,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 +1367,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 +1451,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,10 +1555,14 @@ 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) + if ((str = spa_dict_lookup(info, "bluez5.media-source-role")) != NULL) this->is_input = spa_streq(str, "input"); if ((str = spa_dict_lookup(info, "api.bluez5.a2dp-duplex")) != NULL) this->is_duplex = spa_atob(str); @@ -1464,11 +1572,11 @@ impl_init(const struct spa_handle_factory *factory, spa_log_error(this->log, "a transport is needed"); return -EINVAL; } - if (this->transport->a2dp_codec == NULL) { + if (this->transport->media_codec == NULL) { spa_log_error(this->log, "a transport codec is needed"); return -EINVAL; } - this->codec = this->transport->a2dp_codec; + this->codec = this->transport->media_codec; if (this->is_duplex) { if (!this->codec->duplex_codec) { @@ -1478,15 +1586,23 @@ 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->bap) + this->is_input = this->transport->bap_initiator; if (this->codec->init_props != NULL) this->codec_props = this->codec->init_props(this->codec, + this->is_duplex ? 0 : MEDIA_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 { @@ -1521,12 +1637,22 @@ impl_enum_interface_info(const struct spa_handle_factory *factory, static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Collabora Ltd. <contact@collabora.com>" }, - { SPA_KEY_FACTORY_DESCRIPTION, "Capture bluetooth audio with a2dp" }, + { SPA_KEY_FACTORY_DESCRIPTION, "Capture bluetooth audio with media" }, { SPA_KEY_FACTORY_USAGE, SPA_KEY_API_BLUEZ5_TRANSPORT"=<transport>" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); +const struct spa_handle_factory spa_media_source_factory = { + SPA_VERSION_HANDLE_FACTORY, + SPA_NAME_API_BLUEZ5_MEDIA_SOURCE, + &info, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; + +/* Retained for backward compatibility */ const struct spa_handle_factory spa_a2dp_source_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_BLUEZ5_A2DP_SOURCE, diff --git a/spa/plugins/bluez5/meson.build b/spa/plugins/bluez5/meson.build index 03f986b37249096b2ee37c67f3d5ea438301b914..104a0d20ef0c66a745fae288a14b41aa0ad2c3a0 100644 --- a/spa/plugins/bluez5/meson.build +++ b/spa/plugins/bluez5/meson.build @@ -17,9 +17,9 @@ cdata.set('HAVE_BLUEZ_5_HCI', dependency('bluez', version: '< 6', required: fals bluez5_sources = [ 'plugin.c', 'codec-loader.c', - 'a2dp-codecs.c', - 'a2dp-sink.c', - 'a2dp-source.c', + 'media-codecs.c', + 'media-sink.c', + 'media-source.c', 'sco-sink.c', 'sco-source.c', 'sco-io.c', @@ -59,7 +59,7 @@ bluez5lib = shared_library('spa-bluez5', codec_args = [ '-DCODEC_PLUGIN' ] bluez_codec_sbc = shared_library('spa-codec-bluez5-sbc', - [ 'a2dp-codec-sbc.c', 'a2dp-codecs.c' ], + [ 'a2dp-codec-sbc.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, dependencies : [ spa_dep, sbc_dep ], @@ -67,7 +67,7 @@ bluez_codec_sbc = shared_library('spa-codec-bluez5-sbc', install_dir : spa_plugindir / 'bluez5') bluez_codec_faststream = shared_library('spa-codec-bluez5-faststream', - [ 'a2dp-codec-faststream.c', 'a2dp-codecs.c' ], + [ 'a2dp-codec-faststream.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, dependencies : [ spa_dep, sbc_dep ], @@ -76,7 +76,7 @@ bluez_codec_faststream = shared_library('spa-codec-bluez5-faststream', if fdk_aac_dep.found() bluez_codec_aac = shared_library('spa-codec-bluez5-aac', - [ 'a2dp-codec-aac.c', 'a2dp-codecs.c' ], + [ 'a2dp-codec-aac.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, dependencies : [ spa_dep, fdk_aac_dep ], @@ -86,7 +86,7 @@ endif if aptx_dep.found() bluez_codec_aptx = shared_library('spa-codec-bluez5-aptx', - [ 'a2dp-codec-aptx.c', 'a2dp-codecs.c' ], + [ 'a2dp-codec-aptx.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, dependencies : [ spa_dep, aptx_dep, sbc_dep ], @@ -102,7 +102,7 @@ if ldac_dep.found() ldac_dep += ldac_abr_dep endif bluez_codec_ldac = shared_library('spa-codec-bluez5-ldac', - [ 'a2dp-codec-ldac.c', 'a2dp-codecs.c' ], + [ 'a2dp-codec-ldac.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : ldac_args, dependencies : [ spa_dep, ldac_dep ], @@ -111,13 +111,33 @@ 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' ], + [ 'a2dp-codec-lc3plus.c', 'media-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', 'media-codecs.c' ], + include_directories : [ configinc ], + c_args : opus_args, + dependencies : [ spa_dep, opus_dep, mathlib ], + install : true, + install_dir : spa_plugindir / 'bluez5') +endif + +if get_option('bluez5-codec-lc3').allowed() and lc3_dep.found() + bluez_codec_lc3 = shared_library('spa-codec-bluez5-lc3', + [ 'bap-codec-lc3.c', 'media-codecs.c' ], + include_directories : [ configinc ], + c_args : codec_args, + dependencies : [ spa_dep, lc3_dep, mathlib ], + install : true, + install_dir : spa_plugindir / 'bluez5') +endif diff --git a/spa/plugins/bluez5/plugin.c b/spa/plugins/bluez5/plugin.c index bb09a22a280617bb3be4e0b8a07c0b50170b9a12..4a06f81d344e3ae9398ec9fbb52cd8e905ee7b4c 100644 --- a/spa/plugins/bluez5/plugin.c +++ b/spa/plugins/bluez5/plugin.c @@ -29,10 +29,12 @@ extern const struct spa_handle_factory spa_bluez5_dbus_factory; extern const struct spa_handle_factory spa_bluez5_device_factory; -extern const struct spa_handle_factory spa_a2dp_sink_factory; -extern const struct spa_handle_factory spa_a2dp_source_factory; +extern const struct spa_handle_factory spa_media_sink_factory; +extern const struct spa_handle_factory spa_media_source_factory; extern const struct spa_handle_factory spa_sco_sink_factory; extern const struct spa_handle_factory spa_sco_source_factory; +extern const struct spa_handle_factory spa_a2dp_sink_factory; +extern const struct spa_handle_factory spa_a2dp_source_factory; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) @@ -48,10 +50,10 @@ int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *factory = &spa_bluez5_device_factory; break; case 2: - *factory = &spa_a2dp_sink_factory; + *factory = &spa_media_sink_factory; break; case 3: - *factory = &spa_a2dp_source_factory; + *factory = &spa_media_source_factory; break; case 4: *factory = &spa_sco_sink_factory; @@ -59,6 +61,12 @@ int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t case 5: *factory = &spa_sco_source_factory; break; + case 6: + *factory = &spa_a2dp_sink_factory; + break; + case 7: + *factory = &spa_a2dp_source_factory; + break; default: return 0; } diff --git a/spa/plugins/bluez5/quirks.c b/spa/plugins/bluez5/quirks.c index 7612e9ae6d784aae14f4eef590593cc2cfb27fae..ba47414c8a12a79389e2ac7d74ff1e1f77914549 100644 --- a/spa/plugins/bluez5/quirks.c +++ b/spa/plugins/bluez5/quirks.c @@ -56,7 +56,6 @@ #include <spa/utils/json.h> #include <spa/utils/string.h> -#include "a2dp-codecs.h" #include "defs.h" static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.bluez5.quirks"); 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..0edd1a33cb089480542709a4a1fb0ba258d50ba0 100644 --- a/spa/plugins/libcamera/libcamera-device.cpp +++ b/spa/plugins/libcamera/libcamera-device.cpp @@ -54,59 +54,55 @@ using namespace libcamera; -struct props { - char device[128]; - char device_name[128]; -}; - -static void reset_props(struct props *props) -{ - spa_zero(*props); -} +namespace { struct impl { struct spa_handle handle; - struct spa_device device; + struct spa_device device = {}; struct spa_log *log; - struct props props; + std::string device_id; struct spa_hook_list hooks; - CameraManager *manager; + std::shared_ptr<CameraManager> manager; std::shared_ptr<Camera> camera; + + impl(spa_log *log, + std::shared_ptr<CameraManager> manager, + std::shared_ptr<Camera> camera, + std::string device_id); }; -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,24 +112,26 @@ 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(); info.change_mask = SPA_DEVICE_CHANGE_MASK_PROPS; #define ADD_ITEM(key, value) items[n_items++] = SPA_DICT_ITEM_INIT(key, value) - snprintf(path, sizeof(path), "libcamera:%s", impl->props.device); + snprintf(path, sizeof(path), "libcamera:%s", impl->device_id.c_str()); 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->device_id.c_str()); + + 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); - snprintf(name, sizeof(name), "libcamera_device.%s", impl->props.device); + snprintf(name, sizeof(name), "libcamera_device.%s", impl->device_id.c_str()); ADD_ITEM(SPA_KEY_DEVICE_NAME, name); #undef ADD_ITEM @@ -236,13 +234,30 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void static int impl_clear(struct spa_handle *handle) { - struct impl *impl = (struct impl *) handle; - if (impl->manager) - libcamera_manager_release(impl->manager); - impl->manager = NULL; + std::destroy_at(reinterpret_cast<impl *>(handle)); return 0; } +impl::impl(spa_log *log, + std::shared_ptr<CameraManager> manager, + std::shared_ptr<Camera> camera, + std::string device_id) + : handle({ SPA_VERSION_HANDLE, impl_get_interface, impl_clear }), + log(log), + device_id(std::move(device_id)), + manager(std::move(manager)), + camera(std::move(camera)) +{ + libcamera_log_topic_init(log); + + spa_hook_list_init(&hooks); + + device.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Device, + SPA_VERSION_DEVICE, + &impl_device, this); +} + static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) @@ -257,44 +272,32 @@ impl_init(const struct spa_handle_factory *factory, const struct spa_support *support, uint32_t n_support) { - struct impl *impl; const char *str; int res; 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; - - impl->log = (struct spa_log*) spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); - libcamera_log_topic_init(impl->log); - - spa_hook_list_init(&impl->hooks); + auto log = static_cast<spa_log *>(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log)); - impl->device.iface = SPA_INTERFACE_INIT( - SPA_TYPE_INTERFACE_Device, - SPA_VERSION_DEVICE, - &impl_device, impl); - - reset_props(&impl->props); + auto manager = libcamera_manager_acquire(res); + if (!manager) { + spa_log_error(log, "can't start camera manager: %s", spa_strerror(res)); + return res; + } + std::string device_id; if (info && (str = spa_dict_lookup(info, SPA_KEY_API_LIBCAMERA_PATH))) - strncpy(impl->props.device, str, sizeof(impl->props.device)); + device_id = str; - impl->manager = libcamera_manager_acquire(); - if (impl->manager == NULL) { - res = -errno; - spa_log_error(impl->log, "can't start camera manager: %s", spa_strerror(res)); - return res; - } - - impl->camera = impl->manager->get(impl->props.device); - if (impl->camera == NULL) { - spa_log_error(impl->log, "unknown camera id %s", impl->props.device); - libcamera_manager_release(impl->manager); + auto camera = manager->get(device_id); + if (!camera) { + spa_log_error(log, "unknown camera id %s", device_id.c_str()); return -ENOENT; } + + new (handle) impl(log, std::move(manager), std::move(camera), std::move(device_id)); + return 0; } diff --git a/spa/plugins/libcamera/libcamera-manager.cpp b/spa/plugins/libcamera/libcamera-manager.cpp index b9243ad86982711eaee8f90067ed841757f0e63d..8987c93b7a5a168207d117e186c9d670403d9b0c 100644 --- a/spa/plugins/libcamera/libcamera-manager.cpp +++ b/spa/plugins/libcamera/libcamera-manager.cpp @@ -29,7 +29,10 @@ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> - +#include <utility> +#include <mutex> +#include <optional> +#include <queue> #include <libcamera/camera.h> #include <libcamera/camera_manager.h> @@ -54,67 +57,65 @@ using namespace libcamera; #define MAX_DEVICES 64 -struct global { - int ref; - CameraManager *manager; -}; - -static struct global global; +namespace { struct device { uint32_t id; std::shared_ptr<Camera> camera; }; -typedef struct impl { +struct impl { struct spa_handle handle; - struct spa_device device; + struct spa_device device = {}; struct spa_log *log; - struct spa_loop *main_loop; + struct spa_loop_utils *loop_utils; struct spa_hook_list hooks; - uint64_t info_all; - struct spa_device_info info; + static constexpr uint64_t info_all = SPA_DEVICE_CHANGE_MASK_FLAGS | SPA_DEVICE_CHANGE_MASK_PROPS; + struct spa_device_info info = SPA_DEVICE_INFO_INIT(); - CameraManager *manager; + std::shared_ptr<CameraManager> manager; void addCamera(std::shared_ptr<libcamera::Camera> camera); - void removeCamera(std::shared_ptr<libcamera::Camera> camera); + void removeCamera(std::shared_ptr<libcamera::Camera> camera); struct device devices[MAX_DEVICES]; - uint32_t n_devices; -} Impl; + uint32_t n_devices = 0; -int libcamera_manager_release(CameraManager *manager) -{ - if (global.manager != manager) - return -EINVAL; + struct hotplug_event { + enum class type { add, remove } type; + std::shared_ptr<Camera> camera; + }; - if (--global.ref == 0) { - global.manager->stop(); - delete global.manager; - global.manager = NULL; + std::mutex hotplug_events_lock; + std::queue<hotplug_event> hotplug_events; + struct spa_source *hotplug_event_source; + + impl(spa_log *log, spa_loop_utils *loop_utils, spa_source *hotplug_event_source); + + ~impl() + { + spa_loop_utils_destroy_source(loop_utils, hotplug_event_source); } - return 0; +}; + } -CameraManager *libcamera_manager_acquire(void) +static std::weak_ptr<CameraManager> global_manager; + +std::shared_ptr<CameraManager> libcamera_manager_acquire(int& res) { - int res; + if (auto manager = global_manager.lock()) + return manager; - if (global.ref++ == 0) { - global.manager = new CameraManager(); - if (global.manager == NULL) - return NULL; + auto manager = std::make_shared<CameraManager>(); + if ((res = manager->start()) < 0) + return {}; - if ((res = global.manager->start()) < 0) { - libcamera_manager_release(global.manager); - errno = -res; - return NULL; - } - } - return global.manager; + global_manager = manager; + + return manager; } static struct device *add_device(struct impl *impl, std::shared_ptr<Camera> camera) @@ -127,15 +128,15 @@ static struct device *add_device(struct impl *impl, std::shared_ptr<Camera> came id = impl->n_devices++; device = &impl->devices[id]; device->id = id; - device->camera = camera; + device->camera = std::move(camera); return device; } -static struct device *find_device(struct impl *impl, std::shared_ptr<Camera> camera) +static struct device *find_device(struct impl *impl, const Camera *camera) { uint32_t i; for (i = 0; i < impl->n_devices; i++) { - if (impl->devices[i].camera == camera) + if (impl->devices[i].camera.get() == camera) return &impl->devices[i]; } return NULL; @@ -143,12 +144,14 @@ static struct device *find_device(struct impl *impl, std::shared_ptr<Camera> cam static void remove_device(struct impl *impl, struct device *device) { - *device = impl->devices[--impl->n_devices]; + device->camera.reset(); + *device = std::move(impl->devices[--impl->n_devices]); } static void clear_devices(struct impl *impl) { - impl->n_devices = 0; + while (impl->n_devices > 0) + impl->devices[--impl->n_devices].camera.reset(); } static int emit_object_info(struct impl *impl, struct device *device) @@ -176,66 +179,119 @@ static int emit_object_info(struct impl *impl, struct device *device) ADD_ITEM(SPA_KEY_API_LIBCAMERA_PATH, path); #undef ADD_ITEM - dict = SPA_DICT_INIT(items, n_items); - info.props = &dict; - spa_device_emit_object_info(&impl->hooks, id, &info); + dict = SPA_DICT_INIT(items, n_items); + info.props = &dict; + spa_device_emit_object_info(&impl->hooks, id, &info); return 1; } -void Impl::addCamera(std::shared_ptr<Camera> camera) +static void try_add_camera(struct impl *impl, std::shared_ptr<Camera> camera) { - struct impl *impl = this; struct device *device; - spa_log_info(impl->log, "new camera"); - - if ((device = find_device(impl, camera)) != NULL) + if ((device = find_device(impl, camera.get())) != NULL) return; - if ((device = add_device(impl, camera)) == NULL) + if ((device = add_device(impl, std::move(camera))) == NULL) return; + spa_log_info(impl->log, "camera added: %s", device->camera->id().c_str()); emit_object_info(impl, device); } -void Impl::removeCamera(std::shared_ptr<Camera> camera) +static void try_remove_camera(struct impl *impl, const Camera *camera) { - struct impl *impl = this; struct device *device; - spa_log_info(impl->log, "camera removed"); if ((device = find_device(impl, camera)) == NULL) return; + spa_log_info(impl->log, "camera removed: %s", device->camera->id().c_str()); remove_device(impl, device); } -static int start_monitor(struct impl *impl) +static void consume_hotplug_event(struct impl *impl, impl::hotplug_event& event) { - impl->manager->cameraAdded.connect(impl, &Impl::addCamera); - impl->manager->cameraRemoved.connect(impl, &Impl::removeCamera); - return 0; + auto& [ type, camera ] = event; + + switch (type) { + case impl::hotplug_event::type::add: + spa_log_info(impl->log, "camera appeared: %s", camera->id().c_str()); + try_add_camera(impl, std::move(camera)); + break; + case impl::hotplug_event::type::remove: + spa_log_info(impl->log, "camera disappeared: %s", camera->id().c_str()); + try_remove_camera(impl, camera.get()); + break; + } +} + +static void on_hotplug_event(void *data, std::uint64_t) +{ + auto impl = static_cast<struct impl *>(data); + + for (;;) { + std::optional<impl::hotplug_event> event; + + { + std::unique_lock guard(impl->hotplug_events_lock); + + if (!impl->hotplug_events.empty()) { + event = std::move(impl->hotplug_events.front()); + impl->hotplug_events.pop(); + } + } + + if (!event) + break; + + consume_hotplug_event(impl, *event); + } +} + +void impl::addCamera(std::shared_ptr<Camera> camera) +{ + { + std::unique_lock guard(hotplug_events_lock); + hotplug_events.push({ hotplug_event::type::add, std::move(camera) }); + } + + spa_loop_utils_signal_event(loop_utils, hotplug_event_source); +} + +void impl::removeCamera(std::shared_ptr<Camera> camera) +{ + { + std::unique_lock guard(hotplug_events_lock); + hotplug_events.push({ hotplug_event::type::remove, std::move(camera) }); + } + + spa_loop_utils_signal_event(loop_utils, hotplug_event_source); +} + +static void start_monitor(struct impl *impl) +{ + impl->manager->cameraAdded.connect(impl, &impl::addCamera); + impl->manager->cameraRemoved.connect(impl, &impl::removeCamera); } static int stop_monitor(struct impl *impl) { - if (impl->manager != NULL) { - impl->manager->cameraAdded.disconnect(impl, &Impl::addCamera); - impl->manager->cameraRemoved.disconnect(impl, &Impl::removeCamera); + if (impl->manager) { + impl->manager->cameraAdded.disconnect(impl, &impl::addCamera); + impl->manager->cameraRemoved.disconnect(impl, &impl::removeCamera); } clear_devices (impl); return 0; } -static int enum_devices(struct impl *impl) +static void collect_existing_devices(struct impl *impl) { auto cameras = impl->manager->cameras(); - for (const std::shared_ptr<Camera> &cam : cameras) { - impl->addCamera(cam); - } - return 0; + for (std::shared_ptr<Camera>& camera : cameras) + try_add_camera(impl, std::move(camera)); } static const struct spa_dict_item device_info_items[] = { @@ -262,9 +318,7 @@ static void impl_hook_removed(struct spa_hook *hook) struct impl *impl = (struct impl*)hook->priv; if (spa_hook_list_is_empty(&impl->hooks)) { stop_monitor(impl); - if (impl->manager) - libcamera_manager_release(impl->manager); - impl->manager = NULL; + impl->manager.reset(); } } @@ -274,26 +328,29 @@ impl_device_add_listener(void *object, struct spa_hook *listener, { int res; struct impl *impl = (struct impl*) object; - struct spa_hook_list save; + struct spa_hook_list save; + bool had_manager = !!impl->manager; spa_return_val_if_fail(impl != NULL, -EINVAL); spa_return_val_if_fail(events != NULL, -EINVAL); - impl->manager = libcamera_manager_acquire(); - if (impl->manager == NULL) - return -errno; + if (!impl->manager && !(impl->manager = libcamera_manager_acquire(res))) + return res; - spa_hook_list_isolate(&impl->hooks, &save, listener, events, data); + spa_hook_list_isolate(&impl->hooks, &save, listener, events, data); emit_device_info(impl, true); - if ((res = enum_devices(impl)) < 0) - return res; - - if ((res = start_monitor(impl)) < 0) - return res; + if (had_manager) { + for (std::size_t i = 0; i < impl->n_devices; i++) + emit_object_info(impl, &impl->devices[i]); + } + else { + collect_existing_devices(impl); + start_monitor(impl); + } - spa_hook_list_join(&impl->hooks, &save); + spa_hook_list_join(&impl->hooks, &save); listener->removed = impl_hook_removed; listener->priv = impl; @@ -325,14 +382,30 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void static int impl_clear(struct spa_handle *handle) { - struct impl *impl = (struct impl *) handle; + auto impl = reinterpret_cast<struct impl *>(handle); + stop_monitor(impl); - if (impl->manager) - libcamera_manager_release(impl->manager); - impl->manager = NULL; + std::destroy_at(impl); + return 0; } +impl::impl(spa_log *log, spa_loop_utils *loop_utils, spa_source *hotplug_event_source) + : handle({ SPA_VERSION_HANDLE, impl_get_interface, impl_clear }), + log(log), + loop_utils(loop_utils), + hotplug_event_source(hotplug_event_source) +{ + libcamera_log_topic_init(log); + + spa_hook_list_init(&hooks); + + device.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Device, + SPA_VERSION_DEVICE, + &impl_device, this); +} + static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) @@ -347,35 +420,25 @@ 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; - - impl->log = (struct spa_log*)spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); - libcamera_log_topic_init(impl->log); + auto log = static_cast<spa_log *>(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log)); - impl->main_loop = (struct spa_loop*)spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); - if (impl->main_loop == NULL) { - spa_log_error(impl->log, "a main-loop is needed"); + auto loop_utils = static_cast<spa_loop_utils *>(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_LoopUtils)); + if (!loop_utils) { + spa_log_error(log, "a " SPA_TYPE_INTERFACE_LoopUtils " is needed"); return -EINVAL; } - spa_hook_list_init(&impl->hooks); - impl->device.iface = SPA_INTERFACE_INIT( - SPA_TYPE_INTERFACE_Device, - SPA_VERSION_DEVICE, - &impl_device, impl); + auto hotplug_event_source = spa_loop_utils_add_event(loop_utils, on_hotplug_event, handle); + if (!hotplug_event_source) { + int res = -errno; + spa_log_error(log, "failed to create hotplug event: %m"); + return res; + } - impl->info = SPA_DEVICE_INFO_INIT(); - impl->info_all = SPA_DEVICE_CHANGE_MASK_FLAGS | - SPA_DEVICE_CHANGE_MASK_PROPS; - impl->info.flags = 0; + new (handle) impl(log, loop_utils, hotplug_event_source); return 0; } diff --git a/spa/plugins/libcamera/libcamera-manager.hpp b/spa/plugins/libcamera/libcamera-manager.hpp index 53e488bc52f7386641bcc10780198cd576928cc0..4336a392ec2ae6b55cb8bc6d8739babc4e385a31 100644 --- a/spa/plugins/libcamera/libcamera-manager.hpp +++ b/spa/plugins/libcamera/libcamera-manager.hpp @@ -22,11 +22,8 @@ * DEALINGS IN THE SOFTWARE. */ -#include <libcamera/camera_manager.h> - -#include <linux/media.h> +#include <memory> -using namespace libcamera; +#include <libcamera/camera_manager.h> -CameraManager *libcamera_manager_acquire(void); -int libcamera_manager_release(CameraManager *manager); +std::shared_ptr<libcamera::CameraManager> libcamera_manager_acquire(int& res); diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 948b4413654ce76a6c434e4aee2fe022298a4318..9b6d8246dac56920f978212d5b39ee7e27401bd5 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -31,6 +31,7 @@ #include <sys/stat.h> #include <fcntl.h> #include <deque> +#include <optional> #include <spa/support/plugin.h> #include <spa/support/log.h> @@ -60,15 +61,9 @@ #include "libcamera.h" #include "libcamera-manager.hpp" -struct props { - char device[128]; - char device_name[128]; -}; +using namespace libcamera; -static void reset_props(struct props *props) -{ - spa_zero(*props); -} +namespace { #define MAX_BUFFERS 32 #define MASK_BUFFERS 31 @@ -97,75 +92,97 @@ struct control { struct port { struct impl *impl; - bool have_format; - struct spa_video_info current_format; - struct spa_fraction rate; + std::optional<spa_video_info> current_format; + + struct spa_fraction rate = {}; StreamConfiguration streamConfig; - uint32_t memtype; + uint32_t memtype = 0; struct control controls[MAX_CONTROLS]; - uint32_t n_controls; + uint32_t n_controls = 0; struct buffer buffers[MAX_BUFFERS]; - uint32_t n_buffers; + uint32_t n_buffers = 0; struct spa_list queue; - struct spa_ringbuffer ring; + struct spa_ringbuffer ring = SPA_RINGBUFFER_INIT(); uint32_t ring_ids[MAX_BUFFERS]; - uint64_t info_all; - struct spa_port_info info; - struct spa_io_buffers *io; - struct spa_io_sequence *control; + static constexpr uint64_t info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; + struct spa_port_info info = SPA_PORT_INFO_INIT(); + struct spa_io_buffers *io = nullptr; + struct spa_io_sequence *control = nullptr; struct spa_param_info params[8]; - uint32_t fmt_index; - bool next_fmt; + uint32_t fmt_index = 0; PixelFormat enum_fmt; - uint32_t size_index; - bool next_size; + uint32_t size_index = 0; + + port(struct impl *impl) + : impl(impl) + { + spa_list_init(&queue); + + params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); + params[1] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); + params[2] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); + params[3] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); + params[4] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + params[5] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + + info.flags = SPA_PORT_FLAG_LIVE | SPA_PORT_FLAG_PHYSICAL | SPA_PORT_FLAG_TERMINAL; + info.params = params; + info.n_params = 6; + } }; struct impl { struct spa_handle handle; - struct spa_node node; + struct spa_node node = {}; struct spa_log *log; struct spa_loop *data_loop; struct spa_system *system; - uint64_t info_all; - struct spa_node_info info; + static constexpr uint64_t info_all = + SPA_NODE_CHANGE_MASK_FLAGS | + SPA_NODE_CHANGE_MASK_PROPS | + SPA_NODE_CHANGE_MASK_PARAMS; + struct spa_node_info info = SPA_NODE_INFO_INIT(); struct spa_param_info params[8]; - struct props props; + + std::string device_id; + std::string device_name; struct spa_hook_list hooks; - struct spa_callbacks callbacks; + struct spa_callbacks callbacks = {}; - struct port out_ports[1]; + std::array<port, 1> out_ports; - struct spa_io_position *position; - struct spa_io_clock *clock; + struct spa_io_position *position = nullptr; + struct spa_io_clock *clock = nullptr; - CameraManager *manager; + std::shared_ptr<CameraManager> manager; std::shared_ptr<Camera> camera; - FrameBufferAllocator *allocator; + FrameBufferAllocator *allocator = nullptr; std::vector<std::unique_ptr<libcamera::Request>> requestPool; std::deque<libcamera::Request *> pendingRequests; void requestComplete(libcamera::Request *request); - unsigned int have_config; std::unique_ptr<CameraConfiguration> config; - struct spa_source source; + struct spa_source source = {}; - unsigned int active:1; - unsigned int acquired:1; + bool active = false; + bool acquired = false; + + impl(spa_log *log, spa_loop *data_loop, spa_system *system, + std::shared_ptr<CameraManager> manager, std::shared_ptr<Camera> camera, std::string device_id); }; -typedef struct impl Impl; +} #define CHECK_PORT(impl,direction,port_id) ((direction) == SPA_DIRECTION_OUTPUT && (port_id) == 0) @@ -198,22 +215,20 @@ next: switch (id) { case SPA_PARAM_PropInfo: { - struct props *p = &impl->props; - switch (result.index) { case 0: param = (struct spa_pod*)spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_device), SPA_PROP_INFO_description, SPA_POD_String("The libcamera device"), - SPA_PROP_INFO_type, SPA_POD_String(p->device)); + SPA_PROP_INFO_type, SPA_POD_String(impl->device_id.c_str())); break; case 1: param = (struct spa_pod*)spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_deviceName), SPA_PROP_INFO_description, SPA_POD_String("The libcamera device name"), - SPA_PROP_INFO_type, SPA_POD_String(p->device_name)); + SPA_PROP_INFO_type, SPA_POD_String(impl->device_name.c_str())); break; default: return 0; @@ -222,14 +237,12 @@ next: } case SPA_PARAM_Props: { - struct props *p = &impl->props; - switch (result.index) { case 0: param = (struct spa_pod*)spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, id, - SPA_PROP_device, SPA_POD_String(p->device), - SPA_PROP_deviceName, SPA_POD_String(p->device_name)); + SPA_PROP_device, SPA_POD_String(impl->device_id.c_str()), + SPA_PROP_deviceName, SPA_POD_String(impl->device_name.c_str())); break; default: return 0; @@ -262,15 +275,22 @@ static int impl_node_set_param(void *object, switch (id) { case SPA_PARAM_Props: { - struct props *p = &impl->props; - if (param == NULL) { - reset_props(p); + impl->device_id.clear(); + impl->device_name.clear(); return 0; } - spa_pod_parse_object(param, + + char device[128]; + int res = spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, - SPA_PROP_device, SPA_POD_OPT_Stringn(p->device, sizeof(p->device))); + SPA_PROP_device, SPA_POD_OPT_Stringn(device, sizeof(device))); + + if (res < 0) + return res; + + impl->device_id = device; + break; } default: @@ -311,7 +331,7 @@ static int impl_node_send_command(void *object, const struct spa_command *comman { struct port *port = GET_OUT_PORT(impl, 0); - if (!port->have_format) + if (!port->current_format) return -EIO; if (port->n_buffers == 0) return -EIO; @@ -432,36 +452,36 @@ static int port_get_format(struct impl *impl, struct port *port, { struct spa_pod_frame f; - if (!port->have_format) + if (!port->current_format) return -EIO; if (index > 0) return 0; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, SPA_PARAM_Format); spa_pod_builder_add(builder, - SPA_FORMAT_mediaType, SPA_POD_Id(port->current_format.media_type), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(port->current_format.media_subtype), + SPA_FORMAT_mediaType, SPA_POD_Id(port->current_format->media_type), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(port->current_format->media_subtype), 0); - switch (port->current_format.media_subtype) { + switch (port->current_format->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: spa_pod_builder_add(builder, - SPA_FORMAT_VIDEO_format, SPA_POD_Id(port->current_format.info.raw.format), - SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.raw.size), - SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.raw.framerate), + SPA_FORMAT_VIDEO_format, SPA_POD_Id(port->current_format->info.raw.format), + SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format->info.raw.size), + SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format->info.raw.framerate), 0); break; case SPA_MEDIA_SUBTYPE_mjpg: case SPA_MEDIA_SUBTYPE_jpeg: spa_pod_builder_add(builder, - SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.mjpg.size), - SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.mjpg.framerate), + SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format->info.mjpg.size), + SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format->info.mjpg.framerate), 0); break; case SPA_MEDIA_SUBTYPE_h264: spa_pod_builder_add(builder, - SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.h264.size), - SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.h264.framerate), + SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format->info.h264.size), + SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format->info.h264.framerate), 0); break; default: @@ -515,7 +535,7 @@ next: break; case SPA_PARAM_Buffers: { - if (!port->have_format) + if (!port->current_format) return -EIO; if (result.index > 0) return 0; @@ -591,12 +611,12 @@ static int port_set_format(struct impl *impl, struct port *port, int res; if (format == NULL) { - if (!port->have_format) + if (!port->current_format) return 0; spa_libcamera_stream_off(impl); spa_libcamera_clear_buffers(impl, port); - port->have_format = false; + port->current_format.reset(); spa_libcamera_close(impl); goto done; @@ -616,31 +636,31 @@ static int port_set_format(struct impl *impl, struct port *port, return -EINVAL; } - if (port->have_format && info.media_type == port->current_format.media_type && - info.media_subtype == port->current_format.media_subtype && - info.info.raw.format == port->current_format.info.raw.format && - info.info.raw.size.width == port->current_format.info.raw.size.width && - info.info.raw.size.height == port->current_format.info.raw.size.height) + if (port->current_format && info.media_type == port->current_format->media_type && + info.media_subtype == port->current_format->media_subtype && + info.info.raw.format == port->current_format->info.raw.format && + info.info.raw.size.width == port->current_format->info.raw.size.width && + info.info.raw.size.height == port->current_format->info.raw.size.height) return 0; break; case SPA_MEDIA_SUBTYPE_mjpg: if (spa_format_video_mjpg_parse(format, &info.info.mjpg) < 0) return -EINVAL; - if (port->have_format && info.media_type == port->current_format.media_type && - info.media_subtype == port->current_format.media_subtype && - info.info.mjpg.size.width == port->current_format.info.mjpg.size.width && - info.info.mjpg.size.height == port->current_format.info.mjpg.size.height) + if (port->current_format && info.media_type == port->current_format->media_type && + info.media_subtype == port->current_format->media_subtype && + info.info.mjpg.size.width == port->current_format->info.mjpg.size.width && + info.info.mjpg.size.height == port->current_format->info.mjpg.size.height) return 0; break; case SPA_MEDIA_SUBTYPE_h264: if (spa_format_video_h264_parse(format, &info.info.h264) < 0) return -EINVAL; - if (port->have_format && info.media_type == port->current_format.media_type && - info.media_subtype == port->current_format.media_subtype && - info.info.h264.size.width == port->current_format.info.h264.size.width && - info.info.h264.size.height == port->current_format.info.h264.size.height) + if (port->current_format && info.media_type == port->current_format->media_type && + info.media_subtype == port->current_format->media_subtype && + info.info.h264.size.width == port->current_format->info.h264.size.width && + info.info.h264.size.height == port->current_format->info.h264.size.height) return 0; break; default: @@ -648,9 +668,9 @@ static int port_set_format(struct impl *impl, struct port *port, } } - if (port->have_format && !(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) { + if (port->current_format && !(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) { spa_libcamera_use_buffers(impl, port, NULL, 0); - port->have_format = false; + port->current_format.reset(); } if (spa_libcamera_set_format(impl, port, &info, flags & SPA_NODE_PARAM_FLAG_TEST_ONLY) < 0) @@ -658,12 +678,11 @@ static int port_set_format(struct impl *impl, struct port *port, if (!(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) { port->current_format = info; - port->have_format = true; } done: port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - if (port->have_format) { + if (port->current_format) { port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[5] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { @@ -715,7 +734,7 @@ static int impl_node_port_use_buffers(void *object, port = GET_PORT(impl, direction, port_id); - if (!port->have_format) + if (!port->current_format) return -EIO; if (port->n_buffers) { @@ -823,8 +842,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); @@ -896,13 +915,39 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void static int impl_clear(struct spa_handle *handle) { - struct impl *impl; - - impl = (struct impl *) handle; - impl->~Impl(); + std::destroy_at(reinterpret_cast<impl *>(handle)); return 0; } +impl::impl(spa_log *log, spa_loop *data_loop, spa_system *system, + std::shared_ptr<CameraManager> manager, std::shared_ptr<Camera> camera, std::string device_id) + : handle({ SPA_VERSION_HANDLE, impl_get_interface, impl_clear }), + log(log), + data_loop(data_loop), + system(system), + device_id(std::move(device_id)), + out_ports{{this}}, + manager(std::move(manager)), + camera(std::move(camera)) +{ + libcamera_log_topic_init(log); + + spa_hook_list_init(&hooks); + + node.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Node, + SPA_VERSION_NODE, + &impl_node, this); + + params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); + params[1] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); + + info.max_output_ports = 1; + info.flags = SPA_NODE_FLAG_RT; + info.params = params; + info.n_params = 2; +} + static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) @@ -917,87 +962,45 @@ impl_init(const struct spa_handle_factory *factory, const struct spa_support *support, uint32_t n_support) { - struct impl *impl; const char *str; - struct port *port; int res; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); - impl = new(handle) Impl(); - - handle->get_interface = impl_get_interface; - handle->clear = impl_clear; + auto log = static_cast<spa_log *>(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log)); + auto data_loop = static_cast<spa_loop *>(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop)); + auto system = static_cast<spa_system *>(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System)); - impl->log = (struct spa_log*)spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); - libcamera_log_topic_init(impl->log); - - impl->data_loop = (struct spa_loop*)spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); - impl->system = (struct spa_system*)spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System); - - if (impl->data_loop == NULL) { - spa_log_error(impl->log, "a data_loop is needed"); + if (!data_loop) { + spa_log_error(log, "a data_loop is needed"); return -EINVAL; } - if (impl->system == NULL) { - spa_log_error(impl->log, "a system is needed"); + if (!system) { + spa_log_error(log, "a system is needed"); return -EINVAL; } - impl->node.iface = SPA_INTERFACE_INIT( - SPA_TYPE_INTERFACE_Node, - SPA_VERSION_NODE, - &impl_node, impl); - spa_hook_list_init(&impl->hooks); - - impl->info_all = SPA_NODE_CHANGE_MASK_FLAGS | - SPA_NODE_CHANGE_MASK_PROPS | - SPA_NODE_CHANGE_MASK_PARAMS; - impl->info = SPA_NODE_INFO_INIT(); - impl->info.max_output_ports = 1; - impl->info.flags = SPA_NODE_FLAG_RT; - impl->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); - impl->params[1] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); - impl->info.params = impl->params; - impl->info.n_params = 2; - reset_props(&impl->props); - - port = GET_OUT_PORT(impl, 0); - port->impl = impl; - 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_LIVE | - SPA_PORT_FLAG_PHYSICAL | - SPA_PORT_FLAG_TERMINAL; - port->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); - port->params[1] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); - port->params[2] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); - port->params[3] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); - port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[5] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - port->info.params = port->params; - port->info.n_params = 6; + auto manager = libcamera_manager_acquire(res); + if (!manager) { + spa_log_error(log, "can't start camera manager: %s", spa_strerror(res)); + return res; + } + std::string device_id; if (info && (str = spa_dict_lookup(info, SPA_KEY_API_LIBCAMERA_PATH))) - strncpy(impl->props.device, str, sizeof(impl->props.device)); - - impl->manager = libcamera_manager_acquire(); - if (impl->manager == NULL) { - res = -errno; - spa_log_error(impl->log, "can't start camera manager: %s", spa_strerror(res)); - return res; - } + device_id = str; - impl->camera = impl->manager->get(impl->props.device); - if (impl->camera == NULL) { - spa_log_error(impl->log, "unknown camera id %s", impl->props.device); - libcamera_manager_release(impl->manager); + auto camera = manager->get(device_id); + if (!camera) { + spa_log_error(log, "unknown camera id %s", device_id.c_str()); return -ENOENT; } + + new (handle) impl(log, data_loop, system, + std::move(manager), std::move(camera), std::move(device_id)); + return 0; } diff --git a/spa/plugins/libcamera/libcamera-utils.cpp b/spa/plugins/libcamera/libcamera-utils.cpp index c070e1f60c6164c2b7ef5459eeb4ce4ed26d8d6f..db81a11cd87d73c9e1c7044b8e47a498755877f1 100644 --- a/spa/plugins/libcamera/libcamera-utils.cpp +++ b/spa/plugins/libcamera/libcamera-utils.cpp @@ -40,7 +40,7 @@ int spa_libcamera_open(struct impl *impl) if (impl->acquired) return 0; - spa_log_info(impl->log, "open camera %s", impl->props.device); + spa_log_info(impl->log, "open camera %s", impl->device_id.c_str()); impl->camera->acquire(); impl->allocator = new FrameBufferAllocator(impl->camera); @@ -54,10 +54,10 @@ int spa_libcamera_close(struct impl *impl) struct port *port = &impl->out_ports[0]; if (!impl->acquired) return 0; - if (impl->active || port->have_format) + if (impl->active || port->current_format) return 0; - spa_log_info(impl->log, "close camera %s", impl->props.device); + spa_log_info(impl->log, "close camera %s", impl->device_id.c_str()); delete impl->allocator; impl->allocator = nullptr; @@ -69,13 +69,12 @@ int spa_libcamera_close(struct impl *impl) static void spa_libcamera_get_config(struct impl *impl) { - if (impl->have_config) + if (impl->config) return; StreamRoles roles; roles.push_back(VideoRecording); impl->config = impl->camera->generateConfiguration(roles); - impl->have_config = true; } static int spa_libcamera_buffer_recycle(struct impl *impl, struct port *port, uint32_t buffer_id) @@ -91,10 +90,10 @@ static int spa_libcamera_buffer_recycle(struct impl *impl, struct port *port, ui if (buffer_id >= impl->requestPool.size()) { spa_log_warn(impl->log, "invalid buffer_id %u >= %zu", buffer_id, impl->requestPool.size()); - return -EINVAL; - } + return -EINVAL; + } Request *request = impl->requestPool[buffer_id].get(); - Stream *stream = port->streamConfig.stream(); + Stream *stream = port->streamConfig.stream(); FrameBuffer *buffer = impl->allocator->buffers(stream)[buffer_id].get(); if ((res = request->addBuffer(stream, buffer)) < 0) { spa_log_warn(impl->log, "can't add buffer %u for request: %s", @@ -104,7 +103,7 @@ static int spa_libcamera_buffer_recycle(struct impl *impl, struct port *port, ui if (!impl->active) { impl->pendingRequests.push_back(request); return 0; - } else { + } else { if ((res = impl->camera->queueRequest(request)) < 0) { spa_log_warn(impl->log, "can't queue buffer %u: %s", buffer_id, spa_strerror(res)); @@ -119,7 +118,7 @@ static int allocBuffers(struct impl *impl, struct port *port, unsigned int count int res; if ((res = impl->allocator->allocate(port->streamConfig.stream())) < 0) - return res; + return res; for (unsigned int i = 0; i < count; i++) { std::unique_ptr<Request> request = impl->camera->createRequest(i); @@ -129,7 +128,7 @@ static int allocBuffers(struct impl *impl, struct port *port, unsigned int count } impl->requestPool.push_back(std::move(request)); } - return res; + return res; } static void freeBuffers(struct impl *impl, struct port *port) @@ -417,9 +416,7 @@ static int spa_libcamera_set_format(struct impl *impl, struct port *port, port->streamConfig = impl->config->at(0); if ((res = allocBuffers(impl, port, port->streamConfig.bufferCount)) < 0) - return res; - - port->have_format = true; + goto error; port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_RATE; port->info.flags = SPA_PORT_FLAG_CAN_ALLOC_BUFFERS | @@ -607,7 +604,7 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, } -void Impl::requestComplete(libcamera::Request *request) +void impl::requestComplete(libcamera::Request *request) { struct impl *impl = this; struct port *port = &impl->out_ports[0]; @@ -618,12 +615,12 @@ void Impl::requestComplete(libcamera::Request *request) spa_log_debug(impl->log, "request complete"); if ((request->status() == Request::RequestCancelled)) { - spa_log_debug(impl->log, "Request was cancelled"); - return; - } + spa_log_debug(impl->log, "Request was cancelled"); + return; + } FrameBuffer *buffer = request->findBuffer(stream); if (buffer == nullptr) { - spa_log_warn(impl->log, "unknown buffer"); + spa_log_warn(impl->log, "unknown buffer"); return; } const FrameMetadata &fmd = buffer->metadata(); @@ -664,7 +661,7 @@ static int spa_libcamera_stream_on(struct impl *impl) struct port *port = &impl->out_ports[0]; int res; - if (!port->have_format) { + if (!port->current_format) { spa_log_error(impl->log, "Exting %s with -EIO", __FUNCTION__); return -EIO; } @@ -674,15 +671,15 @@ static int spa_libcamera_stream_on(struct impl *impl) impl->camera->requestCompleted.connect(impl, &impl::requestComplete); - spa_log_info(impl->log, "starting camera %s", impl->props.device); + spa_log_info(impl->log, "starting camera %s", impl->device_id.c_str()); if ((res = impl->camera->start()) < 0) return res == -EACCES ? -EBUSY : res; for (Request *req : impl->pendingRequests) { - if ((res = impl->camera->queueRequest(req)) < 0) + if ((res = impl->camera->queueRequest(req)) < 0) return res == -EACCES ? -EBUSY : res; - } - impl->pendingRequests.clear(); + } + impl->pendingRequests.clear(); impl->source.func = libcamera_on_fd_events; impl->source.data = impl; @@ -724,7 +721,7 @@ static int spa_libcamera_stream_off(struct impl *impl) return 0; } - spa_log_info(impl->log, "stopping camera %s", impl->props.device); + spa_log_info(impl->log, "stopping camera %s", impl->device_id.c_str()); impl->pendingRequests.clear(); if ((res = impl->camera->stop()) < 0) 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..615a49bee364d25752759cf1ab0590c0f23b43d3 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 @@ -357,7 +357,7 @@ impl_init(const struct spa_handle_factory *factory, if ((str = spa_dict_lookup(info, SPA_KEY_LOG_LEVEL)) != NULL) this->log.level = atoi(str); if ((str = spa_dict_lookup(info, SPA_KEY_LOG_FILE)) != NULL) { - this->file = fopen(str, "w"); + this->file = fopen(str, "we"); if (this->file == NULL) fprintf(stderr, "Warning: failed to open file %s: (%m)", str); else 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..4c000f3eb1d0eea5babd92ce70b1cf2699e0e3c1 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,10 +67,11 @@ 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 -#define DEFAULT_RATE 44100 +#define DEFAULT_RATE 48000 #define MAX_BUFFERS 16 #define MAX_PORTS 1 @@ -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..802ead7ee2dbfca254aaf93fe301f916e55e18c8 100644 --- a/spa/plugins/v4l2/v4l2-source.c +++ b/spa/plugins/v4l2/v4l2-source.c @@ -314,7 +314,7 @@ static int impl_node_send_command(void *object, const struct spa_command *comman switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_ParamBegin: - if ((res = spa_v4l2_open(&port->dev, NULL)) < 0) + if ((res = spa_v4l2_open(&port->dev, this->props.device)) < 0) return res; break; case SPA_NODE_COMMAND_ParamEnd: @@ -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/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 22b8fb36837edb591a5fb85c80cd48aa45c9f6da..c09417b41585aad0d7fa4d3384fce515b55eb3ad 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -24,23 +24,32 @@ #include <spa/support/plugin.h> #include <spa/support/log.h> +#include <spa/support/cpu.h> #include <spa/node/node.h> #include <spa/node/io.h> #include <spa/node/utils.h> #include <spa/node/keys.h> -#include <spa/utils/result.h> #include <spa/utils/names.h> +#include <spa/utils/result.h> #include <spa/utils/string.h> #include <spa/buffer/alloc.h> #include <spa/pod/parser.h> #include <spa/pod/filter.h> +#include <spa/pod/dynamic.h> +#include <spa/param/param.h> +#include <spa/param/video/format-utils.h> +#include <spa/param/latency-utils.h> #include <spa/debug/format.h> #include <spa/debug/pod.h> +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT log_topic +static struct spa_log_topic *log_topic = &SPA_LOG_TOPIC(0, "spa.videoadapter"); + #define DEFAULT_ALIGN 16 -#define NAME "videoadapter" +#define MAX_PORTS 1 /** \cond */ @@ -49,19 +58,22 @@ struct impl { struct spa_node node; struct spa_log *log; + struct spa_cpu *cpu; + uint32_t max_align; enum spa_direction direction; struct spa_node *target; - struct spa_hook target_listener; struct spa_node *follower; struct spa_hook follower_listener; uint32_t follower_flags; + struct spa_video_info follower_current_format; + struct spa_video_info default_format; struct spa_handle *hnd_convert; struct spa_node *convert; - + struct spa_hook convert_listener; uint32_t convert_flags; uint32_t n_buffers; @@ -69,32 +81,71 @@ struct impl { struct spa_io_buffers io_buffers; struct spa_io_rate_match io_rate_match; + struct spa_io_position *io_position; uint64_t info_all; struct spa_node_info info; - struct spa_param_info params[5]; +#define IDX_EnumFormat 0 +#define IDX_PropInfo 1 +#define IDX_Props 2 +#define IDX_Format 3 +#define IDX_EnumPortConfig 4 +#define IDX_PortConfig 5 +#define IDX_Latency 6 +#define IDX_ProcessLatency 7 +#define N_NODE_PARAMS 8 + struct spa_param_info params[N_NODE_PARAMS]; + uint32_t convert_params_flags[N_NODE_PARAMS]; + uint32_t follower_params_flags[N_NODE_PARAMS]; struct spa_hook_list hooks; struct spa_callbacks callbacks; - unsigned int use_converter:1; + unsigned int add_listener:1; + unsigned int have_format:1; unsigned int started:1; - unsigned int active:1; unsigned int driver:1; - unsigned int driving:1; - unsigned int monitor:1; + unsigned int async:1; + unsigned int passthrough:1; + unsigned int follower_removing:1; }; /** \endcond */ +static int follower_enum_params(struct impl *this, + uint32_t id, + uint32_t idx, + struct spa_result_node_params *result, + const struct spa_pod *filter, + struct spa_pod_builder *builder) +{ + int res; + if (result->next < 0x100000) { + if (this->convert != NULL && + (res = spa_node_enum_params_sync(this->convert, + id, &result->next, filter, &result->param, builder)) == 1) + return res; + result->next = 0x100000; + } + if (result->next < 0x200000 && this->follower_params_flags[idx] & SPA_PARAM_INFO_READ) { + result->next &= 0xfffff; + if ((res = spa_node_enum_params_sync(this->follower, + id, &result->next, filter, &result->param, builder)) == 1) { + result->next |= 0x100000; + return res; + } + result->next = 0x200000; + } + return 0; +} + static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; - struct spa_pod *param; - struct spa_pod_builder b = { 0 }; - uint8_t buffer[1024]; + uint8_t buffer[4096]; + struct spa_pod_dynamic_builder b; struct spa_result_node_params result; uint32_t count = 0; int res; @@ -105,50 +156,65 @@ static int impl_node_enum_params(void *object, int seq, result.id = id; result.next = start; next: - result.index = result.next++; + result.index = result.next; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); + spa_log_debug(this->log, "%p: %d id:%u", this, seq, id); + + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); switch (id) { + case SPA_PARAM_EnumPortConfig: + case SPA_PARAM_PortConfig: + if (this->convert == NULL) + return 0; + res = spa_node_enum_params(this->convert, seq, id, start, num, filter); + return res; case SPA_PARAM_PropInfo: + res = follower_enum_params(this, + id, IDX_PropInfo, &result, filter, &b.b); + break; case SPA_PARAM_Props: - if ((res = spa_node_enum_params_sync(this->target, - id, &start, filter, ¶m, &b)) != 1) - return res; + res = follower_enum_params(this, + id, IDX_Props, &result, filter, &b.b); + break; + case SPA_PARAM_ProcessLatency: + res = follower_enum_params(this, + id, IDX_ProcessLatency, &result, filter, &b.b); break; - case SPA_PARAM_EnumFormat: case SPA_PARAM_Format: - if ((res = spa_node_port_enum_params_sync(this->follower, + case SPA_PARAM_Latency: + res = spa_node_port_enum_params_sync(this->follower, this->direction, 0, - id, &start, filter, ¶m, &b)) != 1) - return res; + id, &result.next, filter, &result.param, &b.b); break; - default: return -ENOENT; } - if (spa_pod_filter(&b, &result.param, param, filter) < 0) - goto next; + if (res == 1) { + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + count++; + } + spa_pod_dynamic_builder_clean(&b); - spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + if (res != 1) + return res; - if (++count != num) + if (count != num) goto next; return 0; } -#if 0 static int link_io(struct impl *this) { int res; - if (!this->use_converter) + if (this->convert == NULL) return 0; - spa_log_warn(this->log, NAME " %p: controls", this); + spa_log_debug(this->log, "%p: controls", this); spa_zero(this->io_rate_match); this->io_rate_match.rate = 1.0; @@ -157,14 +223,14 @@ static int link_io(struct impl *this) this->direction, 0, SPA_IO_RateMatch, &this->io_rate_match, sizeof(this->io_rate_match))) < 0) { - spa_log_warn(this->log, NAME " %p: set RateMatch on follower failed %d %s", this, + spa_log_debug(this->log, "%p: set RateMatch on follower disabled %d %s", this, res, spa_strerror(res)); } else if ((res = spa_node_port_set_io(this->convert, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_IO_RateMatch, &this->io_rate_match, sizeof(this->io_rate_match))) < 0) { - spa_log_warn(this->log, NAME " %p: set RateMatch on convert failed %d %s", this, + spa_log_warn(this->log, "%p: set RateMatch on convert failed %d %s", this, res, spa_strerror(res)); } @@ -174,7 +240,7 @@ static int link_io(struct impl *this) this->direction, 0, SPA_IO_Buffers, &this->io_buffers, sizeof(this->io_buffers))) < 0) { - spa_log_warn(this->log, NAME " %p: set Buffers on follower failed %d %s", this, + spa_log_warn(this->log, "%p: set Buffers on follower failed %d %s", this, res, spa_strerror(res)); return res; } @@ -182,58 +248,425 @@ static int link_io(struct impl *this) SPA_DIRECTION_REVERSE(this->direction), 0, SPA_IO_Buffers, &this->io_buffers, sizeof(this->io_buffers))) < 0) { - spa_log_warn(this->log, NAME " %p: set Buffers on convert failed %d %s", this, + spa_log_warn(this->log, "%p: set Buffers on convert failed %d %s", this, res, spa_strerror(res)); return res; } return 0; } -#endif static void emit_node_info(struct impl *this, bool full) { + uint32_t i; uint64_t old = full ? this->info.change_mask : 0; + + spa_log_debug(this->log, "%p: info full:%d change:%08"PRIx64, + this, full, this->info.change_mask); + if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { - struct spa_dict_item items[1]; - - this->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; - items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_DRIVER, this->driver ? "true" : "false"); - this->info.props = &SPA_DICT_INIT(items, 1); - + if (this->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { + for (i = 0; i < this->info.n_params; i++) { + if (this->params[i].user > 0) { + this->params[i].flags ^= SPA_PARAM_INFO_SERIAL; + this->params[i].user = 0; + spa_log_debug(this->log, "param %d flags:%08x", + i, this->params[i].flags); + } + } + } spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } +static int debug_params(struct impl *this, struct spa_node *node, + enum spa_direction direction, uint32_t port_id, uint32_t id, struct spa_pod *filter, + const char *debug, int err) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[4096]; + uint32_t state; + struct spa_pod *param; + int res, count = 0; + + spa_log_error(this->log, "params %s: %d:%d (%s) %s", + spa_debug_type_find_name(spa_type_param, id), + direction, port_id, debug, err ? spa_strerror(err) : "no matching params"); + if (err == -EBUSY) + return 0; + + if (filter) { + spa_log_error(this->log, "with this filter:"); + spa_debug_pod(2, NULL, filter); + } else { + spa_log_error(this->log, "there was no filter"); + } + + state = 0; + while (true) { + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + res = spa_node_port_enum_params_sync(node, + direction, port_id, + id, &state, + NULL, ¶m, &b); + if (res != 1) { + if (res < 0) + spa_log_error(this->log, " error: %s", spa_strerror(res)); + break; + } + spa_log_error(this->log, "unmatched %s %d:", debug, count); + spa_debug_pod(2, NULL, param); + count++; + } + if (count == 0) + spa_log_error(this->log, "could not get any %s", debug); + + return 0; +} + +static int negotiate_buffers(struct impl *this) +{ + uint8_t buffer[4096]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + uint32_t state; + struct spa_pod *param; + int res; + bool follower_alloc, conv_alloc; + uint32_t i, size, buffers, blocks, align, flags, stride = 0; + uint32_t *aligns; + struct spa_data *datas; + uint32_t follower_flags, conv_flags; + + spa_log_debug(this->log, "%p: %d", this, this->n_buffers); + + if (this->target == this->follower) + return 0; + + if (this->n_buffers > 0) + return 0; + + state = 0; + param = NULL; + if ((res = spa_node_port_enum_params_sync(this->follower, + this->direction, 0, + SPA_PARAM_Buffers, &state, + param, ¶m, &b)) < 0) { + if (res == -ENOENT) + param = NULL; + else { + debug_params(this, this->follower, this->direction, 0, + SPA_PARAM_Buffers, param, "follower buffers", res); + return res; + } + } + + state = 0; + if ((res = spa_node_port_enum_params_sync(this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_Buffers, &state, + param, ¶m, &b)) != 1) { + debug_params(this, this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_Buffers, param, "convert buffers", res); + return -ENOTSUP; + } + if (param == NULL) + return -ENOTSUP; + + spa_pod_fixate(param); + + follower_flags = this->follower_flags; + conv_flags = this->convert_flags; + + follower_alloc = SPA_FLAG_IS_SET(follower_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); + conv_alloc = SPA_FLAG_IS_SET(conv_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); + + flags = 0; + if (conv_alloc || follower_alloc) { + flags |= SPA_BUFFER_ALLOC_FLAG_NO_DATA; + if (conv_alloc) + follower_alloc = false; + } + + align = DEFAULT_ALIGN; + + if ((res = spa_pod_parse_object(param, + SPA_TYPE_OBJECT_ParamBuffers, NULL, + SPA_PARAM_BUFFERS_buffers, SPA_POD_Int(&buffers), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(&blocks), + SPA_PARAM_BUFFERS_size, SPA_POD_Int(&size), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(&stride), + SPA_PARAM_BUFFERS_align, SPA_POD_OPT_Int(&align))) < 0) + return res; + + spa_log_debug(this->log, "%p: buffers:%d, blocks:%d, size:%d, stride:%d align:%d %d:%d", + this, buffers, blocks, size, stride, align, follower_alloc, conv_alloc); + + align = SPA_MAX(align, this->max_align); + + datas = alloca(sizeof(struct spa_data) * blocks); + memset(datas, 0, sizeof(struct spa_data) * blocks); + aligns = alloca(sizeof(uint32_t) * blocks); + for (i = 0; i < blocks; i++) { + datas[i].type = SPA_DATA_MemPtr; + datas[i].flags = SPA_DATA_FLAG_READWRITE | SPA_DATA_FLAG_DYNAMIC; + datas[i].maxsize = size; + aligns[i] = align; + } + + free(this->buffers); + this->buffers = spa_buffer_alloc_array(buffers, flags, 0, NULL, blocks, datas, aligns); + if (this->buffers == NULL) + return -errno; + this->n_buffers = buffers; + + if ((res = spa_node_port_use_buffers(this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + conv_alloc ? SPA_NODE_BUFFERS_FLAG_ALLOC : 0, + this->buffers, this->n_buffers)) < 0) + return res; + + if ((res = spa_node_port_use_buffers(this->follower, + this->direction, 0, + follower_alloc ? SPA_NODE_BUFFERS_FLAG_ALLOC : 0, + this->buffers, this->n_buffers)) < 0) + return res; + + return 0; +} + +static int configure_format(struct impl *this, uint32_t flags, const struct spa_pod *format) +{ + int res; + + spa_log_debug(this->log, "%p: configure format:", this); + if (format && spa_log_level_enabled(this->log, SPA_LOG_LEVEL_DEBUG)) + spa_debug_format(0, NULL, format); + + if ((res = spa_node_port_set_param(this->follower, + this->direction, 0, + SPA_PARAM_Format, flags, + format)) < 0) + return res; + if (res > 0) { + uint8_t buffer[4096]; + struct spa_pod_builder b = { 0 }; + uint32_t state = 0; + struct spa_pod *fmt; + + /* format was changed to nearest compatible format */ + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + if ((res = spa_node_port_enum_params_sync(this->follower, + this->direction, 0, + SPA_PARAM_Format, &state, + NULL, &fmt, &b)) != 1) + return -EIO; + + format = fmt; + } + + if (this->target != this->follower && this->convert) { + if ((res = spa_node_port_set_param(this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_Format, flags, + format)) < 0) + return res; + } + + this->have_format = format != NULL; + if (format == NULL) { + this->n_buffers = 0; + } else { + res = negotiate_buffers(this); + } + + return res; +} + +static int configure_convert(struct impl *this, uint32_t mode) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param; + + if (this->convert == NULL) + return 0; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + spa_log_debug(this->log, "%p: configure convert %p", this, this->target); + + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(this->direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(mode)); + + return spa_node_set_param(this->convert, SPA_PARAM_PortConfig, 0, param); +} + +extern const struct spa_handle_factory spa_videoconvert_factory; + +static const struct spa_node_events follower_node_events; + +static int reconfigure_mode(struct impl *this, bool passthrough, + enum spa_direction direction, struct spa_pod *format) +{ + int res = 0; + struct spa_hook l; + + spa_log_info(this->log, "%p: passthrough mode %d", this, passthrough); + + if (this->passthrough != passthrough) { + if (passthrough) { + /* remove converter split/merge ports */ + configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_none); + } else { + /* remove follower ports */ + this->follower_removing = true; + spa_zero(l); + spa_node_add_listener(this->follower, &l, &follower_node_events, this); + spa_hook_remove(&l); + this->follower_removing = false; + } + } + + /* set new target */ + this->target = passthrough ? this->follower : this->convert; + + if ((res = configure_format(this, SPA_NODE_PARAM_FLAG_NEAREST, format)) < 0) + return res; + + if (this->passthrough != passthrough) { + this->passthrough = passthrough; + if (passthrough) { + /* add follower ports */ + spa_zero(l); + spa_node_add_listener(this->follower, &l, &follower_node_events, this); + spa_hook_remove(&l); + } else { + /* add converter ports */ + configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_dsp); + link_io(this); + } + } + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; + this->info.flags &= ~SPA_NODE_FLAG_NEED_CONFIGURE; + this->params[IDX_Props].user++; + + emit_node_info(this, false); + + return 0; +} + +static int format_video_raw_parse_opt(const struct spa_pod *format, struct spa_video_info_raw *info) +{ + uint32_t media_type, media_subtype; + int res; + if ((res = spa_format_parse(format, &media_type, &media_subtype)) < 0) + return res; + if (media_type != SPA_MEDIA_TYPE_video || + media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -ENOTSUP; + + spa_zero(*info); + res = spa_pod_parse_object(format, + SPA_TYPE_OBJECT_Format, NULL, + SPA_FORMAT_VIDEO_format, SPA_POD_OPT_Id(&info->format), + SPA_FORMAT_VIDEO_size, SPA_POD_OPT_Int(&info->size)); + return res; +} + static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { - int res = 0; + int res = 0, res2 = 0; struct impl *this = object; + struct spa_video_info info = { 0 }; - spa_log_debug(this->log, NAME" %p: set param %d", this, id); + spa_log_debug(this->log, "%p: set param %d", this, id); switch (id) { - case SPA_PARAM_PortConfig: + case SPA_PARAM_Format: if (this->started) return -EIO; - if (this->target != this->follower) { - if ((res = spa_node_set_param(this->target, id, flags, param)) < 0) + if (param == NULL) + return -EINVAL; + + if ((res = spa_format_parse(param, &info.media_type, &info.media_subtype)) < 0) + return res; + if (info.media_type != SPA_MEDIA_TYPE_video || + info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -EINVAL; + if (spa_format_video_raw_parse(param, &info.info.raw) < 0) + return -EINVAL; + + this->follower_current_format = info; + break; + + case SPA_PARAM_PortConfig: + { + enum spa_direction dir; + enum spa_param_port_config_mode mode; + struct spa_pod *format = NULL; + + if (this->started) { + spa_log_error(this->log, "was started"); + return -EIO; + } + + if (spa_pod_parse_object(param, + SPA_TYPE_OBJECT_ParamPortConfig, NULL, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(&dir), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(&mode), + SPA_PARAM_PORT_CONFIG_format, SPA_POD_OPT_Pod(&format)) < 0) + return -EINVAL; + + if (format) { + struct spa_video_info info; + if (format_video_raw_parse_opt(format, &info.info.raw) >= 0) + this->default_format = info; + } + + switch (mode) { + case SPA_PARAM_PORT_CONFIG_MODE_none: + return -ENOTSUP; + case SPA_PARAM_PORT_CONFIG_MODE_passthrough: + if ((res = reconfigure_mode(this, true, dir, format)) < 0) + return res; + break; + case SPA_PARAM_PORT_CONFIG_MODE_convert: + case SPA_PARAM_PORT_CONFIG_MODE_dsp: + if (this->convert == NULL) + return -ENOTSUP; + if ((res = reconfigure_mode(this, false, dir, NULL)) < 0) return res; + break; + default: + return -EINVAL; } - break; - case SPA_PARAM_Props: + if (this->target != this->follower) { if ((res = spa_node_set_param(this->target, id, flags, param)) < 0) return res; - - this->info.change_mask = SPA_NODE_CHANGE_MASK_PARAMS; - this->params[2].flags ^= SPA_PARAM_INFO_SERIAL; - emit_node_info(this, false); } break; + } + + case SPA_PARAM_Props: + if (this->target != this->follower) + res = spa_node_set_param(this->target, id, flags, param); + res2 = spa_node_set_param(this->follower, id, flags, param); + if (res < 0 && res2 < 0) + return res; + res = 0; + break; + case SPA_PARAM_ProcessLatency: + res = spa_node_set_param(this->follower, id, flags, param); + break; default: res = -ENOTSUP; break; @@ -248,6 +681,14 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) spa_return_val_if_fail(this != NULL, -EINVAL); + switch (id) { + case SPA_IO_Position: + this->io_position = data; + break; + default: + break; + } + if (this->target) res = spa_node_set_io(this->target, id, data, size); @@ -257,105 +698,479 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) return res; } +static struct spa_pod *merge_objects(struct impl *this, struct spa_pod_builder *b, uint32_t id, + struct spa_pod_object *o1, struct spa_pod_object *o2) +{ + const struct spa_pod_prop *p1, *p2; + struct spa_pod_frame f; + struct spa_pod_builder_state state; + int res = 0; + + if (o2 == NULL || SPA_POD_TYPE(o1) != SPA_POD_TYPE(o2)) + return (struct spa_pod*)o1; + + spa_pod_builder_push_object(b, &f, o1->body.type, o1->body.id); + p2 = NULL; + SPA_POD_OBJECT_FOREACH(o1, p1) { + p2 = spa_pod_object_find_prop(o2, p2, p1->key); + if (p2 != NULL) { + spa_pod_builder_get_state(b, &state); + res = spa_pod_filter_prop(b, p1, p2); + if (res < 0) + spa_pod_builder_reset(b, &state); + } + if (p2 == NULL || res < 0) + spa_pod_builder_raw_padded(b, p1, SPA_POD_PROP_SIZE(p1)); + } + p1 = NULL; + SPA_POD_OBJECT_FOREACH(o2, p2) { + p1 = spa_pod_object_find_prop(o1, p1, p2->key); + if (p1 != NULL) + continue; + spa_pod_builder_raw_padded(b, p2, SPA_POD_PROP_SIZE(p2)); + } + return spa_pod_builder_pop(b, &f); +} + +static int negotiate_format(struct impl *this) +{ + uint32_t state; + struct spa_pod *format, *def; + uint8_t buffer[4096]; + struct spa_pod_builder b = { 0 }; + int res; + + if (this->have_format) + return 0; + + if (this->target == this->follower) + return 0; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + spa_log_debug(this->log, "%p: negiotiate", this); + + spa_node_send_command(this->follower, + &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamBegin)); + + state = 0; + format = NULL; + if ((res = spa_node_port_enum_params_sync(this->follower, + this->direction, 0, + SPA_PARAM_EnumFormat, &state, + format, &format, &b)) < 0) { + if (res == -ENOENT) + format = NULL; + else { + debug_params(this, this->follower, this->direction, 0, + SPA_PARAM_EnumFormat, format, "follower format", res); + goto done; + } + } + if (this->convert) { + state = 0; + if ((res = spa_node_port_enum_params_sync(this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_EnumFormat, &state, + format, &format, &b)) != 1) { + debug_params(this, this->convert, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_EnumFormat, format, "convert format", res); + res = -ENOTSUP; + goto done; + } + } + if (format == NULL) { + res = -ENOTSUP; + goto done; + } + + def = spa_format_video_raw_build(&b, + SPA_PARAM_Format, &this->default_format.info.raw); + + format = merge_objects(this, &b, SPA_PARAM_Format, + (struct spa_pod_object*)format, + (struct spa_pod_object*)def); + + spa_pod_fixate(format); + + res = configure_format(this, SPA_NODE_PARAM_FLAG_NEAREST, format); + +done: + spa_node_send_command(this->follower, + &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamEnd)); + + return res; +} + + static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; int res; - spa_return_val_if_fail(this != NULL, -EINVAL); - - switch (SPA_NODE_COMMAND_ID(command)) { - case SPA_NODE_COMMAND_Start: - this->started = true; - break; - case SPA_NODE_COMMAND_Pause: - this->started = false; - break; - default: - break; - } + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_log_debug(this->log, "%p: command %d", this, SPA_NODE_COMMAND_ID(command)); + + switch (SPA_NODE_COMMAND_ID(command)) { + case SPA_NODE_COMMAND_Start: + if ((res = negotiate_format(this)) < 0) + return res; + if ((res = negotiate_buffers(this)) < 0) + return res; + break; + case SPA_NODE_COMMAND_Suspend: + configure_format(this, 0, NULL); + SPA_FALLTHROUGH + case SPA_NODE_COMMAND_Pause: + this->started = false; + break; + case SPA_NODE_COMMAND_Flush: + this->io_buffers.status = SPA_STATUS_OK; + break; + default: + break; + } + + if ((res = spa_node_send_command(this->target, command)) < 0) { + spa_log_error(this->log, "%p: can't send command %d: %s", + this, SPA_NODE_COMMAND_ID(command), + spa_strerror(res)); + return res; + } + + if (this->target != this->follower) { + if ((res = spa_node_send_command(this->follower, command)) < 0) { + spa_log_error(this->log, "%p: can't send command %d: %s", + this, SPA_NODE_COMMAND_ID(command), + spa_strerror(res)); + return res; + } + } + switch (SPA_NODE_COMMAND_ID(command)) { + case SPA_NODE_COMMAND_Start: + this->started = true; + break; + } + return res; +} + +static void convert_node_info(void *data, const struct spa_node_info *info) +{ + struct impl *this = data; + uint32_t i; + + spa_log_debug(this->log, "%p: info change:%08"PRIx64, this, + info->change_mask); + + if (info->change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { + for (i = 0; i < info->n_params; i++) { + uint32_t idx; + + switch (info->params[i].id) { + case SPA_PARAM_EnumPortConfig: + idx = IDX_EnumPortConfig; + break; + case SPA_PARAM_PortConfig: + idx = IDX_PortConfig; + break; + case SPA_PARAM_PropInfo: + idx = IDX_PropInfo; + break; + case SPA_PARAM_Props: + idx = IDX_Props; + break; + default: + continue; + } + if (!this->add_listener && + this->convert_params_flags[idx] == info->params[i].flags) + continue; + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + this->convert_params_flags[idx] = info->params[i].flags; + this->params[idx].flags = + (this->params[idx].flags & SPA_PARAM_INFO_SERIAL) | + (info->params[i].flags & SPA_PARAM_INFO_READWRITE); + + if (!this->add_listener) { + this->params[idx].user++; + spa_log_debug(this->log, "param %d changed", info->params[i].id); + } + } + } + emit_node_info(this, false); +} + +static void convert_port_info(void *data, + enum spa_direction direction, uint32_t port_id, + const struct spa_port_info *info) +{ + struct impl *this = data; + + if (direction != this->direction) { + if (port_id == 0) + return; + else + port_id--; + } + + spa_log_debug(this->log, "%p: port info %d:%d", this, + direction, port_id); + + if (this->target != this->follower) + spa_node_emit_port_info(&this->hooks, direction, port_id, info); +} + +static void convert_result(void *data, int seq, int res, uint32_t type, const void *result) +{ + struct impl *this = data; + + if (this->target == this->follower) + return; + + spa_log_trace(this->log, "%p: result %d %d", this, seq, res); + spa_node_emit_result(&this->hooks, seq, res, type, result); +} + +static const struct spa_node_events convert_node_events = { + SPA_VERSION_NODE_EVENTS, + .info = convert_node_info, + .port_info = convert_port_info, + .result = convert_result, +}; + +static void follower_info(void *data, const struct spa_node_info *info) +{ + struct impl *this = data; + uint32_t i; + + spa_log_debug(this->log, "%p: info change:%08"PRIx64, this, + info->change_mask); + + if (this->follower_removing) + return; + + this->async = (info->flags & SPA_NODE_FLAG_ASYNC) != 0; + + if (info->max_input_ports > 0) + this->direction = SPA_DIRECTION_INPUT; + else + this->direction = SPA_DIRECTION_OUTPUT; + + if (this->direction == SPA_DIRECTION_INPUT) { + this->info.flags |= SPA_NODE_FLAG_IN_PORT_CONFIG; + this->info.max_input_ports = MAX_PORTS; + } else { + this->info.flags |= SPA_NODE_FLAG_OUT_PORT_CONFIG; + this->info.max_output_ports = MAX_PORTS; + } + + spa_log_debug(this->log, "%p: follower info %s", this, + this->direction == SPA_DIRECTION_INPUT ? + "Input" : "Output"); + + if (info->change_mask & SPA_NODE_CHANGE_MASK_PROPS) { + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; + this->info.props = info->props; + } + if (info->change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { + for (i = 0; i < info->n_params; i++) { + uint32_t idx; + + switch (info->params[i].id) { + case SPA_PARAM_PropInfo: + idx = IDX_PropInfo; + break; + case SPA_PARAM_Props: + idx = IDX_Props; + break; + case SPA_PARAM_ProcessLatency: + idx = IDX_ProcessLatency; + break; + default: + continue; + } + if (!this->add_listener && + this->follower_params_flags[idx] == info->params[i].flags) + continue; + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + this->follower_params_flags[idx] = info->params[i].flags; + this->params[idx].flags = + (this->params[idx].flags & SPA_PARAM_INFO_SERIAL) | + (info->params[i].flags & SPA_PARAM_INFO_READWRITE); + + if (!this->add_listener) { + this->params[idx].user++; + spa_log_debug(this->log, "param %d changed", info->params[i].id); + } + } + } + emit_node_info(this, false); + + spa_zero(this->info.props); + this->info.change_mask &= ~SPA_NODE_CHANGE_MASK_PROPS; + +} + +static int recalc_latency(struct impl *this, enum spa_direction direction, uint32_t port_id) +{ + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param; + uint32_t index = 0; + struct spa_latency_info latency; + int res; + + spa_log_debug(this->log, "%p: ", this); - if ((res = spa_node_send_command(this->target, command)) < 0) { - spa_log_error(this->log, NAME " %p: can't start convert: %s", - this, spa_strerror(res)); - return res; - } + if (this->target == this->follower) + return 0; - if (this->target != this->follower) { - if ((res = spa_node_send_command(this->follower, command)) < 0) { - spa_log_error(this->log, NAME " %p: can't start follower: %s", - this, spa_strerror(res)); + while (true) { + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + if ((res = spa_node_port_enum_params_sync(this->follower, + direction, port_id, SPA_PARAM_Latency, + &index, NULL, ¶m, &b)) != 1) return res; - } + if ((res = spa_latency_parse(param, &latency)) < 0) + return res; + if (latency.direction == direction) + break; } - return res; + if ((res = spa_node_port_set_param(this->target, + SPA_DIRECTION_REVERSE(direction), 0, + SPA_PARAM_Latency, 0, param)) < 0) + return res; + + return 0; } -static void target_port_info(void *data, +static void follower_port_info(void *data, enum spa_direction direction, uint32_t port_id, const struct spa_port_info *info) { struct impl *this = data; + uint32_t i; + int res; - if (direction != this->direction) { - if (port_id == 0) - return; - else - port_id--; + if (this->follower_removing) { + spa_node_emit_port_info(&this->hooks, direction, port_id, NULL); + return; } - spa_log_trace(this->log, NAME" %p: port info %d:%d", this, - direction, port_id); + spa_log_debug(this->log, "%p: follower port info %s %p %08"PRIx64, this, + this->direction == SPA_DIRECTION_INPUT ? + "Input" : "Output", info, info->change_mask); + + if (info->change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { + for (i = 0; i < info->n_params; i++) { + uint32_t idx; + + switch (info->params[i].id) { + case SPA_PARAM_EnumFormat: + idx = IDX_EnumFormat; + break; + case SPA_PARAM_Format: + idx = IDX_Format; + break; + case SPA_PARAM_Latency: + idx = IDX_Latency; + break; + default: + continue; + } + if (!this->add_listener && + this->follower_params_flags[idx] == info->params[i].flags) + continue; + + this->follower_params_flags[idx] = info->params[i].flags; + this->params[idx].flags = + (this->params[idx].flags & SPA_PARAM_INFO_SERIAL) | + (info->params[i].flags & SPA_PARAM_INFO_READWRITE); + + if (idx == IDX_Latency) { + res = recalc_latency(this, direction, port_id); + spa_log_debug(this->log, "latency: %d (%s)", res, + spa_strerror(res)); + } + + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + if (!this->add_listener) { + this->params[idx].user++; + spa_log_debug(this->log, "param %d changed", info->params[i].id); + } + } + } + emit_node_info(this, false); - spa_node_emit_port_info(&this->hooks, direction, port_id, info); + if (this->target == this->follower) + spa_node_emit_port_info(&this->hooks, direction, port_id, info); } -static void target_result(void *data, int seq, int res, uint32_t type, const void *result) +static void follower_result(void *data, int seq, int res, uint32_t type, const void *result) { struct impl *this = data; - spa_log_trace(this->log, NAME" %p: result %d %d", this, seq, res); + + if (this->target != this->follower) + return; + + spa_log_trace(this->log, "%p: result %d %d", this, seq, res); spa_node_emit_result(&this->hooks, seq, res, type, result); } -static const struct spa_node_events target_node_events = { - SPA_VERSION_NODE_EVENTS, - .port_info = target_port_info, - .result = target_result, -}; - -static void follower_info(void *data, const struct spa_node_info *info) +static void follower_event(void *data, const struct spa_event *event) { struct impl *this = data; - const char *str; - - if (info->max_input_ports > 0) - this->direction = SPA_DIRECTION_INPUT; - else - this->direction = SPA_DIRECTION_OUTPUT; - spa_log_debug(this->log, NAME" %p: follower info %s", this, - this->direction == SPA_DIRECTION_INPUT ? - "Input" : "Output"); + spa_log_trace(this->log, "%p: event %d", this, SPA_EVENT_TYPE(event)); - if (info->props) { - if ((str = spa_dict_lookup(info->props, SPA_KEY_NODE_DRIVER)) != NULL) - this->driver = spa_atob(str); + switch (SPA_NODE_EVENT_ID(event)) { + case SPA_NODE_EVENT_Error: + /* Forward errors */ + spa_node_emit_event(&this->hooks, event); + break; + default: + /* Ignore other events */ + break; } } static const struct spa_node_events follower_node_events = { SPA_VERSION_NODE_EVENTS, .info = follower_info, + .port_info = follower_port_info, + .result = follower_result, + .event = follower_event, }; static int follower_ready(void *data, int status) { struct impl *this = data; - spa_log_trace(this->log, NAME " %p: ready %d", this, status); + spa_log_trace_fp(this->log, "%p: ready %d", this, status); + + if (this->target != this->follower) { + this->driver = true; + + if (this->direction == SPA_DIRECTION_OUTPUT) { + int retry = 8; + while (retry--) { + status = spa_node_process(this->convert); + if (status & SPA_STATUS_HAVE_DATA) + break; + + if (status & SPA_STATUS_NEED_DATA) { + status = spa_node_process(this->follower); + if (!(status & SPA_STATUS_HAVE_DATA)) + break; + } + } - if (this->direction == SPA_DIRECTION_OUTPUT) - status = spa_node_process(this->convert); + } + } return spa_node_call_ready(&this->callbacks, status); } @@ -365,7 +1180,7 @@ static int follower_reuse_buffer(void *data, uint32_t port_id, uint32_t buffer_i int res; struct impl *this = data; - if (this->use_converter) + if (this->target != this->follower && this->convert) res = spa_node_port_reuse_buffer(this->convert, port_id, buffer_id); else res = spa_node_call_reuse_buffer(&this->callbacks, port_id, buffer_id); @@ -373,10 +1188,17 @@ static int follower_reuse_buffer(void *data, uint32_t port_id, uint32_t buffer_i return res; } +static int follower_xrun(void *data, uint64_t trigger, uint64_t delay, struct spa_pod *info) +{ + struct impl *this = data; + return spa_node_call_xrun(&this->callbacks, trigger, delay, info); +} + static const struct spa_node_callbacks follower_node_callbacks = { SPA_VERSION_NODE_CALLBACKS, .ready = follower_ready, .reuse_buffer = follower_reuse_buffer, + .xrun = follower_xrun, }; static int impl_node_add_listener(void *object, @@ -390,17 +1212,25 @@ static int impl_node_add_listener(void *object, spa_return_val_if_fail(this != NULL, -EINVAL); - spa_log_trace(this->log, NAME" %p: add listener %p", this, listener); + spa_log_trace(this->log, "%p: add listener %p", this, listener); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); - emit_node_info(this, true); + if (events->info || events->port_info) { + this->add_listener = true; - if (this->use_converter) { spa_zero(l); - spa_node_add_listener(this->convert, &l, &target_node_events, this); + spa_node_add_listener(this->follower, &l, &follower_node_events, this); spa_hook_remove(&l); - } + if (this->convert) { + spa_zero(l); + spa_node_add_listener(this->convert, &l, &convert_node_events, this); + spa_hook_remove(&l); + } + this->add_listener = false; + + emit_node_info(this, true); + } spa_hook_list_join(&this->hooks, &save); return 0; @@ -471,201 +1301,12 @@ impl_node_port_enum_params(void *object, int seq, if (direction != this->direction) port_id++; - spa_log_debug(this->log, NAME" %p: %d %u", this, seq, id); + spa_log_debug(this->log, "%p: %d %u", this, seq, id); return spa_node_port_enum_params(this->target, seq, direction, port_id, id, start, num, filter); } -static int debug_params(struct impl *this, struct spa_node *node, - enum spa_direction direction, uint32_t port_id, uint32_t id, struct spa_pod *filter, - const char *debug, int err) -{ - struct spa_pod_builder b = { 0 }; - uint8_t buffer[4096]; - uint32_t state; - struct spa_pod *param; - int res; - - spa_log_error(this->log, "params %s: %d:%d (%s) %s", - spa_debug_type_find_name(spa_type_param, id), - direction, port_id, debug, spa_strerror(err)); - - state = 0; - while (true) { - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - res = spa_node_port_enum_params_sync(node, - direction, port_id, - id, &state, - NULL, ¶m, &b); - if (res != 1) { - if (res < 0) - spa_log_error(this->log, " error: %s", spa_strerror(res)); - break; - } - spa_debug_pod(2, NULL, param); - } - - spa_log_error(this->log, "failed filter:"); - if (filter) - spa_debug_pod(2, NULL, filter); - - return 0; -} - - -static int negotiate_format(struct impl *this) -{ - uint32_t state; - struct spa_pod *format; - uint8_t buffer[4096]; - struct spa_pod_builder b = { 0 }; - int res; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - spa_log_debug(this->log, NAME "%p: negiotiate", this); - - state = 0; - format = NULL; - if ((res = spa_node_port_enum_params_sync(this->follower, - this->direction, 0, - SPA_PARAM_EnumFormat, &state, - format, &format, &b)) < 0) { - debug_params(this, this->follower, this->direction, 0, - SPA_PARAM_EnumFormat, format, "follower format", res); - return -ENOTSUP; - } - - state = 0; - if ((res = spa_node_port_enum_params_sync(this->convert, - SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_EnumFormat, &state, - format, &format, &b)) != 1) { - debug_params(this, this->convert, - SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_EnumFormat, format, "convert format", res); - return -ENOTSUP; - } - - spa_pod_fixate(format); - if (spa_log_level_enabled(this->log, SPA_LOG_LEVEL_DEBUG)) - spa_debug_format(0, NULL, format); - - if ((res = spa_node_port_set_param(this->convert, - SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_Format, 0, - format)) < 0) - return res; - - if ((res = spa_node_port_set_param(this->follower, - this->direction, 0, - SPA_PARAM_Format, 0, - format)) < 0) - return res; - - return res; -} - -static int negotiate_buffers(struct impl *this) -{ - uint8_t buffer[4096]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - uint32_t state; - struct spa_pod *param; - int res, i; - bool follower_alloc, conv_alloc; - int32_t size, buffers, blocks, align, flags; - uint32_t *aligns; - struct spa_data *datas; - uint32_t follower_flags, conv_flags; - - spa_log_debug(this->log, "%p: %d", this, this->n_buffers); - - if (this->n_buffers > 0) - return 0; - - state = 0; - param = NULL; - if ((res = spa_node_port_enum_params_sync(this->follower, - this->direction, 0, - SPA_PARAM_Buffers, &state, - param, ¶m, &b)) < 0) { - debug_params(this, this->follower, this->direction, 0, - SPA_PARAM_Buffers, param, "follower buffers", res); - return -ENOTSUP; - } - - state = 0; - if ((res = spa_node_port_enum_params_sync(this->convert, - SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_Buffers, &state, - param, ¶m, &b)) != 1) { - debug_params(this, this->convert, - SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_Buffers, param, "convert buffers", res); - return -ENOTSUP; - } - - spa_pod_fixate(param); - - follower_flags = this->follower_flags; - conv_flags = this->convert_flags; - - follower_alloc = SPA_FLAG_IS_SET(follower_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); - conv_alloc = SPA_FLAG_IS_SET(conv_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); - - flags = 0; - if (conv_alloc || follower_alloc) { - flags |= SPA_BUFFER_ALLOC_FLAG_NO_DATA; - if (conv_alloc) - follower_alloc = false; - } - - align = DEFAULT_ALIGN; - - if ((res = spa_pod_parse_object(param, - SPA_TYPE_OBJECT_ParamBuffers, NULL, - SPA_PARAM_BUFFERS_buffers, SPA_POD_Int(&buffers), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(&blocks), - SPA_PARAM_BUFFERS_size, SPA_POD_Int(&size), - SPA_PARAM_BUFFERS_align, SPA_POD_OPT_Int(&align))) < 0) - return res; - - spa_log_debug(this->log, "%p: buffers %d, blocks %d, size %d, align %d %d:%d", - this, buffers, blocks, size, align, follower_alloc, conv_alloc); - - datas = alloca(sizeof(struct spa_data) * blocks); - memset(datas, 0, sizeof(struct spa_data) * blocks); - aligns = alloca(sizeof(uint32_t) * blocks); - for (i = 0; i < blocks; i++) { - datas[i].type = SPA_DATA_MemPtr; - datas[i].flags = SPA_DATA_FLAG_DYNAMIC; - datas[i].maxsize = size; - aligns[i] = align; - } - - free(this->buffers); - this->buffers = spa_buffer_alloc_array(buffers, flags, 0, NULL, blocks, datas, aligns); - if (this->buffers == NULL) - return -errno; - this->n_buffers = buffers; - - if ((res = spa_node_port_use_buffers(this->convert, - SPA_DIRECTION_REVERSE(this->direction), 0, - conv_alloc ? SPA_NODE_BUFFERS_FLAG_ALLOC : 0, - this->buffers, this->n_buffers)) < 0) - return res; - - if ((res = spa_node_port_use_buffers(this->follower, - this->direction, 0, - follower_alloc ? SPA_NODE_BUFFERS_FLAG_ALLOC : 0, - this->buffers, this->n_buffers)) < 0) { - return res; - } - return 0; -} - static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, @@ -686,19 +1327,13 @@ impl_node_port_set_param(void *object, flags, param)) < 0) return res; - if (id == SPA_PARAM_Format && this->use_converter) { - if (param == NULL) { - if ((res = spa_node_port_set_param(this->target, - SPA_DIRECTION_REVERSE(direction), 0, - id, 0, NULL)) < 0) - return res; - this->n_buffers = 0; - } - else { - if (port_id == 0) - res = negotiate_format(this); - } + if ((id == SPA_PARAM_Latency) && + direction == this->direction) { + if ((res = spa_node_port_set_param(this->follower, direction, 0, id, + flags, param)) < 0) + return res; } + return res; } @@ -737,18 +1372,13 @@ impl_node_port_use_buffers(void *object, if (direction != this->direction) port_id++; + spa_log_debug(this->log, "%p: %d %d:%d", this, + n_buffers, direction, port_id); + if ((res = spa_node_port_use_buffers(this->target, direction, port_id, flags, buffers, n_buffers)) < 0) return res; - - spa_log_debug(this->log, NAME" %p: %d %d:%d", this, - n_buffers, direction, port_id); - - if (n_buffers > 0 && this->use_converter) { - if (port_id == 0) - res = negotiate_buffers(this); - } return res; } @@ -765,25 +1395,96 @@ impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) static int impl_node_process(void *object) { struct impl *this = object; - int status; + int status = 0, fstatus, retry = 8; - spa_log_trace_fp(this->log, "%p: process convert:%u", - this, this->use_converter); + spa_log_trace_fp(this->log, "%p: process convert:%p driver:%d", + this, this->convert, this->driver); - if (this->direction == SPA_DIRECTION_INPUT) { - if (this->use_converter) - status = spa_node_process(this->convert); + if (this->target == this->follower) { + if (this->io_position) + this->io_rate_match.size = this->io_position->clock.duration; + return spa_node_process(this->follower); } - status = spa_node_process(this->follower); - - if (this->monitor) - status |= SPA_STATUS_HAVE_DATA; + if (this->direction == SPA_DIRECTION_INPUT) { + /* an input node (sink). + * First we run the converter to process the input for the follower + * then if it produced data, we run the follower. */ + while (retry--) { + status = this->convert ? spa_node_process(this->convert) : 0; + /* schedule the follower when the converter needed + * a recycled buffer */ + if (status == -EPIPE || status == 0) + status = SPA_STATUS_HAVE_DATA; + else if (status < 0) + break; + + if (status & (SPA_STATUS_HAVE_DATA | SPA_STATUS_DRAINED)) { + /* as long as the converter produced something or + * is drained, process the follower. */ + fstatus = spa_node_process(this->follower); + if (fstatus < 0) { + status = fstatus; + break; + } + /* if the follower doesn't need more data or is + * drained we can stop */ + if ((fstatus & SPA_STATUS_NEED_DATA) == 0 || + (fstatus & SPA_STATUS_DRAINED)) + break; + } + /* the converter needs more data */ + if ((status & SPA_STATUS_NEED_DATA)) + break; + } + } else if (!this->driver) { + bool done = false; + while (retry--) { + /* output node (source). First run the converter to make + * sure we push out any queued data. Then when it needs + * more data, schedule the follower. */ + status = this->convert ? spa_node_process(this->convert) : 0; + if (status == 0) + status = SPA_STATUS_NEED_DATA; + else if (status < 0) + break; + + done = (status & (SPA_STATUS_HAVE_DATA | SPA_STATUS_DRAINED)); + + /* when not async, we can return the data when we are done. + * In async mode we might first need to wake up the follower + * to asynchronously provide more data for the next round. */ + if (!this->async && done) + break; + + if (status & SPA_STATUS_NEED_DATA) { + /* the converter needs more data, schedule the + * follower */ + fstatus = spa_node_process(this->follower); + if (fstatus < 0) { + status = fstatus; + break; + } + /* if the follower didn't produce more data or is + * not drained we can stop now */ + if ((fstatus & (SPA_STATUS_HAVE_DATA | SPA_STATUS_DRAINED)) == 0) + break; + } + /* converter produced something or is drained and we + * scheduled the follower above, we can stop now*/ + if (done) + break; + } + if (!done) + spa_node_call_xrun(&this->callbacks, 0, 0, NULL); - if (this->direction == SPA_DIRECTION_OUTPUT && !this->driving) { - if (this->use_converter) - status = spa_node_process(this->convert); + } else { + status = spa_node_process(this->follower); } + spa_log_trace_fp(this->log, "%p: process status:%d", this, status); + + this->driver = false; + return status; } @@ -834,6 +1535,9 @@ static int impl_clear(struct spa_handle *handle) spa_hook_remove(&this->follower_listener); spa_node_set_callbacks(this->follower, NULL, NULL); + if (this->hnd_convert) + spa_handle_clear(this->hnd_convert); + if (this->buffers) free(this->buffers); this->buffers = NULL; @@ -841,7 +1545,6 @@ static int impl_clear(struct spa_handle *handle) return 0; } -extern const struct spa_handle_factory spa_videoconvert_factory; static size_t impl_get_size(const struct spa_handle_factory *factory, @@ -879,23 +1582,27 @@ impl_init(const struct spa_handle_factory *factory, this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + spa_log_topic_init(this->log, log_topic); + + this->cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); - if (info == NULL || (str = spa_dict_lookup(info, "video.adapt.follower")) == NULL) + if (info == NULL || + (str = spa_dict_lookup(info, "video.adapt.follower")) == NULL) return -EINVAL; sscanf(str, "pointer:%p", &this->follower); if (this->follower == NULL) return -EINVAL; - spa_node_add_listener(this->follower, - &this->follower_listener, &follower_node_events, this); - spa_node_set_callbacks(this->follower, &follower_node_callbacks, this); + if (this->cpu) + this->max_align = spa_cpu_get_max_align(this->cpu); + + spa_hook_list_init(&this->hooks); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); - spa_hook_list_init(&this->hooks); #if 0 this->hnd_convert = SPA_PTROFF(this, sizeof(struct impl), struct spa_handle); @@ -905,31 +1612,36 @@ impl_init(const struct spa_handle_factory *factory, spa_handle_get_interface(this->hnd_convert, SPA_TYPE_INTERFACE_Node, &iface); this->convert = iface; +#endif this->target = this->convert; - spa_node_add_listener(this->convert, - &this->convert_listener, &target_node_events, this); - this->use_converter = true; - link_io(this); + this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | + SPA_NODE_CHANGE_MASK_PARAMS; + this->info = SPA_NODE_INFO_INIT(); + this->info.flags = SPA_NODE_FLAG_RT | + SPA_NODE_FLAG_NEED_CONFIGURE; + this->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); + this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); + this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); + this->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + this->params[IDX_EnumPortConfig] = SPA_PARAM_INFO(SPA_PARAM_EnumPortConfig, SPA_PARAM_INFO_READ); + this->params[IDX_PortConfig] = SPA_PARAM_INFO(SPA_PARAM_PortConfig, SPA_PARAM_INFO_READWRITE); + this->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); + this->params[IDX_ProcessLatency] = SPA_PARAM_INFO(SPA_PARAM_ProcessLatency, SPA_PARAM_INFO_READWRITE); + this->info.params = this->params; + this->info.n_params = N_NODE_PARAMS; -#else - this->target = this->follower; - spa_node_add_listener(this->target, - &this->target_listener, &target_node_events, this); + spa_node_add_listener(this->follower, + &this->follower_listener, &follower_node_events, this); + spa_node_set_callbacks(this->follower, &follower_node_callbacks, this); -#endif + if (this->convert) + spa_node_add_listener(this->convert, + &this->convert_listener, &convert_node_events, this); - this->info_all = SPA_NODE_CHANGE_MASK_PARAMS; - this->info = SPA_NODE_INFO_INIT(); - this->info.max_input_ports = 0; - this->info.max_output_ports = 0; - this->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); - this->params[1] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); - this->params[2] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); - this->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READ); - this->params[4] = SPA_PARAM_INFO(SPA_PARAM_PortConfig, SPA_PARAM_INFO_WRITE); - this->info.params = this->params; - this->info.n_params = 5; + reconfigure_mode(this, true, this->direction, NULL); + + link_io(this); return 0; } diff --git a/spa/plugins/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..fe6fcc4f4f9e09787baaacd4ab4d9df616cec8ed 100644 --- a/spa/plugins/volume/volume.c +++ b/spa/plugins/volume/volume.c @@ -39,6 +39,9 @@ #define NAME "volume" +#define DEFAULT_RATE 48000 +#define DEFAULT_CHANNELS 2 + #define DEFAULT_VOLUME 1.0 #define DEFAULT_MUTE false @@ -322,8 +325,10 @@ static int port_enum_formats(void *object, SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_S32), - SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(44100, 1, INT32_MAX), - SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(2, 1, INT32_MAX)); + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int( + DEFAULT_RATE, 1, INT32_MAX), + SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int( + DEFAULT_CHANNELS, 1, INT32_MAX)); break; default: return 0; @@ -679,8 +684,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 +697,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-capture.c b/src/examples/audio-capture.c new file mode 100644 index 0000000000000000000000000000000000000000..25c5fa7da339e02ac6bce85b1994ef0e0fc3265e --- /dev/null +++ b/src/examples/audio-capture.c @@ -0,0 +1,206 @@ +/* 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. + */ + +/* + [title] + Audio capture using \ref pw_stream "pw_stream". + [title] + */ + +#include <stdio.h> +#include <errno.h> +#include <math.h> +#include <signal.h> + +#include <spa/param/audio/format-utils.h> + +#include <pipewire/pipewire.h> + +struct data { + struct pw_main_loop *loop; + struct pw_stream *stream; + + struct spa_audio_info format; +}; + +/* our data processing function is in general: + * + * struct pw_buffer *b; + * b = pw_stream_dequeue_buffer(stream); + * + * .. consume stuff in the buffer ... + * + * pw_stream_queue_buffer(stream, b); + */ +static void on_process(void *userdata) +{ + struct data *data = userdata; + struct pw_buffer *b; + struct spa_buffer *buf; + float *samples, max; + uint32_t c, n, n_channels, n_samples, peak; + + if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { + pw_log_warn("out of buffers: %m"); + return; + } + + buf = b->buffer; + if ((samples = buf->datas[0].data) == NULL) + return; + + n_channels = data->format.info.raw.channels; + n_samples = buf->datas[0].chunk->size / sizeof(float); + + fprintf(stdout, "captured %d samples\n", n_samples / n_channels); + for (c = 0; c < data->format.info.raw.channels; c++) { + max = 0.0f; + for (n = c; n < n_samples; n += n_channels) + max = fmaxf(max, fabsf(samples[n])); + + peak = SPA_CLAMP(max * 30, 0, 39); + + fprintf(stdout, "channel %d: |%*s%*s| peak:%f\n", + c, peak+1, "*", 40 - peak, "", max); + } + /* move cursor up */ + fprintf(stdout, "%c[%dA", 0x1b, n_channels + 1); + fflush(stdout); + + pw_stream_queue_buffer(data->stream, b); +} + +/* Be notified when the stream param changes. We're only looking at the + * format changes. + */ +static void +on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) +{ + struct data *data = _data; + + /* NULL means to clear the format */ + if (param == NULL || id != SPA_PARAM_Format) + return; + + if (spa_format_parse(param, &data->format.media_type, &data->format.media_subtype) < 0) + return; + + /* only accept raw audio */ + if (data->format.media_type != SPA_MEDIA_TYPE_audio || + data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return; + + /* call a helper function to parse the format for us. */ + spa_format_audio_raw_parse(param, &data->format.info.raw); + + fprintf(stdout, "capturing rate:%d channels:%d\n", + data->format.info.raw.rate, data->format.info.raw.channels); + +} + +static const struct pw_stream_events stream_events = { + PW_VERSION_STREAM_EVENTS, + .param_changed = on_stream_param_changed, + .process = on_process, +}; + +static void do_quit(void *userdata, int signal_number) +{ + struct data *data = userdata; + pw_main_loop_quit(data->loop); +} + +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); + + /* make a main loop. If you already have another main loop, you can add + * the fd of this pipewire mainloop to it. */ + data.loop = pw_main_loop_new(NULL); + + pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); + pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); + + /* Create a simple stream, the simple stream manages the core and remote + * objects for you if you don't need to deal with them. + * + * If you plan to autoconnect your stream, you need to provide at least + * media, category and role properties. + * + * Pass your events and a user_data pointer as the last arguments. This + * will inform you about the stream state. The most important event + * 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, "Capture", + 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]); + + /* uncomment if you want to capture from the sink monitor ports */ + /* pw_properties_set(props, PW_KEY_STREAM_CAPTURE_SINK, "true"); */ + + data.stream = pw_stream_new_simple( + pw_main_loop_get_loop(data.loop), + "audio-capture", + props, + &stream_events, + &data); + + /* Make one parameter with the supported formats. The SPA_PARAM_EnumFormat + * id means that this is a format enumeration (of 1 value). + * We leave the channels and rate empty to accept the native graph + * rate and channels. */ + params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, + &SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32)); + + /* Now connect this stream. We ask that our process function is + * called in a realtime thread. */ + pw_stream_connect(data.stream, + PW_DIRECTION_INPUT, + PW_ID_ANY, + PW_STREAM_FLAG_AUTOCONNECT | + PW_STREAM_FLAG_MAP_BUFFERS | + PW_STREAM_FLAG_RT_PROCESS, + params, 1); + + /* and wait while we let things run */ + pw_main_loop_run(data.loop); + + pw_stream_destroy(data.stream); + pw_main_loop_destroy(data.loop); + pw_deinit(); + + return 0; +} 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/meson.build b/src/examples/meson.build index 747cb1416233c48977cbd40a4f52f9eec14ef5a5..e2f260085b254530c2d5d40ff8a95198dfa6fcb6 100644 --- a/src/examples/meson.build +++ b/src/examples/meson.build @@ -3,6 +3,7 @@ examples = [ 'audio-src', 'audio-dsp-src', 'audio-dsp-filter', + 'audio-capture', 'video-play', 'video-src', 'video-dsp-play', 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..0f5b148e8367e83d73b02dca138382d26fbcac2b --- /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, MSG_NOSIGNAL); + 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-client-node/v0/ext-client-node.h b/src/modules/module-client-node/v0/ext-client-node.h index 564df77b65ec40ff10b3e7fa250c44986f512e65..21ba59718bd9aa4d592f21ef21bc0aa50d8e447a 100644 --- a/src/modules/module-client-node/v0/ext-client-node.h +++ b/src/modules/module-client-node/v0/ext-client-node.h @@ -140,9 +140,9 @@ struct pw_client_node0_message_port_reuse_buffer { #define PW_CLIENT_NODE0_MESSAGE_TYPE(message) (((struct pw_client_node0_message*)(message))->body.type.value) -#define PW_CLIENT_NODE0_MESSAGE_INIT(message) (struct pw_client_node0_message) \ +#define PW_CLIENT_NODE0_MESSAGE_INIT(message) ((struct pw_client_node0_message) \ { { { sizeof(struct pw_client_node0_message_body), SPA_TYPE_Struct } }, \ - { SPA_POD_INIT_Int(message) } } + { SPA_POD_INIT_Int(message) } }) #define PW_CLIENT_NODE0_MESSAGE_INIT_FULL(type,size,message,...) (type) \ { { { size, SPA_TYPE_Struct } }, \ diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index 8d1205a2a2e98633d3bd2040226e1f626f210375..42caf780bcd01fd4a4a2c190f6db9811fcbb2446 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 @@ -398,7 +402,7 @@ static void capture_process(void *data) pw_stream_queue_buffer(impl->capture, buf); } -static void input_state_changed(void *data, enum pw_stream_state old, +static void capture_state_changed(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct impl *impl = data; @@ -419,6 +423,44 @@ static void input_state_changed(void *data, enum pw_stream_state old, } } +static void source_state_changed(void *data, enum pw_stream_state old, + enum pw_stream_state state, const char *error) +{ + struct impl *impl = data; + int res; + + switch (state) { + case PW_STREAM_STATE_PAUSED: + pw_stream_flush(impl->source, false); + pw_stream_flush(impl->capture, false); + + if (old == PW_STREAM_STATE_STREAMING) { + pw_log_debug("%p: deactivate %s", impl, impl->aec->name); + res = spa_audio_aec_deactivate(impl->aec); + if (res < 0 && res != -EOPNOTSUPP) { + pw_log_error("aec plugin %s deactivate failed: %s", impl->aec->name, spa_strerror(res)); + } + } + break; + case PW_STREAM_STATE_STREAMING: + pw_log_debug("%p: activate %s", impl, impl->aec->name); + res = spa_audio_aec_activate(impl->aec); + if (res < 0 && res != -EOPNOTSUPP) { + pw_log_error("aec plugin %s activate failed: %s", impl->aec->name, spa_strerror(res)); + } + break; + case PW_STREAM_STATE_UNCONNECTED: + pw_log_info("%p: input unconnected", impl); + pw_impl_module_schedule_destroy(impl->module); + break; + case PW_STREAM_STATE_ERROR: + pw_log_info("%p: input error: %s", impl, error); + break; + default: + break; + } +} + static void input_param_latency_changed(struct impl *impl, const struct spa_pod *param) { struct spa_latency_info latency; @@ -451,7 +493,7 @@ static void input_param_changed(void *data, uint32_t id, const struct spa_pod *p static const struct pw_stream_events capture_events = { PW_VERSION_STREAM_EVENTS, .destroy = capture_destroy, - .state_changed = input_state_changed, + .state_changed = capture_state_changed, .process = capture_process, .param_changed = input_param_changed }; @@ -466,7 +508,7 @@ static void source_destroy(void *d) static const struct pw_stream_events source_events = { PW_VERSION_STREAM_EVENTS, .destroy = source_destroy, - .state_changed = input_state_changed, + .state_changed = source_state_changed, .param_changed = input_param_changed }; @@ -859,9 +901,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 +975,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) @@ -974,7 +1014,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) handle = spa_plugin_loader_load(impl->loader, SPA_NAME_AEC, &info); if (handle == NULL) { - pw_log_error("AEC codec plugin %s not available library.name %s", SPA_NAME_AEC, path); + pw_log_error("aec plugin %s not available library.name %s", SPA_NAME_AEC, path); return -ENOENT; } @@ -984,16 +1024,16 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) } impl->aec = iface; impl->spa_handle = handle; - if (impl->aec->iface.version != SPA_VERSION_AUDIO_AEC) { - pw_log_error("codec plugin %s has incompatible ABI version (%d != %d)", + + if (impl->aec->iface.version > SPA_VERSION_AUDIO_AEC) { + pw_log_error("codec plugin %s has incompatible ABI version (%d > %d)", SPA_NAME_AEC, impl->aec->iface.version, SPA_VERSION_AUDIO_AEC); res = -ENOENT; goto error; } - (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); + pw_log_info("Using plugin AEC %s with version %d", impl->aec->name, + impl->aec->iface.version); if ((str = pw_properties_get(props, "aec.args")) != NULL) aec_props = pw_properties_new_string(str); @@ -1005,7 +1045,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_properties_free(aec_props); if (res < 0) { - pw_log_error("codec plugin %s create failed: %s", impl->aec->name, + pw_log_error("aec plugin %s create failed: %s", impl->aec->name, spa_strerror(res)); goto error; } diff --git a/src/modules/module-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..d794679d147f0ba5e44e32881a4a7657ece078a7 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -519,13 +519,13 @@ struct link { struct graph_port { const struct fc_descriptor *desc; - void *hndl; + void **hndl; uint32_t port; }; struct graph_hndl { const struct fc_descriptor *desc; - void *hndl; + void **hndl; }; struct graph { @@ -577,6 +577,10 @@ struct impl { struct graph graph; }; +static int graph_instantiate(struct graph *graph); +static void graph_cleanup(struct graph *graph); + + static void capture_destroy(void *d) { struct impl *impl = d; @@ -585,51 +589,72 @@ static void capture_destroy(void *d) } static void capture_process(void *d) +{ + struct impl *impl = d; + pw_stream_trigger_process(impl->playback); +} + +static void playback_process(void *d) { struct impl *impl = d; struct pw_buffer *in, *out; struct graph *graph = &impl->graph; - uint32_t i, outsize = 0, n_hndl = graph->n_hndl; + uint32_t i, insize = 0, 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"); + pw_log_debug("%p: out of capture buffers: %m", impl); if ((out = pw_stream_dequeue_buffer(impl->playback)) == NULL) - pw_log_debug("out of playback buffers: %m"); + pw_log_debug("%p: out of playback buffers: %m", impl); if (in == NULL || out == NULL) 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]; + + offs = SPA_MIN(bd->chunk->offset, bd->maxsize); + size = SPA_MIN(bd->chunk->size, bd->maxsize - offs); - if (port->desc) - port->desc->connect_port(port->hndl, port->port, - SPA_PTROFF(ds->data, offs, void)); + port = i < graph->n_input ? &graph->input[i] : NULL; - outsize = SPA_MAX(outsize, size); - stride = SPA_MAX(stride, ds->chunk->stride); + if (port && port->desc) + port->desc->connect_port(*port->hndl, port->port, + SPA_PTROFF(bd->data, offs, void)); + + insize = i == 0 ? size : SPA_MIN(insize, size); + stride = SPA_MAX(stride, bd->chunk->stride); } + outsize = insize; + 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; } + + pw_log_trace_fp("%p: stride:%d in:%d out:%d requested:%"PRIu64" (%"PRIu64")", impl, + stride, insize, outsize, out->requested, out->requested * stride); + for (i = 0; i < n_hndl; i++) { struct graph_hndl *hndl = &graph->hndl[i]; - hndl->desc->run(hndl->hndl, outsize / sizeof(float)); + hndl->desc->run(*hndl->hndl, outsize / sizeof(float)); } done: @@ -637,8 +662,6 @@ done: pw_stream_queue_buffer(impl->capture, in); if (out != NULL) pw_stream_queue_buffer(impl->playback, out); - - pw_stream_trigger_process(impl->playback); } static float get_default(struct impl *impl, struct descriptor *desc, uint32_t p) @@ -673,11 +696,21 @@ static struct port *find_port(struct node *node, const char *name, int descripto str = strdupa(name); col = strchr(str, ':'); if (col != NULL) { + struct node *find; node_name = str; port_name = col + 1; *col = '\0'; - node = find_node(node->graph, node_name); - } else { + find = find_node(node->graph, node_name); + if (find == NULL) { + /* it's possible that the : is part of the port name, + * try again without splitting things up. */ + *col = ':'; + col = NULL; + } else { + node = find; + } + } + if (col == NULL) { node_name = node->name; port_name = str; } @@ -727,11 +760,12 @@ static struct spa_pod *get_prop_info(struct graph *graph, struct spa_pod_builder struct fc_port *p = &d->ports[port->p]; float def, min, max; char name[512]; + uint32_t rate = impl->rate ? impl->rate : 48000; if (p->hint & FC_HINT_SAMPLE_RATE) { - def = p->def * impl->rate; - min = p->min * impl->rate; - max = p->max * impl->rate; + def = p->def * rate; + min = p->min * rate; + max = p->max * rate; } else { def = p->def; min = p->min; @@ -829,7 +863,7 @@ static int set_control_value(struct node *node, const char *name, float *value) port = find_port(node, name, FC_PORT_INPUT | FC_PORT_CONTROL); if (port == NULL) - return 0; + return -ENOENT; node = port->node; desc = node->desc; @@ -844,7 +878,7 @@ static int parse_params(struct graph *graph, const struct spa_pod *pod) { struct spa_pod_parser prs; struct spa_pod_frame f; - int changed = 0; + int res, changed = 0; struct node *def_node; def_node = spa_list_first(&graph->node_list, struct node, link); @@ -877,7 +911,8 @@ static int parse_params(struct graph *graph, const struct spa_pod *pod) struct spa_pod *pod; spa_pod_parser_get_pod(&prs, &pod); } - changed += set_control_value(def_node, name, val); + if ((res = set_control_value(def_node, name, val)) > 0) + changed += res; } return changed; } @@ -888,13 +923,14 @@ static void graph_reset(struct graph *graph) for (i = 0; i < graph->n_hndl; i++) { struct graph_hndl *hndl = &graph->hndl[i]; const struct fc_descriptor *d = hndl->desc; + if (hndl->hndl == NULL || *hndl->hndl == NULL) + continue; if (d->deactivate) - d->deactivate(hndl->hndl); + d->deactivate(*hndl->hndl); if (d->activate) - d->activate(hndl->hndl); + d->activate(*hndl->hndl); } } - static void param_props_changed(struct impl *impl, const struct spa_pod *param) { struct spa_pod_object *obj = (struct spa_pod_object *) param; @@ -969,8 +1005,15 @@ static void param_changed(void *data, uint32_t id, const struct spa_pod *param) switch (id) { case SPA_PARAM_Format: - if (param == NULL) - graph_reset(graph); + if (param == NULL) { + graph_cleanup(graph); + } else { + struct spa_audio_info_raw info; + spa_zero(info); + spa_format_audio_raw_parse(param, &info); + impl->rate = info.rate; + graph_instantiate(graph); + } break; case SPA_PARAM_Props: if (param != NULL) @@ -1000,6 +1043,7 @@ static void playback_destroy(void *d) static const struct pw_stream_events out_stream_events = { PW_VERSION_STREAM_EVENTS, .destroy = playback_destroy, + .process = playback_process, .state_changed = state_changed, .param_changed = param_changed }; @@ -1329,15 +1373,17 @@ static int parse_control(struct node *node, struct spa_json *control) while (spa_json_get_string(control, key, sizeof(key)) > 0) { float fl; const char *val; - int len; + int res, len; if ((len = spa_json_next(control, &val)) < 0) break; - if (spa_json_parse_float(val, len, &fl) <= 0) + if (spa_json_parse_float(val, len, &fl) <= 0) { pw_log_warn("control '%s' expects a number, ignoring", key); - else - set_control_value(node, key, &fl); + } + else if ((res = set_control_value(node, key, &fl)) < 0) { + pw_log_warn("control '%s' can not be set: %s", key, spa_strerror(res)); + } } return 0; } @@ -1455,6 +1501,7 @@ static int load_node(struct graph *graph, struct spa_json *json) bool have_config = false; uint32_t i; int res; + float *data; while (spa_json_get_string(json, key, sizeof(key)) > 0) { if (spa_streq("type", key)) { @@ -1529,6 +1576,14 @@ static int load_node(struct graph *graph, struct spa_json *json) port->idx = i; port->external = SPA_ID_INVALID; port->p = desc->output[i]; + if ((data = port->audio_data[i]) == NULL) { + data = calloc(1, MAX_SAMPLES * sizeof(float)); + if (data == NULL) { + pw_log_error("cannot create port data: %m"); + return -errno; + } + } + port->audio_data[i] = data; spa_list_init(&port->link_list); } for (i = 0; i < desc->n_control; i++) { @@ -1559,21 +1614,31 @@ static int load_node(struct graph *graph, struct spa_json *json) return 0; } -static void node_free(struct node *node) +static void node_cleanup(struct node *node) { - uint32_t i, j; const struct fc_descriptor *d = node->desc->desc; + uint32_t i; - spa_list_remove(&node->link); for (i = 0; i < node->n_hndl; i++) { - for (j = 0; j < node->desc->n_output; j++) - free(node->output_port[j].audio_data[i]); if (node->hndl[i] == NULL) continue; if (d->deactivate) d->deactivate(node->hndl[i]); d->cleanup(node->hndl[i]); + node->hndl[i] = NULL; } +} + +static void node_free(struct node *node) +{ + uint32_t i, j; + + spa_list_remove(&node->link); + for (i = 0; i < node->n_hndl; i++) { + for (j = 0; j < node->desc->n_output; j++) + free(node->output_port[j].audio_data[i]); + } + node_cleanup(node); descriptor_unref(node->desc); free(node->input_port); free(node->output_port); @@ -1582,61 +1647,88 @@ static void node_free(struct node *node) free(node); } -static struct node *find_next_node(struct graph *graph) +static void graph_cleanup(struct graph *graph) { struct node *node; - spa_list_for_each(node, &graph->node_list, link) { - if (node->n_deps == 0 && !node->visited) { - node->visited = true; - return node; - } - } - return NULL; + spa_list_for_each(node, &graph->node_list, link) + node_cleanup(node); } -static int setup_input_port(struct graph *graph, struct port *port) +static int graph_instantiate(struct graph *graph) { - struct descriptor *desc = port->node->desc; - const struct fc_descriptor *d = desc->desc; + struct impl *impl = graph->impl; + struct node *node; + struct port *port; struct link *link; - uint32_t i, n_hndl = port->node->n_hndl; + struct descriptor *desc; + const struct fc_descriptor *d; + uint32_t i, j; + int res; - spa_list_for_each(link, &port->link_list, input_link) { - struct port *peer = link->output; - for (i = 0; i < n_hndl; i++) { - pw_log_info("connect input port %s[%d]:%s %p", - port->node->name, i, d->ports[port->p].name, - peer->audio_data[i]); - d->connect_port(port->node->hndl[i], port->p, peer->audio_data[i]); + spa_list_for_each(node, &graph->node_list, link) { + float *sd = silence_data, *dd = discard_data; + + node_cleanup(node); + + desc = node->desc; + d = desc->desc; + if (d->flags & FC_DESCRIPTOR_SUPPORTS_NULL_DATA) + sd = dd = NULL; + + for (i = 0; i < node->n_hndl; i++) { + pw_log_info("instantiate %s %d rate:%lu", d->name, i, impl->rate); + if ((node->hndl[i] = d->instantiate(d, impl->rate, i, node->config)) == NULL) { + pw_log_error("cannot create plugin instance: %m"); + res = -errno; + goto error; + } + for (j = 0; j < desc->n_input; j++) { + port = &node->input_port[j]; + d->connect_port(node->hndl[i], port->p, sd); + + spa_list_for_each(link, &port->link_list, input_link) { + struct port *peer = link->output; + pw_log_info("connect input port %s[%d]:%s %p", + node->name, i, d->ports[port->p].name, + peer->audio_data[i]); + d->connect_port(node->hndl[i], port->p, peer->audio_data[i]); + } + } + for (j = 0; j < desc->n_output; j++) { + port = &node->output_port[j]; + pw_log_info("connect output port %s[%d]:%s %p", + node->name, i, d->ports[port->p].name, + port->audio_data[i]); + d->connect_port(node->hndl[i], port->p, port->audio_data[i]); + } + for (j = 0; j < desc->n_control; j++) { + port = &node->control_port[j]; + d->connect_port(node->hndl[i], port->p, &port->control_data); + } + for (j = 0; j < desc->n_notify; j++) { + port = &node->notify_port[j]; + d->connect_port(node->hndl[i], port->p, &port->control_data); + } + if (d->activate) + d->activate(node->hndl[i]); } } return 0; +error: + graph_cleanup(graph); + return res; } -static int setup_output_port(struct graph *graph, struct port *port) +static struct node *find_next_node(struct graph *graph) { - struct descriptor *desc = port->node->desc; - const struct fc_descriptor *d = desc->desc; - struct link *link; - uint32_t i, n_hndl = port->node->n_hndl; - - spa_list_for_each(link, &port->link_list, output_link) { - for (i = 0; i < n_hndl; i++) { - float *data; - if ((data = port->audio_data[i]) == NULL) { - data = calloc(1, MAX_SAMPLES * sizeof(float)); - if (data == NULL) - return -errno; - } - port->audio_data[i] = data; - pw_log_info("connect output port %s[%d]:%s %p", - port->node->name, i, d->ports[port->p].name, - port->audio_data[i]); - d->connect_port(port->node->hndl[i], port->p, data); + struct node *node; + spa_list_for_each(node, &graph->node_list, link) { + if (node->n_deps == 0 && !node->visited) { + node->visited = true; + return node; } - link->input->node->n_deps--; } - return 0; + return NULL; } static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_json *outputs) @@ -1644,11 +1736,11 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ struct impl *impl = graph->impl; struct node *node, *first, *last; struct port *port; + struct link *link; struct graph_port *gp; struct graph_hndl *gh; uint32_t i, j, n_nodes, n_input, n_output, n_control, n_hndl = 0; int res; - unsigned long p; struct descriptor *desc; const struct fc_descriptor *d; char v[256]; @@ -1690,67 +1782,40 @@ 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. */ n_control = 0; n_nodes = 0; spa_list_for_each(node, &graph->node_list, link) { - float *sd = silence_data, *dd = discard_data; - + node->n_hndl = n_hndl; desc = node->desc; - d = desc->desc; - if (d->flags & FC_DESCRIPTOR_SUPPORTS_NULL_DATA) - sd = dd = NULL; - - for (i = 0; i < n_hndl; i++) { - pw_log_info("instantiate %s %d", d->name, i); - if ((node->hndl[i] = d->instantiate(d, &impl->rate, i, node->config)) == NULL) { - pw_log_error("cannot create plugin instance: %m"); - res = -errno; - goto error; - } - node->n_hndl = i + 1; - - for (j = 0; j < desc->n_input; j++) { - p = desc->input[j]; - d->connect_port(node->hndl[i], p, sd); - } - for (j = 0; j < desc->n_output; j++) { - p = desc->output[j]; - d->connect_port(node->hndl[i], p, dd); - } - for (j = 0; j < desc->n_control; j++) { - port = &node->control_port[j]; - d->connect_port(node->hndl[i], port->p, &port->control_data); - } - for (j = 0; j < desc->n_notify; j++) { - port = &node->notify_port[j]; - d->connect_port(node->hndl[i], port->p, &port->control_data); - } - if (d->activate) - d->activate(node->hndl[i]); - } n_control += desc->n_control; n_nodes++; } - pw_log_info("suggested rate:%lu capture:%d playback:%d", impl->rate, - impl->capture_info.rate, impl->playback_info.rate); - - if (impl->capture_info.rate == 0) - impl->capture_info.rate = impl->rate; - if (impl->playback_info.rate == 0) - impl->playback_info.rate = impl->rate; - graph->n_input = 0; graph->input = calloc(n_input * n_hndl, sizeof(struct graph_port)); graph->n_output = 0; @@ -1766,7 +1831,7 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ pw_log_info("input port %s[%d]:%s", first->name, i, d->ports[desc->input[j]].name); gp->desc = d; - gp->hndl = first->hndl[i]; + gp->hndl = &first->hndl[i]; gp->port = desc->input[j]; } } else { @@ -1800,7 +1865,7 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ port->node->name, i, d->ports[port->p].name); port->external = graph->n_input; gp->desc = d; - gp->hndl = port->node->hndl[i]; + gp->hndl = &port->node->hndl[i]; gp->port = port->p; } graph->n_input++; @@ -1814,7 +1879,7 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ pw_log_info("output port %s[%d]:%s", last->name, i, d->ports[desc->output[j]].name); gp->desc = d; - gp->hndl = last->hndl[i]; + gp->hndl = &last->hndl[i]; gp->port = desc->output[j]; } } else { @@ -1848,7 +1913,7 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ port->node->name, i, d->ports[port->p].name); port->external = graph->n_output; gp->desc = d; - gp->hndl = port->node->hndl[i]; + gp->hndl = &port->node->hndl[i]; gp->port = port->p; } graph->n_output++; @@ -1868,17 +1933,14 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ desc = node->desc; d = desc->desc; - for (i = 0; i < desc->n_input; i++) - setup_input_port(graph, &node->input_port[i]); - for (i = 0; i < n_hndl; i++) { gh = &graph->hndl[graph->n_hndl++]; - gh->hndl = node->hndl[i]; + gh->hndl = &node->hndl[i]; gh->desc = d; - } - for (i = 0; i < desc->n_output; i++) - setup_output_port(graph, &node->output_port[i]); + spa_list_for_each(link, &node->output_port[i].link_list, output_link) + link->input->node->n_deps--; + } /* collect all control ports on the graph */ for (i = 0; i < desc->n_control; i++) { @@ -1886,17 +1948,8 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ graph->n_control++; } } - return 0; - + res = 0; error: - spa_list_for_each(node, &graph->node_list, link) { - for (i = 0; i < node->n_hndl; i++) { - if (node->hndl[i] != NULL) - node->desc->desc->cleanup(node->hndl[i]); - node->hndl[i] = NULL; - } - node->n_hndl = 0; - } return res; } @@ -2027,12 +2080,20 @@ static const struct pw_proxy_events core_proxy_events = { static void impl_destroy(struct impl *impl) { + /* disconnect both streams before destroying any of them */ + if (impl->capture) + pw_stream_disconnect(impl->capture); + if (impl->playback) + pw_stream_disconnect(impl->playback); + if (impl->capture) pw_stream_destroy(impl->capture); if (impl->playback) pw_stream_destroy(impl->playback); + if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); + pw_properties_free(impl->capture_props); pw_properties_free(impl->playback_props); graph_free(&impl->graph); @@ -2083,8 +2144,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)); } @@ -2141,7 +2203,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->module = module; impl->context = context; - impl->rate = 48000; impl->graph.impl = impl; spa_list_init(&impl->plugin_list); @@ -2151,6 +2212,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_properties_setf(props, PW_KEY_NODE_LINK_GROUP, "filter-chain-%u-%u", pid, id); if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); + if (pw_properties_get(props, "resample.prefill") == NULL) + pw_properties_set(props, "resample.prefill", "true"); if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, "filter-chain-%u-%u", pid, id); @@ -2168,6 +2231,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_LATENCY); copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_MEDIA_NAME); + copy_props(impl, props, "resample.prefill"); parse_audio_info(impl->capture_props, &impl->capture_info); parse_audio_info(impl->playback_props, &impl->playback_info); diff --git a/src/modules/module-filter-chain/builtin_plugin.c b/src/modules/module-filter-chain/builtin_plugin.c index 00982567e0d9c8547f01064e8ea963468364fa4b..c74554bd0513d02229233188d8c1eb25381ebecc 100644 --- a/src/modules/module-filter-chain/builtin_plugin.c +++ b/src/modules/module-filter-chain/builtin_plugin.c @@ -52,7 +52,7 @@ struct builtin { }; static void *builtin_instantiate(const struct fc_descriptor * Descriptor, - unsigned long *SampleRate, int index, const char *config) + unsigned long SampleRate, int index, const char *config) { struct builtin *impl; @@ -60,7 +60,7 @@ static void *builtin_instantiate(const struct fc_descriptor * Descriptor, if (impl == NULL) return NULL; - impl->rate = *SampleRate; + impl->rate = SampleRate; return impl; } @@ -576,7 +576,7 @@ static float *create_dirac(const char *filename, float gain, int delay, int offs } static void * convolver_instantiate(const struct fc_descriptor * Descriptor, - unsigned long *SampleRate, int index, const char *config) + unsigned long SampleRate, int index, const char *config) { struct convolver_impl *impl; float *samples; @@ -588,6 +588,7 @@ static void * convolver_instantiate(const struct fc_descriptor * Descriptor, int blocksize = 0, tailsize = 0; int delay = 0; float gain = 1.0f; + unsigned long rate; if (config == NULL) return NULL; @@ -647,8 +648,13 @@ static void * convolver_instantiate(const struct fc_descriptor * Descriptor, samples = create_dirac(filename, gain, delay, offset, length, &n_samples); } else { + rate = SampleRate; samples = read_samples(filename, gain, delay, offset, - length, channel, SampleRate, &n_samples); + length, channel, &rate, &n_samples); + if (rate != SampleRate) { + pw_log_warn("Convolver samplerate %lu doesn't match filter rate %lu. " + "Consider forcing a filter rate.", rate, SampleRate); + } } if (samples == NULL) return NULL; @@ -664,7 +670,7 @@ static void * convolver_instantiate(const struct fc_descriptor * Descriptor, if (impl == NULL) goto error; - impl->rate = *SampleRate; + impl->rate = SampleRate; impl->conv = convolver_new(blocksize, tailsize, samples, n_samples); if (impl->conv == NULL) @@ -750,7 +756,7 @@ static void delay_cleanup(void * Instance) } static void *delay_instantiate(const struct fc_descriptor * Descriptor, - unsigned long *SampleRate, int index, const char *config) + unsigned long SampleRate, int index, const char *config) { struct delay_impl *impl; struct spa_json it[2]; @@ -782,7 +788,7 @@ static void *delay_instantiate(const struct fc_descriptor * Descriptor, if (impl == NULL) return NULL; - impl->rate = *SampleRate; + impl->rate = SampleRate; impl->buffer_samples = max_delay * impl->rate; pw_log_info("%lu %d", impl->rate, impl->buffer_samples); 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..3cce6e2ca52468a7a4358673a55e50f0e9ccc58b 100644 --- a/src/modules/module-filter-chain/ladspa_plugin.c +++ b/src/modules/module-filter-chain/ladspa_plugin.c @@ -47,10 +47,10 @@ struct descriptor { }; static void *ladspa_instantiate(const struct fc_descriptor *desc, - unsigned long *SampleRate, int index, const char *config) + unsigned long SampleRate, int index, const char *config) { struct descriptor *d = (struct descriptor *)desc; - return d->d->instantiate(d->d, *SampleRate); + return d->d->instantiate(d->d, SampleRate); } static const LADSPA_Descriptor *find_desc(LADSPA_Descriptor_Function desc_func, const char *name) @@ -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..e607e2f1e265291b05e5942cc3c350866ddefc29 100644 --- a/src/modules/module-filter-chain/lv2_plugin.c +++ b/src/modules/module-filter-chain/lv2_plugin.c @@ -35,11 +35,27 @@ #include <pipewire/array.h> #include <lilv/lilv.h> -#include <lv2/lv2plug.in/ns/ext/atom/atom.h> -#include <lv2/lv2plug.in/ns/ext/buf-size/buf-size.h> -#include "lv2/lv2plug.in/ns/ext/worker/worker.h" -#include "lv2/lv2plug.in/ns/ext/options/options.h" -#include "lv2/lv2plug.in/ns/ext/parameters/parameters.h" + +#if defined __has_include +# if __has_include (<lv2/atom/atom.h>) + + #include <lv2/atom/atom.h> + #include <lv2/buf-size/buf-size.h> + #include <lv2/worker/worker.h> + #include <lv2/options/options.h> + #include <lv2/parameters/parameters.h> + +# else + + #include <lv2/lv2plug.in/ns/ext/atom/atom.h> + #include <lv2/lv2plug.in/ns/ext/buf-size/buf-size.h> + #include <lv2/lv2plug.in/ns/ext/worker/worker.h> + #include <lv2/lv2plug.in/ns/ext/options/options.h> + #include <lv2/lv2plug.in/ns/ext/parameters/parameters.h> + +# endif + +#endif #include "plugin.h" @@ -282,7 +298,7 @@ work_schedule(LV2_Worker_Schedule_Handle handle, uint32_t size, const void *data } static void *lv2_instantiate(const struct fc_descriptor *desc, - unsigned long *SampleRate, int index, const char *config) + unsigned long SampleRate, int index, const char *config) { struct descriptor *d = (struct descriptor*)desc; struct plugin *p = d->p; @@ -292,7 +308,7 @@ static void *lv2_instantiate(const struct fc_descriptor *desc, static const int32_t min_block_length = 1; static const int32_t max_block_length = 8192; static const int32_t seq_size = 32768; - float fsample_rate = *SampleRate; + float fsample_rate = SampleRate; i = calloc(1, sizeof(*i)); if (i == NULL) @@ -334,7 +350,7 @@ static void *lv2_instantiate(const struct fc_descriptor *desc, i->options_feature.data = i->options; i->features[n_features++] = &i->options_feature; - i->instance = lilv_plugin_instantiate(p->p, *SampleRate, i->features); + i->instance = lilv_plugin_instantiate(p->p, SampleRate, i->features); if (i->instance == NULL) { free(i); return NULL; diff --git a/src/modules/module-filter-chain/plugin.h b/src/modules/module-filter-chain/plugin.h index e4f2eb5f1d6c1df78d53c68c17ea93dd0a2a3232..ce46b62ecf87bdaf2f442ad499728f2b37fb0b09 100644 --- a/src/modules/module-filter-chain/plugin.h +++ b/src/modules/module-filter-chain/plugin.h @@ -72,7 +72,7 @@ struct fc_descriptor { struct fc_port *ports; void *(*instantiate) (const struct fc_descriptor *desc, - unsigned long *SampleRate, int index, const char *config); + unsigned long SampleRate, int index, const char *config); void (*cleanup) (void *instance); diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index 543ee3d818b338496d098d5ab09be2a6a690bc8c..66149b347da5bd0893d4c766bc5830f28e3daa8d 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 * } @@ -176,6 +177,12 @@ static void capture_destroy(void *d) } static void capture_process(void *d) +{ + struct impl *impl = d; + pw_stream_trigger_process(impl->playback); +} + +static void playback_process(void *d) { struct impl *impl = d; struct pw_buffer *in, *out; @@ -188,33 +195,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; } } @@ -222,8 +231,6 @@ static void capture_process(void *d) pw_stream_queue_buffer(impl->capture, in); if (out != NULL) pw_stream_queue_buffer(impl->playback, out); - - pw_stream_trigger_process(impl->playback); } static void param_latency_changed(struct impl *impl, const struct spa_pod *param, @@ -302,6 +309,7 @@ static void playback_param_changed(void *data, uint32_t id, const struct spa_pod static const struct pw_stream_events out_stream_events = { PW_VERSION_STREAM_EVENTS, .destroy = playback_destroy, + .process = playback_process, .state_changed = stream_state_changed, .param_changed = playback_param_changed, }; @@ -396,12 +404,20 @@ static const struct pw_proxy_events core_proxy_events = { static void impl_destroy(struct impl *impl) { + /* disconnect both streams before destroying any of them */ + if (impl->capture) + pw_stream_disconnect(impl->capture); + if (impl->playback) + pw_stream_disconnect(impl->playback); + if (impl->capture) pw_stream_destroy(impl->capture); if (impl->playback) pw_stream_destroy(impl->playback); + if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); + pw_properties_free(impl->capture_props); pw_properties_free(impl->playback_props); free(impl); @@ -453,6 +469,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)); } @@ -514,10 +531,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_properties_setf(props, PW_KEY_NODE_LINK_GROUP, "loopback-%u-%u", pid, id); if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); - - if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL) - pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, - "loopback-%u-%u", pid, id); + if (pw_properties_get(props, "resample.prefill") == NULL) + pw_properties_set(props, "resample.prefill", "true"); if ((str = pw_properties_get(props, "capture.props")) != NULL) pw_properties_update_string(impl->capture_props, str, strlen(str)); @@ -533,6 +548,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_LATENCY); copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_MEDIA_NAME); + copy_props(impl, props, "resample.prefill"); if ((str = pw_properties_get(props, PW_KEY_NODE_NAME)) == NULL) { pw_properties_setf(props, PW_KEY_NODE_NAME, @@ -545,6 +561,10 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (pw_properties_get(impl->playback_props, PW_KEY_NODE_NAME) == NULL) pw_properties_setf(impl->playback_props, PW_KEY_NODE_NAME, "output.%s", str); + if (pw_properties_get(impl->capture_props, PW_KEY_NODE_DESCRIPTION) == NULL) + pw_properties_set(impl->capture_props, PW_KEY_NODE_DESCRIPTION, str); + if (pw_properties_get(impl->playback_props, PW_KEY_NODE_DESCRIPTION) == NULL) + pw_properties_set(impl->playback_props, PW_KEY_NODE_DESCRIPTION, str); parse_audio_info(impl->capture_props, &impl->capture_info); parse_audio_info(impl->playback_props, &impl->playback_info); diff --git a/src/modules/module-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-native/connection.c b/src/modules/module-protocol-native/connection.c index 6383024bbdfa4a5ac2b74b9e0a7f9dc754bc1abe..78e84b552f0729d63980d0876841adca2ae40401 100644 --- a/src/modules/module-protocol-native/connection.c +++ b/src/modules/module-protocol-native/connection.c @@ -221,7 +221,10 @@ static int refill_buffer(struct pw_protocol_native_connection *conn, struct buff struct cmsghdr *cmsg = NULL; struct msghdr msg = { 0 }; struct iovec iov[1]; - char cmsgbuf[CMSG_SPACE(MAX_FDS_MSG * sizeof(int))]; + union { + char cmsgbuf[CMSG_SPACE(MAX_FDS_MSG * sizeof(int))]; + struct cmsghdr align; + } cmsgbuf; int n_fds = 0; size_t avail; @@ -231,7 +234,7 @@ static int refill_buffer(struct pw_protocol_native_connection *conn, struct buff iov[0].iov_len = avail; msg.msg_iov = iov; msg.msg_iovlen = 1; - msg.msg_control = cmsgbuf; + msg.msg_control = &cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); msg.msg_flags = MSG_CMSG_CLOEXEC | MSG_DONTWAIT; @@ -755,7 +758,10 @@ int pw_protocol_native_connection_flush(struct pw_protocol_native_connection *co struct msghdr msg = { 0 }; struct iovec iov[1]; struct cmsghdr *cmsg; - char cmsgbuf[CMSG_SPACE(MAX_FDS_MSG * sizeof(int))]; + union { + char cmsgbuf[CMSG_SPACE(MAX_FDS_MSG * sizeof(int))]; + struct cmsghdr align; + } cmsgbuf; int res = 0, *fds; uint32_t fds_len, to_close, n_fds, outfds, i; struct buffer *buf; @@ -786,7 +792,7 @@ int pw_protocol_native_connection_flush(struct pw_protocol_native_connection *co msg.msg_iovlen = 1; if (outfds > 0) { - msg.msg_control = cmsgbuf; + msg.msg_control = &cmsgbuf; msg.msg_controllen = CMSG_SPACE(fds_len); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; diff --git a/src/modules/module-protocol-native/defs.h b/src/modules/module-protocol-native/defs.h index 60adad027ef69a311f15e007cd1bb49e97ffdf0c..dc3c625942f17b045af5475eeb850e550d8e5cd2 100644 --- a/src/modules/module-protocol-native/defs.h +++ b/src/modules/module-protocol-native/defs.h @@ -31,13 +31,19 @@ int pw_protocol_native_connect_portal_screencast(struct pw_protocol_client *clie void (*done_callback) (void *data, int res), void *data); -static inline void *get_first_pod_from_data(void *data, size_t maxsize, off_t offset) +static inline void *get_first_pod_from_data(void *data, uint32_t maxsize, uint64_t offset) { void *pod; - if (offset + sizeof(struct spa_pod) > maxsize) + if (maxsize <= offset) return NULL; + + /* spa_pod_parser_advance() rounds up, so round down here to compensate */ + maxsize = SPA_ROUND_DOWN_N(maxsize - offset, 8); + if (maxsize < sizeof(struct spa_pod)) + return NULL; + pod = SPA_PTROFF(data, offset, void); - if (offset + SPA_POD_SIZE(pod) > maxsize) + if (SPA_POD_BODY_SIZE(pod) > maxsize - sizeof(struct spa_pod)) return NULL; return pod; } diff --git a/src/modules/module-protocol-native/protocol-footer.c b/src/modules/module-protocol-native/protocol-footer.c index bc4caa4a80b5014f61f3f131f1d3218f2248fe76..9b6fe62e60740934acf784043ed00c8c59709de1 100644 --- a/src/modules/module-protocol-native/protocol-footer.c +++ b/src/modules/module-protocol-native/protocol-footer.c @@ -45,7 +45,7 @@ struct footer_builder { unsigned int started:1; }; -#define FOOTER_BUILDER_INIT(builder) (struct footer_builder) { builder } +#define FOOTER_BUILDER_INIT(builder) ((struct footer_builder) { (builder) }) static void start_footer_entry(struct footer_builder *fb, uint32_t opcode) { diff --git a/src/modules/module-protocol-native/protocol-native.c b/src/modules/module-protocol-native/protocol-native.c index b2a1dc56bb15bcf21bb5b18a046320f3a85ef998..1c2d83e4ac400965e7d322da8aaff1f420906818 100644 --- a/src/modules/module-protocol-native/protocol-native.c +++ b/src/modules/module-protocol-native/protocol-native.c @@ -219,13 +219,13 @@ do { \ spa_pod_parser_get(prs, \ SPA_POD_Int(&(n_params)), NULL) < 0) \ return -EINVAL; \ - params = NULL; \ - if (n_params > 0) { \ + (params) = NULL; \ + if ((n_params) > 0) { \ uint32_t i; \ - if (n_params > MAX_PARAM_INFO) \ + if ((n_params) > MAX_PARAM_INFO) \ return -ENOSPC; \ - params = alloca(n_params * sizeof(struct spa_param_info)); \ - for (i = 0; i < n_params; i++) { \ + (params) = alloca((n_params) * sizeof(struct spa_param_info)); \ + for (i = 0; i < (n_params); i++) { \ if (spa_pod_parser_get(prs, \ SPA_POD_Id(&(params)[i].id), \ SPA_POD_Int(&(params)[i].flags), NULL) < 0) \ @@ -240,18 +240,18 @@ do { \ do { \ if (spa_pod_parser_push_struct(prs, f) < 0 || \ spa_pod_parser_get(prs, \ - SPA_POD_Int(&n_permissions), NULL) < 0) \ + SPA_POD_Int(&(n_permissions)), NULL) < 0) \ return -EINVAL; \ - permissions = NULL; \ - if (n_permissions > 0) { \ + (permissions) = NULL; \ + if ((n_permissions) > 0) { \ uint32_t i; \ - if (n_permissions > MAX_PERMISSIONS) \ + if ((n_permissions) > MAX_PERMISSIONS) \ return -ENOSPC; \ - permissions = alloca(n_permissions * sizeof(struct pw_permission)); \ - for (i = 0; i < n_permissions; i++) { \ + (permissions) = alloca((n_permissions) * sizeof(struct pw_permission)); \ + for (i = 0; i < (n_permissions); i++) { \ if (spa_pod_parser_get(prs, \ - SPA_POD_Int(&permissions[i].id), \ - SPA_POD_Int(&permissions[i].permissions), NULL) < 0) \ + SPA_POD_Int(&(permissions)[i].id), \ + SPA_POD_Int(&(permissions)[i].permissions), NULL) < 0) \ return -EINVAL; \ } \ } \ diff --git a/src/modules/module-protocol-native/v0/protocol-native.c b/src/modules/module-protocol-native/v0/protocol-native.c index bc05e9162dbebbee49a6ea49400e950c11e7d66c..65e055c16f1ae62cee4ccfe5c14cd27b9363c59e 100644 --- a/src/modules/module-protocol-native/v0/protocol-native.c +++ b/src/modules/module-protocol-native/v0/protocol-native.c @@ -415,7 +415,7 @@ struct spa_pod_prop_body0 { (iter) <= SPA_PTROFF((body), (_size)-(body)->value.size, __typeof__(*iter)); \ (iter) = SPA_PTROFF((iter), (body)->value.size, __typeof__(*iter))) -#define SPA0_POD_PROP_N_VALUES(b,size) ((size - sizeof(struct spa_pod_prop_body0)) / (b)->value.size) +#define SPA0_POD_PROP_N_VALUES(b,size) (((size) - sizeof(struct spa_pod_prop_body0)) / (b)->value.size) static int remap_from_v2(uint32_t type, void *body, uint32_t size, struct pw_impl_client *client, struct spa_pod_builder *builder) 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/manager.c b/src/modules/module-protocol-pulse/manager.c index 4841b67c09bf6a00d43a3258742479d713717833..1b08740d4c93ffe87712532a961ffdf27eed6d17 100644 --- a/src/modules/module-protocol-pulse/manager.c +++ b/src/modules/module-protocol-pulse/manager.c @@ -35,13 +35,13 @@ #define MAX_PARAMS 32 -#define manager_emit_sync(m) spa_hook_list_call(&m->hooks, struct pw_manager_events, sync, 0) -#define manager_emit_added(m,o) spa_hook_list_call(&m->hooks, struct pw_manager_events, added, 0, o) -#define manager_emit_updated(m,o) spa_hook_list_call(&m->hooks, struct pw_manager_events, updated, 0, o) -#define manager_emit_removed(m,o) spa_hook_list_call(&m->hooks, struct pw_manager_events, removed, 0, o) -#define manager_emit_metadata(m,o,s,k,t,v) spa_hook_list_call(&m->hooks, struct pw_manager_events, metadata,0,o,s,k,t,v) -#define manager_emit_disconnect(m) spa_hook_list_call(&m->hooks, struct pw_manager_events, disconnect, 0) -#define manager_emit_object_data_timeout(m,o,k) spa_hook_list_call(&m->hooks, struct pw_manager_events, object_data_timeout,0,o,k) +#define manager_emit_sync(m) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, sync, 0) +#define manager_emit_added(m,o) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, added, 0, o) +#define manager_emit_updated(m,o) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, updated, 0, o) +#define manager_emit_removed(m,o) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, removed, 0, o) +#define manager_emit_metadata(m,o,s,k,t,v) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, metadata,0,o,s,k,t,v) +#define manager_emit_disconnect(m) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, disconnect, 0) +#define manager_emit_object_data_timeout(m,o,k) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, object_data_timeout,0,o,k) struct object; 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..7cbe275bcd7f7686fe17bbced32327f2fe35fbba 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -89,7 +89,7 @@ #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 MAX_BLOCK (64*1024) #define TEMPORARY_MOVE_TIMEOUT (SPA_NSEC_PER_SEC) @@ -445,9 +445,17 @@ static uint32_t frac_to_bytes_round_up(struct spa_fraction val, const struct sam return (uint32_t) u; } -static uint32_t fix_playback_buffer_attr(struct stream *s, struct buffer_attr *attr) +static void clamp_latency(struct stream *s, struct spa_fraction *lat) { - uint32_t frame_size, max_prebuf, minreq, latency, max_latency; + if (lat->num * s->min_quantum.denom / lat->denom < s->min_quantum.num) + lat->num = (s->min_quantum.num * lat->denom + + (s->min_quantum.denom -1)) / s->min_quantum.denom; +} + +static uint64_t fix_playback_buffer_attr(struct stream *s, struct buffer_attr *attr, + uint32_t rate, struct spa_fraction *lat) +{ + uint32_t frame_size, max_prebuf, minreq, latency, max_latency, maxlength; struct defs *defs = &s->impl->defs; if ((frame_size = s->frame_size) == 0) @@ -455,20 +463,26 @@ static uint32_t fix_playback_buffer_attr(struct stream *s, struct buffer_attr *a if (frame_size == 0) frame_size = 4; + maxlength = SPA_ROUND_DOWN(MAXLENGTH, frame_size); + + pw_log_info("[%s] maxlength:%u tlength:%u minreq:%u prebuf:%u max:%u", + s->client->name, attr->maxlength, attr->tlength, + attr->minreq, attr->prebuf, maxlength); + minreq = frac_to_bytes_round_up(s->min_req, &s->ss); max_latency = defs->quantum_limit * frame_size; - if (attr->maxlength == (uint32_t) -1 || attr->maxlength > MAXLENGTH) - attr->maxlength = MAXLENGTH; - attr->maxlength = SPA_ROUND_UP(attr->maxlength, frame_size); + if (attr->maxlength == (uint32_t) -1 || attr->maxlength > maxlength) + attr->maxlength = maxlength; + else + attr->maxlength = SPA_ROUND_DOWN(attr->maxlength, frame_size); minreq = SPA_MIN(minreq, attr->maxlength); if (attr->tlength == (uint32_t) -1) attr->tlength = frac_to_bytes_round_up(s->default_tlength, &s->ss); - attr->tlength = SPA_MIN(attr->tlength, attr->maxlength); + attr->tlength = SPA_CLAMP(attr->tlength, minreq, attr->maxlength); attr->tlength = SPA_ROUND_UP(attr->tlength, frame_size); - attr->tlength = SPA_MAX(attr->tlength, minreq); if (attr->minreq == (uint32_t) -1) { uint32_t process = frac_to_bytes_round_up(s->default_req, &s->ss); @@ -520,11 +534,15 @@ static uint32_t fix_playback_buffer_attr(struct stream *s, struct buffer_attr *a attr->fragsize = 0; - pw_log_info("[%s] maxlength:%u tlength:%u minreq:%u/%u prebuf:%u latency:%u %u", + lat->num = latency / frame_size; + lat->denom = rate; + clamp_latency(s, lat); + + pw_log_info("[%s] maxlength:%u tlength:%u minreq:%u/%u prebuf:%u latency:%u/%u %u", s->client->name, attr->maxlength, attr->tlength, - attr->minreq, minreq, attr->prebuf, latency, frame_size); + attr->minreq, minreq, attr->prebuf, lat->num, lat->denom, frame_size); - return latency / frame_size; + return lat->num * SPA_USEC_PER_SEC / lat->denom; } static uint64_t set_playback_buffer_attr(struct stream *s, struct buffer_attr *attr) @@ -538,16 +556,10 @@ static uint64_t set_playback_buffer_attr(struct stream *s, struct buffer_attr *a char attr_prebuf[32]; char attr_minreq[32]; - lat.denom = s->ss.rate; - lat.num = fix_playback_buffer_attr(s, attr); + lat_usec = fix_playback_buffer_attr(s, attr, s->ss.rate, &lat); s->attr = *attr; - if (lat.num * s->min_quantum.denom / lat.denom < s->min_quantum.num) - lat.num = (s->min_quantum.num * lat.denom + - (s->min_quantum.denom -1)) / s->min_quantum.denom; - lat_usec = lat.num * SPA_USEC_PER_SEC / lat.denom; - snprintf(latency, sizeof(latency), "%u/%u", lat.num, lat.denom); snprintf(rate, sizeof(rate), "1/%u", lat.denom); snprintf(attr_maxlength, sizeof(attr_maxlength), "%u", s->attr.maxlength); @@ -642,46 +654,57 @@ static int reply_create_playback_stream(struct stream *stream, struct pw_manager return client_queue_message(client, reply); } -static uint32_t fix_record_buffer_attr(struct stream *s, struct buffer_attr *attr) +static uint64_t fix_record_buffer_attr(struct stream *s, struct buffer_attr *attr, + uint32_t rate, struct spa_fraction *lat) { - uint32_t frame_size, minfrag, latency; + uint32_t frame_size, minfrag, latency, maxlength; if ((frame_size = s->frame_size) == 0) frame_size = sample_spec_frame_size(&s->ss); if (frame_size == 0) frame_size = 4; - if (attr->maxlength == (uint32_t) -1 || attr->maxlength > MAXLENGTH) - attr->maxlength = MAXLENGTH; - attr->maxlength -= attr->maxlength % frame_size; + maxlength = SPA_ROUND_DOWN(MAXLENGTH, frame_size); + + pw_log_info("[%s] maxlength:%u fragsize:%u framesize:%u", + s->client->name, attr->maxlength, attr->fragsize, + frame_size); + + if (attr->maxlength == (uint32_t) -1 || attr->maxlength > maxlength) + attr->maxlength = maxlength; + else + attr->maxlength = SPA_ROUND_DOWN(attr->maxlength, frame_size); attr->maxlength = SPA_MAX(attr->maxlength, frame_size); minfrag = frac_to_bytes_round_up(s->min_frag, &s->ss); 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_MAX(attr->fragsize, minfrag); - attr->fragsize = SPA_MAX(attr->fragsize, frame_size); - - if (attr->fragsize > attr->maxlength) - attr->fragsize = attr->maxlength; + attr->fragsize = SPA_CLAMP(attr->fragsize, minfrag, attr->maxlength); + attr->fragsize = SPA_ROUND_UP(attr->fragsize, frame_size); attr->tlength = attr->minreq = attr->prebuf = 0; - if (s->early_requests) { - latency = attr->fragsize; - } else if (s->adjust_latency) { - latency = attr->fragsize; - } else { - latency = attr->fragsize; + /* make sure we can queue at least to fragsize without overruns */ + if (attr->maxlength < attr->fragsize * 4) { + attr->maxlength = attr->fragsize * 4; + if (attr->maxlength > maxlength) { + attr->maxlength = maxlength; + attr->fragsize = SPA_ROUND_DOWN(maxlength / 4, frame_size); + } } - pw_log_info("[%s] maxlength:%u fragsize:%u minfrag:%u latency:%u", + latency = attr->fragsize; + + lat->num = latency / frame_size; + lat->denom = rate; + clamp_latency(s, lat); + + pw_log_info("[%s] maxlength:%u fragsize:%u minfrag:%u latency:%u/%u", s->client->name, attr->maxlength, attr->fragsize, minfrag, - latency); + lat->num, lat->denom); - return latency / frame_size; + return lat->num * SPA_USEC_PER_SEC / lat->denom; } static uint64_t set_record_buffer_attr(struct stream *s, struct buffer_attr *attr) @@ -693,13 +716,9 @@ static uint64_t set_record_buffer_attr(struct stream *s, struct buffer_attr *att struct spa_fraction lat; uint64_t lat_usec; - lat.denom = s->ss.rate; - lat.num = fix_record_buffer_attr(s, &s->attr); + lat_usec = fix_record_buffer_attr(s, attr, s->ss.rate, &lat); - if (lat.num * s->min_quantum.denom / lat.denom < s->min_quantum.num) - lat.num = (s->min_quantum.num * lat.denom + - (s->min_quantum.denom -1)) / s->min_quantum.denom; - lat_usec = lat.num * SPA_USEC_PER_SEC / lat.denom; + s->attr = *attr; snprintf(latency, sizeof(latency), "%u/%u", lat.num, lat.denom); snprintf(rate, sizeof(rate), "1/%u", lat.denom); @@ -748,13 +767,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 +867,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 +976,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 +991,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 +1053,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 +1292,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,9 +1347,11 @@ 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_MIN(avail, MAX_BLOCK); + towrite = SPA_MIN(towrite, stream->attr.fragsize); towrite = SPA_ROUND_DOWN(towrite, stream->frame_size); msg = message_alloc(impl, stream->channel, towrite); @@ -1400,7 +1431,7 @@ static void stream_process(void *data) spa_ringbuffer_read_data(&stream->ring, stream->buffer, MAXLENGTH, index % MAXLENGTH, - p, avail); + p, SPA_MIN((uint32_t)avail, size)); index += avail; } pd.playing_for = size; @@ -1717,9 +1748,11 @@ static int do_create_playback_stream(struct client *client, uint32_t command, ui stream->underrun_for = -1; if (rate != 0) { + struct spa_fraction lat; + fix_playback_buffer_attr(stream, &attr, rate, &lat); pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", rate); pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", - fix_playback_buffer_attr(stream, &attr), rate); + lat.num, lat.denom); } if (no_remix) pw_properties_set(props, PW_KEY_STREAM_DONT_REMIX, "true"); @@ -1792,7 +1825,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, @@ -1978,9 +2011,11 @@ static int do_create_record_stream(struct client *client, uint32_t command, uint no_move = false; if (rate != 0) { + struct spa_fraction lat; + fix_record_buffer_attr(stream, &attr, rate, &lat); pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", rate); pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", - fix_record_buffer_attr(stream, &attr), rate); + lat.num, lat.denom); } if (peak_detect) pw_properties_set(props, PW_KEY_STREAM_MONITOR, "true"); @@ -2111,7 +2146,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 +2195,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, @@ -2697,11 +2737,17 @@ static int do_flush_trigger_prebuf_stream(struct client *client, uint32_t comman break; case COMMAND_TRIGGER_PLAYBACK_STREAM: case COMMAND_PREBUF_PLAYBACK_STREAM: + if (stream->type != STREAM_TYPE_PLAYBACK) + return -ENOENT; + if (command == COMMAND_TRIGGER_PLAYBACK_STREAM) + stream->in_prebuf = false; + else if (stream->attr.prebuf > 0) + stream->in_prebuf = true; + stream_send_request(stream); break; default: return -EINVAL; } - return reply_simple_ack(client, tag); } @@ -3236,6 +3282,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 +3722,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 +3835,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 +3927,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 +4049,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 +4388,7 @@ error_invalid: goto error; error: if (reply) - message_free(impl, reply, false, false); + message_free(reply, false, false); return res; } @@ -4389,7 +4464,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 +4788,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 +4843,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 +4880,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 +4912,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 +5454,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..af1913b9518033c35924f3229fcee8e018362743 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; @@ -167,6 +166,8 @@ static int handle_memblock(struct client *client, struct message *msg) index += diff; filled += diff; stream->write_index += diff; + if ((flags & FLAG_SEEKMASK) == SEEK_RELATIVE) + stream->requested -= diff; if (filled < 0) { /* underrun, reported on reader side */ @@ -183,14 +184,15 @@ static int handle_memblock(struct client *client, struct message *msg) msg->data, SPA_MIN(msg->length, MAXLENGTH)); index += msg->length; - stream->write_index += msg->length; spa_ringbuffer_write_update(&stream->ring, index); - stream->requested -= SPA_MIN(msg->length, stream->requested); + + stream->write_index += msg->length; + stream->requested -= msg->length; stream_send_request(stream); finish: - message_free(impl, msg, false, false); + message_free(msg, false, false); return res; } @@ -264,7 +266,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 +421,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 +495,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..617ca05244ffd3e0fe542d1295887412eaaf14e9 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")) { @@ -192,7 +195,7 @@ int create_pid_file(void) { strcat(pid_file, "/pid"); - if ((f = fopen(pid_file, "w")) == NULL) { + if ((f = fopen(pid_file, "we")) == NULL) { res = -errno; pw_log_error("failed to open pid file: %m"); return res; 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..483e7d301bee0942e2371ae82658034fb2ce1b20 100644 --- a/src/modules/module-raop-discover.c +++ b/src/modules/module-raop-discover.c @@ -113,7 +113,7 @@ struct tunnel_info { const char *domain; }; -#define TUNNEL_INFO(...) (struct tunnel_info){ __VA_ARGS__ } +#define TUNNEL_INFO(...) ((struct tunnel_info){ __VA_ARGS__ }) struct tunnel { struct spa_list link; @@ -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..245bb83a6574f1f1147b4ccc12e4e9fa176e657c 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -66,10 +66,63 @@ * * Creates a new Sink to stream to an Airplay device. * + * Normally this sink is automatically created with \ref page_module_raop_discover + * with the right parameters but it is possible to manually create a RAOP sink + * as well. + * * ## Module Options * + * Options specific to the behavior of this module + * + * - `raop.hostname`: The hostname of the remote end. + * - `raop.port`: The port of the remote end. + * - `raop.transport`: The data transport to use, one of "udp" or "tcp". Defaults + * to "udp". + * - `raop.encryption.type`: The encryption type to use. One of "none", "RSA" or + * "auth_setup". Default is "none". + * - `raop.audio.codec`: The audio codec to use. Needs to be "PCM". Defaults to "PCM". + * - `raop.password`: The password to use. + * - `stream.props = {}`: properties to be passed to the sink stream + * + * Options with well-known behavior. + * + * - \ref PW_KEY_REMOTE_NAME + * - \ref PW_KEY_AUDIO_FORMAT + * - \ref PW_KEY_AUDIO_RATE + * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_POSITION + * - \ref PW_KEY_NODE_NAME + * - \ref PW_KEY_NODE_DESCRIPTION + * - \ref PW_KEY_NODE_GROUP + * - \ref PW_KEY_NODE_LATENCY + * - \ref PW_KEY_NODE_VIRTUAL + * - \ref PW_KEY_MEDIA_CLASS + * * ## Example configuration * + *\code{.unparsed} + * context.modules = [ + * { name = libpipewire-module-raop-sink + * args = { + * # Set the remote address to tunnel to + * raop.hostname = "my-raop-device" + * raop.port = 8190 + * #raop.transport = "udp" + * raop.encryption = "RSA" + * #raop.audio.codec = "PCM" + * #raop.password = "****" + * #audio.format = "S16" + * #audio.rate = 44100 + * #audio.channels = 22 + * #audio.position = [ FL FR ] + * stream.props = { + * # extra sink properties + * } + * } + * } + * ] + *\endcode + * * ## See also * * \ref page_module_raop_discover @@ -106,11 +159,17 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define DEFAULT_LATENCY (DEFAULT_RATE*2) -#define MODULE_USAGE "[ node.latency=<latency as fraction> ] " \ +#define MODULE_USAGE "[ raop.hostname=<name of host> ] " \ + "[ raop.port=<remote port> ] " \ + "[ raop.transport=<transport, default:udp> ] " \ + "[ raop.encryption.type=<encryption, default:none> ] " \ + "[ raop.audio.codec=PCM ] " \ + "[ raop.password=<password for auth> ] " \ + "[ node.latency=<latency as fraction> ] " \ "[ node.name=<name of the nodes> ] " \ "[ node.description=<description of the nodes> ] " \ "[ audio.format=<format, default:"DEFAULT_FORMAT"> ] " \ - "[ 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 +189,7 @@ enum { enum { CRYPTO_NONE, CRYPTO_RSA, + CRYPTO_AUTH_SETUP, }; enum { CODEC_PCM, @@ -257,7 +317,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 +339,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 +361,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 +407,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) @@ -359,6 +421,7 @@ static int flush_to_udp_packet(struct impl *impl) switch (impl->codec) { case CODEC_PCM: + case CODEC_ALAC: len = write_codec_pcm(dst, impl->buffer, n_frames); break; default: @@ -373,7 +436,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; @@ -401,6 +464,7 @@ static int flush_to_tcp_packet(struct impl *impl) switch (impl->codec) { case CODEC_PCM: + case CODEC_ALAC: len = write_codec_pcm(dst, impl->buffer, n_frames); break; default: @@ -417,7 +481,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 +657,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 +676,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 +904,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 +937,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 +1060,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 +1120,29 @@ 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) +{ + static const unsigned char content[33] = + "\x01" + "\x59\x02\xed\xe9\x0d\x4e\xf2\xbd\x4c\xb6\x8a\x63\x30\x03\x82\x07" + "\xa9\x4d\xbd\x50\xd8\xaa\x46\x5b\x5d\x8c\x01\x2a\x0c\x7e\x1d\x4e"; + + return pw_rtsp_client_url_send(impl->rtsp, "/auth-setup", "POST", &impl->headers->dict, + "application/octet-stream", content, sizeof(content), + rtsp_auth_setup_reply, impl); +} + static const char *find_attr(char **tokens, const char *key) { int i; @@ -1143,7 +1238,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 +1263,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 +1582,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 +1718,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 +1736,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 +1746,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; } @@ -1648,8 +1758,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) str = "PCM"; if (spa_streq(str, "PCM")) impl->codec = CODEC_PCM; + else if (spa_streq(str, "ALAC")) + impl->codec = CODEC_ALAC; 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..6e80b17b1bf168be78e68febaee6f81f6d24e9ed 100644 --- a/src/modules/module-raop/rtsp-client.c +++ b/src/modules/module-raop/rtsp-client.c @@ -44,11 +44,18 @@ struct message { void *data; size_t len; size_t offset; - int cseq; + uint32_t cseq; void (*reply) (void *user_data, int status, const struct spa_dict *headers); void *user_data; }; +enum client_recv_state { + CLIENT_RECV_NONE, + CLIENT_RECV_STATUS, + CLIENT_RECV_HEADERS, + CLIENT_RECV_CONTENT, +}; + struct pw_rtsp_client { struct pw_loop *loop; struct pw_properties *props; @@ -67,15 +74,15 @@ struct pw_rtsp_client { struct spa_source *source; unsigned int connecting:1; unsigned int need_flush:1; - unsigned int wait_status:1; + enum client_recv_state recv_state; int status; char line_buf[1024]; size_t line_pos; struct pw_properties *headers; + size_t content_length; - char *session; - int cseq; + uint32_t cseq; struct spa_list messages; struct spa_list pending; @@ -102,6 +109,7 @@ struct pw_rtsp_client *pw_rtsp_client_new(struct pw_loop *main_loop, spa_list_init(&client->pending); spa_hook_list_init(&client->listener_list); client->headers = pw_properties_new(NULL, NULL); + client->recv_state = CLIENT_RECV_NONE; pw_log_info("new client %p", client); @@ -145,7 +153,7 @@ int pw_rtsp_client_get_local_ip(struct pw_rtsp_client *client, if (ip) inet_ntop(client->local_addr.sa.sa_family, &client->local_addr.in.sin_addr, ip, len); - } else if (client->local_addr.sa.sa_family == AF_INET6) { + } else if (client->local_addr.sa.sa_family == AF_INET6) { *version = 6; if (ip) inet_ntop(client->local_addr.sa.sa_family, @@ -160,7 +168,7 @@ static int handle_connect(struct pw_rtsp_client *client, int fd) { int res, ip_version; socklen_t len; - char local_ip[INET6_ADDRSTRLEN]; + char local_ip[INET6_ADDRSTRLEN]; len = sizeof(res); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &res, &len) < 0) { @@ -180,13 +188,19 @@ static int handle_connect(struct pw_rtsp_client *client, int fd) if (ip_version == 4) asprintf(&client->url, "rtsp://%s/%s", local_ip, client->session_id); - else + else asprintf(&client->url, "rtsp://[%s]/%s", local_ip, client->session_id); pw_log_info("connected local ip %s", local_ip); client->connecting = false; - client->wait_status = true; + + client->recv_state = CLIENT_RECV_STATUS; + pw_properties_clear(client->headers); + client->status = 0; + client->line_pos = 0; + client->content_length = 0; + pw_rtsp_client_emit_connected(client); return 0; @@ -226,7 +240,7 @@ static int read_line(struct pw_rtsp_client *client, char **buf) return 0; } -static struct message *find_pending(struct pw_rtsp_client *client, int cseq) +static struct message *find_pending(struct pw_rtsp_client *client, uint32_t cseq) { struct message *msg; spa_list_for_each(msg, &client->pending, link) { @@ -236,74 +250,145 @@ static struct message *find_pending(struct pw_rtsp_client *client, int cseq) return NULL; } -static int process_input(struct pw_rtsp_client *client) +static int process_status(struct pw_rtsp_client *client, char *buf) { - char *buf = NULL; - int res; + const char *state = NULL, *s; + size_t len; - if ((res = read_line(client, &buf)) <= 0) - return res; + pw_log_info("status: %s", buf); - pw_log_debug("%s", buf); + s = pw_split_walk(buf, " ", &len, &state); + if (!spa_strstartswith(s, "RTSP/")) + return -EPROTO; - if (client->wait_status) { - const char *state = NULL, *s; - size_t len; + s = pw_split_walk(buf, " ", &len, &state); + if (s == NULL) + return -EPROTO; - pw_log_info("status: %s", buf); + client->status = atoi(s); + if (client->status == 0) + return -EPROTO; - s = pw_split_walk(buf, " ", &len, &state); - if (!spa_strstartswith(s, "RTSP/")) - goto error; + s = pw_split_walk(buf, " ", &len, &state); + if (s == NULL) + return -EPROTO; - s = pw_split_walk(buf, " ", &len, &state); - if (s == NULL) - goto error; + pw_properties_clear(client->headers); + client->recv_state = CLIENT_RECV_HEADERS; - client->status = atoi(s); + return 0; +} - s = pw_split_walk(buf, " ", &len, &state); - if (s == NULL) - goto error; +static void dispatch_handler(struct pw_rtsp_client *client) +{ + uint32_t cseq; + if (pw_properties_fetch_uint32(client->headers, "CSeq", &cseq) < 0) + return; + + pw_log_info("received reply to request with cseq:%" PRIu32, cseq); + + struct message *msg = find_pending(client, cseq); + if (msg) { + msg->reply(msg->user_data, client->status, &client->headers->dict); + spa_list_remove(&msg->link); + free(msg); + } + else { + pw_rtsp_client_emit_message(client, client->status, &client->headers->dict); + } +} - client->wait_status = false; - pw_properties_clear(client->headers); - } else { - if (strlen(buf) == 0) { - int cseq; - struct message *msg; - const struct spa_dict_item *it; +static void process_received_message(struct pw_rtsp_client *client) +{ + client->recv_state = CLIENT_RECV_STATUS; + dispatch_handler(client); +} - spa_dict_for_each(it, &client->headers->dict) - pw_log_info(" %s: %s", it->key, it->value); +static int process_header(struct pw_rtsp_client *client, char *buf) +{ + if (strlen(buf) > 0) { + char *key = buf, *value; - cseq = pw_properties_get_int32(client->headers, "CSeq", 0); + value = strstr(buf, ":"); + if (value == NULL) + return -EPROTO; - if ((msg = find_pending(client, cseq)) != NULL) { - msg->reply(msg->user_data, client->status, &client->headers->dict); - spa_list_remove(&msg->link); - free(msg); - } else { - pw_rtsp_client_emit_message(client, client->status, - &client->headers->dict); - } - client->wait_status = true; - } else { - char *key, *value; + *value++ = '\0'; - key = buf; - value = strstr(buf, ":"); - if (value == NULL) - goto error; - *value++ = '\0'; - while (*value == ' ') - value++; - pw_properties_set(client->headers, key, value); + value = pw_strip(value, " "); + + pw_properties_set(client->headers, key, value); + } + else { + const struct spa_dict_item *it; + spa_dict_for_each(it, &client->headers->dict) + pw_log_info(" %s: %s", it->key, it->value); + + client->content_length = pw_properties_get_uint32(client->headers, "Content-Length", 0); + if (client->content_length > 0) + client->recv_state = CLIENT_RECV_CONTENT; + else + process_received_message(client); + } + + return 0; +} + +static int process_content(struct pw_rtsp_client *client) +{ + char buf[1024]; + + while (client->content_length > 0) { + const size_t max_recv = SPA_MIN(sizeof(buf), client->content_length); + + ssize_t res = read(client->source->fd, buf, max_recv); + if (res == 0) + return -EPIPE; + + if (res < 0) { + res = -errno; + if (res == -EAGAIN || res == -EWOULDBLOCK) + return 0; + + return res; } + + spa_assert((size_t) res <= client->content_length); + client->content_length -= res; } + + if (client->content_length == 0) + process_received_message(client); + return 0; -error: - return -EPROTO; +} + +static int process_input(struct pw_rtsp_client *client) +{ + if (client->recv_state == CLIENT_RECV_STATUS || client->recv_state == CLIENT_RECV_HEADERS) { + char *buf = NULL; + int res; + + if ((res = read_line(client, &buf)) <= 0) + return res; + + pw_log_debug("received line: %s", buf); + + switch (client->recv_state) { + case CLIENT_RECV_STATUS: + return process_status(client, buf); + case CLIENT_RECV_HEADERS: + return process_header(client, buf); + default: + spa_assert_not_reached(); + } + } + else if (client->recv_state == CLIENT_RECV_CONTENT) { + return process_content(client); + } + else { + spa_assert_not_reached(); + } } static int flush_output(struct pw_rtsp_client *client) @@ -366,19 +451,19 @@ on_source_io(void *data, int fd, uint32_t mask) if (mask & SPA_IO_IN) { if ((res = process_input(client)) < 0) goto error; - } + } if (mask & SPA_IO_OUT || client->need_flush) { if (client->connecting) { if ((res = handle_connect(client, fd)) < 0) goto error; } res = flush_output(client); - if (res >= 0) { + if (res >= 0) { pw_loop_update_io(client->loop, client->source, client->source->mask & ~SPA_IO_OUT); } else if (res != -EAGAIN) goto error; - } + } done: return; error: @@ -466,9 +551,9 @@ 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, + const char *content_type, const void *content, size_t content_length, void (*reply) (void *user_data, int status, const struct spa_dict *headers), void *user_data) { @@ -476,7 +561,7 @@ int pw_rtsp_client_send(struct pw_rtsp_client *client, size_t len; const struct spa_dict_item *it; struct message *msg; - int cseq; + uint32_t cseq; if ((f = open_memstream((char**)&msg, &len)) == NULL) return -errno; @@ -485,21 +570,21 @@ 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, "CSeq: %d\r\n", cseq); + fprintf(f, "%s %s RTSP/1.0\r\n", cmd, url); + fprintf(f, "CSeq: %" PRIu32 "\r\n", cseq); if (headers != NULL) { spa_dict_for_each(it, headers) fprintf(f, "%s: %s\r\n", it->key, it->value); } if (content_type != NULL && content != NULL) { - fprintf(f, "Content-Type: %s\r\nContent-Length: %d\r\n", - content_type, (int)strlen(content)); + fprintf(f, "Content-Type: %s\r\nContent-Length: %zu\r\n", + content_type, content_length); } fprintf(f, "\r\n"); if (content_type && content) - fprintf(f, "%s", content); + fwrite(content, 1, content_length, f); fclose(f); @@ -516,6 +601,19 @@ int pw_rtsp_client_send(struct pw_rtsp_client *client, if (client->source && !(client->source->mask & SPA_IO_OUT)) { pw_loop_update_io(client->loop, client->source, client->source->mask | SPA_IO_OUT); - } + } return 0; } + +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) +{ + const size_t content_length = content ? strlen(content) : 0; + + return pw_rtsp_client_url_send(client, client->url, cmd, headers, + content_type, content, content_length, + reply, user_data); +} diff --git a/src/modules/module-raop/rtsp-client.h b/src/modules/module-raop/rtsp-client.h index 4588eb8303d7be6a197c2735688587d737703318..75b1ce6a15795d3166d3678758739a465187efbe 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 void *content, size_t content_length, + 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..5cfd766e40290363faee4d4208cebbee6c3fe8fb 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,33 +951,66 @@ 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; } +#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( @@ -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/modules/module-session-manager/client-session/endpoint-link.h b/src/modules/module-session-manager/client-session/endpoint-link.h index 936eed13074e31bcc45f04bd2464b95074d2dddb..c75f975bab93263630ee650bda2a2fccde8106ce 100644 --- a/src/modules/module-session-manager/client-session/endpoint-link.h +++ b/src/modules/module-session-manager/client-session/endpoint-link.h @@ -25,6 +25,7 @@ #ifndef MODULE_SESSION_MANAGER_ENDPOINT_LINK_H #define MODULE_SESSION_MANAGER_ENDPOINT_LINK_H +#include <stdint.h> #ifdef __cplusplus extern "C" { diff --git a/src/modules/module-session-manager/client-session/session.h b/src/modules/module-session-manager/client-session/session.h index ec6549e82f59aef7c4eda03b9e27c7e3aaa76833..a94b18fc27a3bb956b10711b2a95d2b7be71e0fc 100644 --- a/src/modules/module-session-manager/client-session/session.h +++ b/src/modules/module-session-manager/client-session/session.h @@ -25,6 +25,7 @@ #ifndef MODULE_SESSION_MANAGER_SESSION_H #define MODULE_SESSION_MANAGER_SESSION_H +#include <stdint.h> #ifdef __cplusplus extern "C" { diff --git a/src/modules/module-zeroconf-discover.c b/src/modules/module-zeroconf-discover.c index 317f87ee428e72590d657890b63e5189debb3e55..2cf45143049492311dfe2bfb6c317e55339ce04a 100644 --- a/src/modules/module-zeroconf-discover.c +++ b/src/modules/module-zeroconf-discover.c @@ -109,7 +109,7 @@ struct tunnel_info { const char *domain; }; -#define TUNNEL_INFO(...) (struct tunnel_info){ __VA_ARGS__ } +#define TUNNEL_INFO(...) ((struct tunnel_info){ __VA_ARGS__ }) struct tunnel { struct spa_list link; diff --git a/src/pipewire/array.h b/src/pipewire/array.h index 4e2dd72ebbe95589e42ac1cfe97cccf7c495131e..44f31a897786dab9d20c13e2e88779b81567a9ca 100644 --- a/src/pipewire/array.h +++ b/src/pipewire/array.h @@ -52,7 +52,7 @@ struct pw_array { size_t extend; /**< number of bytes to extend with */ }; -#define PW_ARRAY_INIT(extend) (struct pw_array) { NULL, 0, 0, extend } +#define PW_ARRAY_INIT(extend) ((struct pw_array) { NULL, 0, 0, (extend) }) #define pw_array_get_len_s(a,s) ((a)->size / (s)) #define pw_array_get_unchecked_s(a,idx,s,t) SPA_PTROFF((a)->data,(idx)*(s),t) @@ -67,17 +67,17 @@ struct pw_array { #define pw_array_first(a) ((a)->data) #define pw_array_end(a) SPA_PTROFF((a)->data, (a)->size, void) -#define pw_array_check(a,p) (SPA_PTROFF(p,sizeof(*p),void) <= pw_array_end(a)) +#define pw_array_check(a,p) (SPA_PTROFF(p,sizeof(*(p)),void) <= pw_array_end(a)) #define pw_array_for_each(pos, array) \ - for (pos = (__typeof__(pos)) pw_array_first(array); \ + for ((pos) = (__typeof__(pos)) pw_array_first(array); \ pw_array_check(array, pos); \ (pos)++) #define pw_array_consume(pos, array) \ - for (pos = (__typeof__(pos)) pw_array_first(array); \ + for ((pos) = (__typeof__(pos)) pw_array_first(array); \ pw_array_check(array, pos); \ - pos = (__typeof__(pos)) pw_array_first(array)) + (pos) = (__typeof__(pos)) pw_array_first(array)) #define pw_array_remove(a,p) \ ({ \ 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..72c2797ab06356fd5802949d7ed56f56507b3516 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) @@ -1326,7 +1205,7 @@ again: if (settings->clock_rate_update_mode == CLOCK_RATE_UPDATE_MODE_HARD) suspend_driver(context, n); } else { - if (n->info.state >= PW_NODE_STATE_IDLE) + if (n->info.state >= PW_NODE_STATE_SUSPENDED) suspend_driver(context, n); } /* we're setting the pending rate. This will become the new @@ -1376,12 +1255,15 @@ 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; } - pw_log_debug("%p: driving %p running:%d passive:%d quantum:%u '%s'", + pw_log_debug("%p: driver %p running:%d passive:%d quantum:%u '%s'", context, n, running, n->passive, quantum, n->name); /* first change the node states of the followers to the new target */ 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/filter.c b/src/pipewire/filter.c index 0eb368def05462567da909b84fef8c2bcfdd600c..334125ff42b0e70469196ca56bdc9a59db26c69c 100644 --- a/src/pipewire/filter.c +++ b/src/pipewire/filter.c @@ -1179,7 +1179,7 @@ struct match { struct pw_filter *filter; int count; }; -#define MATCH_INIT(f) (struct match){ .filter = f } +#define MATCH_INIT(f) ((struct match){ .filter = (f) }) static int execute_match(void *data, const char *location, const char *action, const char *val, size_t len) diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index 095f1eabcde99540d28cd8433ccfabcce5bd830c..b0293772927d2cc1affb4835a4338d3b6956f3ed 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); @@ -617,7 +619,7 @@ int pw_impl_link_activate(struct pw_impl_link *this) pw_log_debug("%p: activate activated:%d state:%s", this, impl->activated, pw_link_state_as_string(this->info.state)); - if (impl->activated || !this->prepared || !impl->inode->active || !impl->onode->active) + if (impl->activated || !this->prepared || !impl->inode->added || !impl->onode->active) return 0; if (!impl->io_set) { @@ -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..25664b9954c0e44a2f24e3f8a84c93c0da8770e9 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -167,6 +167,7 @@ do_node_remove(struct spa_loop *loop, spa_loop_remove_source(loop, &this->source); remove_node(this); } + this->added = false; return 0; } @@ -187,12 +188,12 @@ static void node_deactivate(struct pw_impl_node *this) pw_loop_invoke(this->data_loop, do_node_remove, 1, NULL, 0, true, this); } -static int pause_node(struct pw_impl_node *this) +static int idle_node(struct pw_impl_node *this) { struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); int res = 0; - pw_log_debug("%p: pause node state:%s pending:%s pause-on-idle:%d", this, + pw_log_debug("%p: idle node state:%s pending:%s pause-on-idle:%d", this, pw_node_state_as_string(this->info.state), pw_node_state_as_string(impl->pending_state), impl->pause_on_idle); @@ -200,6 +201,9 @@ static int pause_node(struct pw_impl_node *this) if (impl->pending_state <= PW_NODE_STATE_IDLE) return 0; + if (!impl->pause_on_idle) + return 0; + node_deactivate(this); res = spa_node_send_command(this->node, @@ -210,19 +214,26 @@ static int pause_node(struct pw_impl_node *this) return res; } -static void node_activate(struct pw_impl_node *this) +static void node_activate_outputs(struct pw_impl_node *this) { struct pw_impl_port *port; pw_log_debug("%p: activate", this); - spa_list_for_each(port, &this->input_ports, link) { + spa_list_for_each(port, &this->output_ports, link) { struct pw_impl_link *link; - spa_list_for_each(link, &port->links, input_link) + spa_list_for_each(link, &port->links, output_link) pw_impl_link_activate(link); } - spa_list_for_each(port, &this->output_ports, link) { +} + +static void node_activate_inputs(struct pw_impl_node *this) +{ + struct pw_impl_port *port; + + pw_log_debug("%p: activate", this); + spa_list_for_each(port, &this->input_ports, link) { struct pw_impl_link *link; - spa_list_for_each(link, &port->links, output_link) + spa_list_for_each(link, &port->links, input_link) pw_impl_link_activate(link); } } @@ -232,12 +243,15 @@ static int start_node(struct pw_impl_node *this) struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); int res = 0; - node_activate(this); + /* First activate the outputs so that when the node starts pushing, + * we can process the outputs */ + node_activate_outputs(this); if (impl->pending_state >= PW_NODE_STATE_RUNNING) return 0; - pw_log_debug("%p: start node", this); + pw_log_debug("%p: start node driving:%d driver:%d added:%d", this, + this->driving, this->driver, this->added); if (!(this->driving && this->driver)) { impl->pending_play = true; @@ -336,6 +350,7 @@ do_node_add(struct spa_loop *loop, spa_loop_add_source(loop, &this->source); add_node(this, driver); } + this->added = true; return 0; } @@ -346,6 +361,9 @@ static void node_update_state(struct pw_impl_node *node, enum pw_node_state stat switch (state) { case PW_NODE_STATE_RUNNING: + pw_log_debug("%p: start node driving:%d driver:%d added:%d", node, + node->driving, node->driver, node->added); + if (node->driving && node->driver) { res = spa_node_send_command(node->node, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start)); @@ -354,8 +372,11 @@ static void node_update_state(struct pw_impl_node *node, enum pw_node_state stat error = spa_aprintf("Start error: %s", spa_strerror(res)); } } - if (res >= 0) + if (res >= 0) { pw_loop_invoke(node->data_loop, do_node_add, 1, NULL, 0, true, node); + /* now activate the inputs */ + node_activate_inputs(node); + } break; default: break; @@ -424,6 +445,9 @@ static int suspend_node(struct pw_impl_node *this) p->state = PW_IMPL_PORT_STATE_CONFIGURE; } + pw_log_debug("%p: suspend node driving:%d driver:%d added:%d", this, + this->driving, this->driver, this->added); + res = spa_node_send_command(this->node, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Suspend)); if (res == -ENOTSUP) @@ -1047,7 +1071,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; @@ -1078,6 +1102,12 @@ static inline int process_node(void *data) a->status = PW_NODE_ACTIVATION_AWAKE; a->awake_time = SPA_TIMESPEC_TO_NSEC(&ts); + if (!this->added) { + /* This should not happen here. We activate the input + * links after we add the node to the graph. */ + pw_log_warn("%p: scheduling non-active node", this); + return -EIO; + } pw_log_trace_fp("%p: process %"PRIu64, this, a->awake_time); /* when transport sync is not supported, just clear the flag */ @@ -1145,7 +1175,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 +1292,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 +1643,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) { @@ -2165,8 +2195,7 @@ int pw_impl_node_set_state(struct pw_impl_node *node, enum pw_node_state state) break; case PW_NODE_STATE_IDLE: - if (impl->pause_on_idle) - res = pause_node(node); + res = idle_node(node); break; case PW_NODE_STATE_RUNNING: @@ -2195,8 +2224,7 @@ int pw_impl_node_set_state(struct pw_impl_node *node, enum pw_node_state state) state < PW_NODE_STATE_RUNNING && impl->pending_play) { impl->pending_play = false; - spa_node_send_command(node->node, - &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Pause)); + idle_node(node); } pw_work_queue_cancel(impl->work, node, impl->pending_id); node->info.state = impl->pending_state; 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/log.h b/src/pipewire/log.h index 26ffc20f9a0d6e5d3690abb6192079bf40c565f3..f91dc1136e96a4a3f5065708ded5a57891a507db 100644 --- a/src/pipewire/log.h +++ b/src/pipewire/log.h @@ -116,7 +116,7 @@ _pw_log_topic_new(struct spa_log_topic *topic); */ #define PW_LOG_TOPIC_STATIC(var, topic) \ static struct spa_log_topic var##__LINE__ = SPA_LOG_TOPIC(0, topic); \ - static struct spa_log_topic *var = &(var##__LINE__) + static struct spa_log_topic *(var) = &(var##__LINE__) /** * Declare a static log topic named \a var. @@ -131,7 +131,7 @@ _pw_log_topic_new(struct spa_log_topic *topic); */ #define PW_LOG_TOPIC(var, topic) \ struct spa_log_topic var##__LINE__ = SPA_LOG_TOPIC(0, topic); \ - struct spa_log_topic *var = &(var##__LINE__) + struct spa_log_topic *(var) = &(var##__LINE__) #define PW_LOG_TOPIC_INIT(var) \ spa_log_topic_init(pw_log_get(), var); diff --git a/src/pipewire/map.h b/src/pipewire/map.h index f2b54fc7cfb9b037f7090854d8d2d023e81c0216..9a46082e235ad315a415cebda1343e3d7b5a6396 100644 --- a/src/pipewire/map.h +++ b/src/pipewire/map.h @@ -85,7 +85,7 @@ struct pw_map { }; /** \param extend the amount of bytes to grow the map with when needed */ -#define PW_MAP_INIT(extend) (struct pw_map) { PW_ARRAY_INIT(extend), SPA_ID_INVALID } +#define PW_MAP_INIT(extend) ((struct pw_map) { PW_ARRAY_INIT(extend), SPA_ID_INVALID }) /** * Get the number of currently allocated elements in the map. 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/permission.h b/src/pipewire/permission.h index e0274ddcbb2c463678e128d3bce324afca5387cf..1bcebc3aa0a3d115c7681aa8a9b03640208dbb11 100644 --- a/src/pipewire/permission.h +++ b/src/pipewire/permission.h @@ -66,7 +66,7 @@ struct pw_permission { uint32_t permissions; /**< bitmask of above permissions */ }; -#define PW_PERMISSION_INIT(id,p) (struct pw_permission){ (id), (p) } +#define PW_PERMISSION_INIT(id,p) ((struct pw_permission){ (id), (p) }) #define PW_PERMISSION_FORMAT "%c%c%c%c" #define PW_PERMISSION_ARGS(permission) \ 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..21be1dbfa87c57c011a363b1990945b606ada57e 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 @@ -177,7 +177,7 @@ static inline struct spa_param_info *pw_param_info_find(struct spa_param_info in return NULL; } -#define pw_protocol_emit_destroy(p) spa_hook_list_call(&p->listener_list, struct pw_protocol_events, destroy, 0) +#define pw_protocol_emit_destroy(p) spa_hook_list_call(&(p)->listener_list, struct pw_protocol_events, destroy, 0) struct pw_protocol { struct spa_list link; /**< link in context protocol_list */ @@ -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; }; @@ -704,6 +704,7 @@ struct pw_impl_node { unsigned int transport_sync:1; /**< supports transport sync */ unsigned int current_pending:1; /**< a quantum/rate update is pending */ unsigned int moved:1; /**< the node was moved drivers */ + unsigned int added:1; /**< the node was add to graph */ uint32_t port_user_data_size; /**< extra size for port user data */ @@ -804,7 +805,7 @@ struct pw_impl_port_implementation { #define pw_impl_port_emit_param_changed(p,i) pw_impl_port_emit(p, param_changed, 1, i) #define pw_impl_port_emit_latency_changed(p) pw_impl_port_emit(p, latency_changed, 2) -#define PW_IMPL_PORT_IS_CONTROL(port) SPA_FLAG_MASK(port->flags, \ +#define PW_IMPL_PORT_IS_CONTROL(port) SPA_FLAG_MASK((port)->flags, \ PW_IMPL_PORT_FLAG_BUFFERS|PW_IMPL_PORT_FLAG_CONTROL,\ PW_IMPL_PORT_FLAG_CONTROL) struct pw_impl_port { @@ -1153,16 +1154,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..9ba438b6a23506ee88705e7c58d0511bcc80af12 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -125,12 +125,12 @@ struct stream { uint32_t port_change_mask_all; struct spa_port_info port_info; struct pw_properties *port_props; -#define IDX_EnumFormat 0 -#define IDX_Meta 1 -#define IDX_IO 2 -#define IDX_Format 3 -#define IDX_Buffers 4 -#define IDX_Latency 5 +#define PORT_EnumFormat 0 +#define PORT_Meta 1 +#define PORT_IO 2 +#define PORT_Format 3 +#define PORT_Buffers 4 +#define PORT_Latency 5 #define N_PORT_PARAMS 6 struct spa_param_info port_params[N_PORT_PARAMS]; @@ -138,9 +138,11 @@ struct stream { uint32_t change_mask_all; struct spa_node_info info; -#define IDX_PropInfo 0 -#define IDX_Props 1 -#define N_NODE_PARAMS 2 +#define NODE_PropInfo 0 +#define NODE_Props 1 +#define NODE_EnumFormat 2 +#define NODE_Format 3 +#define N_NODE_PARAMS 4 struct spa_param_info params[N_NODE_PARAMS]; uint32_t media_type; @@ -179,9 +181,13 @@ static int get_param_index(uint32_t id) { switch (id) { case SPA_PARAM_PropInfo: - return IDX_PropInfo; + return NODE_PropInfo; case SPA_PARAM_Props: - return IDX_Props; + return NODE_Props; + case SPA_PARAM_EnumFormat: + return NODE_EnumFormat; + case SPA_PARAM_Format: + return NODE_Format; default: return -1; } @@ -191,17 +197,17 @@ static int get_port_param_index(uint32_t id) { switch (id) { case SPA_PARAM_EnumFormat: - return IDX_EnumFormat; + return PORT_EnumFormat; case SPA_PARAM_Meta: - return IDX_Meta; + return PORT_Meta; case SPA_PARAM_IO: - return IDX_IO; + return PORT_IO; case SPA_PARAM_Format: - return IDX_Format; + return PORT_Format; case SPA_PARAM_Buffers: - return IDX_Buffers; + return PORT_Buffers; case SPA_PARAM_Latency: - return IDX_Latency; + return PORT_Latency; default: return -1; } @@ -267,7 +273,8 @@ static struct param *add_param(struct stream *impl, impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; impl->params[idx].flags |= SPA_PARAM_INFO_READ; impl->params[idx].user++; - } else if ((idx = get_port_param_index(id)) != -1) { + } + if ((idx = get_port_param_index(id)) != -1) { impl->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; impl->port_params[idx].flags |= SPA_PARAM_INFO_READ; impl->port_params[idx].user++; @@ -313,7 +320,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 +337,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; @@ -387,6 +400,28 @@ static struct buffer *get_buffer(struct pw_stream *stream, uint32_t id) return NULL; } +static inline uint32_t update_requested(struct stream *impl) +{ + uint32_t index, id, res = 0; + struct buffer *buffer; + struct spa_io_rate_match *r = impl->rate_match; + + if (spa_ringbuffer_get_read_index(&impl->dequeued.ring, &index) < 1) + return 0; + + id = impl->dequeued.ids[index & MASK_BUFFERS]; + buffer = &impl->buffers[id]; + if (r) { + buffer->this.requested = r->size; + res = r->size > 0 ? 1 : 0; + } else { + buffer->this.requested = impl->quantum; + res = 1; + } + pw_log_trace_fp("%p: update buffer:%u size:%"PRIu64, impl, id, buffer->this.requested); + return res; +} + static int do_call_process(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) @@ -398,9 +433,11 @@ do_call_process(struct spa_loop *loop, return 0; } -static void call_process(struct stream *impl) +static inline void call_process(struct stream *impl) { pw_log_trace_fp("%p: call process rt:%u", impl, impl->process_rt); + if (impl->direction == SPA_DIRECTION_OUTPUT && update_requested(impl) <= 0) + return; if (impl->process_rt) spa_callbacks_call(&impl->rt_callbacks, struct pw_stream_events, process, 0); else @@ -550,28 +587,6 @@ static int impl_set_param(void *object, uint32_t id, uint32_t flags, const struc return 0; } -static inline uint32_t update_requested(struct stream *impl) -{ - uint32_t index, id, res = 0; - struct buffer *buffer; - struct spa_io_rate_match *r = impl->rate_match; - - if (spa_ringbuffer_get_read_index(&impl->dequeued.ring, &index) < 1) - return 0; - - id = impl->dequeued.ids[index & MASK_BUFFERS]; - buffer = &impl->buffers[id]; - if (r) { - buffer->this.requested = r->size; - res = r->size > 0 ? 1 : 0; - } else { - buffer->this.requested = impl->quantum; - res = 1; - } - pw_log_trace_fp("%p: update buffer:%u size:%u", impl, id, r->size); - return res; -} - static int impl_send_command(void *object, const struct spa_command *command) { struct stream *impl = object; @@ -599,10 +614,8 @@ static int impl_send_command(void *object, const struct spa_command *command) if (impl->direction == SPA_DIRECTION_INPUT) impl->io->status = SPA_STATUS_NEED_DATA; - else if (!impl->process_rt && !impl->driving) { - if (update_requested(impl) > 0) - call_process(impl); - } + else if (!impl->process_rt && !impl->driving) + call_process(impl); stream_set_state(stream, PW_STREAM_STATE_STREAMING, NULL); } @@ -788,7 +801,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); } @@ -861,6 +874,7 @@ static int impl_port_set_param(void *object, if (stream->state == PW_STREAM_STATE_ERROR) return -EIO; + emit_node_info(impl, false); emit_port_info(impl, false); return 0; @@ -927,7 +941,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 +959,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 +998,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 +1007,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 +1027,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 +1067,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 +1080,12 @@ 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 (update_requested(impl) > 0) - call_process(impl); - if (impl->draining || - spa_ringbuffer_get_read_index(&impl->queued.ring, &index) > 0) + if (ask_more) { + call_process(impl); + /* 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 +1176,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 +1203,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 +1223,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 +1253,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 +1268,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 +1283,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 || @@ -1377,7 +1402,7 @@ struct match { struct pw_stream *stream; int count; }; -#define MATCH_INIT(s) (struct match){ .stream = s } +#define MATCH_INIT(s) ((struct match){ .stream = (s) }) static int execute_match(void *data, const char *location, const char *action, const char *val, size_t len) @@ -1817,8 +1842,10 @@ pw_stream_connect(struct pw_stream *stream, if (!impl->process_rt) impl->info.flags |= SPA_NODE_FLAG_ASYNC; impl->info.props = &stream->properties->dict; - impl->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, 0); - impl->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_WRITE); + impl->params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, 0); + impl->params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_WRITE); + impl->params[NODE_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, 0); + impl->params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); impl->info.params = impl->params; impl->info.n_params = N_NODE_PARAMS; impl->info.change_mask = impl->change_mask_all; @@ -1833,12 +1860,12 @@ pw_stream_connect(struct pw_stream *stream, impl->port_info.flags = 0; if (SPA_FLAG_IS_SET(flags, PW_STREAM_FLAG_ALLOC_BUFFERS)) impl->port_info.flags |= SPA_PORT_FLAG_CAN_ALLOC_BUFFERS; - impl->port_params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, 0); - impl->port_params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, 0); - impl->port_params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, 0); - impl->port_params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - impl->port_params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - impl->port_params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_WRITE); + impl->port_params[PORT_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, 0); + impl->port_params[PORT_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, 0); + impl->port_params[PORT_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, 0); + impl->port_params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + impl->port_params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + impl->port_params[PORT_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_WRITE); impl->port_info.props = &impl->port_props->dict; impl->port_info.params = impl->port_params; impl->port_info.n_params = N_PORT_PARAMS; @@ -2223,7 +2250,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 +2261,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 +2281,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 +2302,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); @@ -2324,7 +2351,7 @@ do_trigger_process(struct spa_loop *loop, int res; if (impl->direction == SPA_DIRECTION_OUTPUT) { if (impl->process_rt) - spa_callbacks_call(&impl->rt_callbacks, struct pw_stream_events, process, 0); + call_process(impl); res = impl->node_methods.process(impl); } else { res = SPA_STATUS_NEED_DATA; @@ -2358,11 +2385,9 @@ int pw_stream_trigger_process(struct pw_stream *stream) if (!impl->driving && !impl->trigger) { res = trigger_request_process(impl); } else { - if (impl->direction == SPA_DIRECTION_OUTPUT && - !impl->process_rt) { - pw_loop_invoke(impl->context->main_loop, - do_call_process, 1, NULL, 0, false, impl); - } + if (!impl->process_rt) + call_process(impl); + res = pw_loop_invoke(impl->context->data_loop, do_trigger_process, 1, NULL, 0, false, impl); } 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-loop.c b/src/pipewire/thread-loop.c index 5588b63461f29acc1b60bfeaa0b878b112989cc1..2fdd50e224d309d464d7ea3d3a5bcf2852ee3fe0 100644 --- a/src/pipewire/thread-loop.c +++ b/src/pipewire/thread-loop.c @@ -91,7 +91,7 @@ static void do_stop(void *data, uint64_t count) #define CHECK(expression,label) \ do { \ - if ((errno = expression) != 0) { \ + if ((errno = (expression)) != 0) { \ res = -errno; \ pw_log_error(#expression ": %s", strerror(errno)); \ goto label; \ diff --git a/src/pipewire/thread.c b/src/pipewire/thread.c index f699ebf5bbb43b26ecc573bf72074a4486a049c0..da44716d702b389d321c7e395deb1e26c3558121 100644 --- a/src/pipewire/thread.c +++ b/src/pipewire/thread.c @@ -37,7 +37,7 @@ #define CHECK(expression,label) \ do { \ - if ((errno = expression) != 0) { \ + if ((errno = (expression)) != 0) { \ res = -errno; \ pw_log_error(#expression ": %s", strerror(errno)); \ goto label; \ @@ -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..d8b330570393c3b58cba10c01dd8b63e20c9aca1 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) { - data.current->prompt_pending = pw_core_sync(data.current->core, 0, 0); + data.current->prompt_pending = pw_core_sync(data.current->core, 0, 0); + while (!data.quit && data.current) { pw_main_loop_run(data.loop); + if (!monitor) + break; } } spa_list_consume(rd, &data.remotes, link) diff --git a/src/tools/pw-dot.c b/src/tools/pw-dot.c index 68abc3b95434356bbc3d2947e6cdcb7238785ed8..eed63512a3c57eac57bf87734157e038784e3adf 100644 --- a/src/tools/pw-dot.c +++ b/src/tools/pw-dot.c @@ -514,7 +514,7 @@ static int draw_graph(struct data *d, const char *path) fputs(d->dot_str, stdout); } else { /* open the file */ - fp = fopen(path, "w"); + fp = fopen(path, "we"); if (fp == NULL) { printf("open error: could not open %s for writing\n", path); return -1; diff --git a/src/tools/pw-profiler.c b/src/tools/pw-profiler.c index f2000a9860c8342d1cc83f379881125acbe019db..00dc4104a0c2464a3f718341eeed29a34584fa1f 100644 --- a/src/tools/pw-profiler.c +++ b/src/tools/pw-profiler.c @@ -262,7 +262,7 @@ static void dump_scripts(struct data *d) printf("\ndumping scripts for %d followers\n", d->n_followers); - out = fopen("Timing1.plot", "w"); + out = fopen("Timing1.plot", "we"); if (out == NULL) { pw_log_error("Can't open Timing1.plot: %m"); } else { @@ -282,7 +282,7 @@ static void dump_scripts(struct data *d) fclose(out); } - out = fopen("Timing2.plot", "w"); + out = fopen("Timing2.plot", "we"); if (out == NULL) { pw_log_error("Can't open Timing2.plot: %m"); } else { @@ -298,7 +298,7 @@ static void dump_scripts(struct data *d) fclose(out); } - out = fopen("Timing3.plot", "w"); + out = fopen("Timing3.plot", "we"); if (out == NULL) { pw_log_error("Can't open Timing3.plot: %m"); } else { @@ -328,7 +328,7 @@ static void dump_scripts(struct data *d) fclose(out); } - out = fopen("Timing4.plot", "w"); + out = fopen("Timing4.plot", "we"); if (out == NULL) { pw_log_error("Can't open Timing4.plot: %m"); } else { @@ -355,7 +355,7 @@ static void dump_scripts(struct data *d) fclose(out); } - out = fopen("Timing5.plot", "w"); + out = fopen("Timing5.plot", "we"); if (out == NULL) { pw_log_error("Can't open Timing5.plot: %m"); } else { @@ -381,7 +381,7 @@ static void dump_scripts(struct data *d) "unset output\n"); fclose(out); } - out = fopen("Timings.html", "w"); + out = fopen("Timings.html", "we"); if (out == NULL) { pw_log_error("Can't open Timings.html: %m"); } else { @@ -409,7 +409,7 @@ static void dump_scripts(struct data *d) fclose(out); } - out = fopen("generate_timings.sh", "w"); + out = fopen("generate_timings.sh", "we"); if (out == NULL) { pw_log_error("Can't open generate_timings.sh: %m"); } else { @@ -624,7 +624,7 @@ int main(int argc, char *argv[]) data.filename = opt_output; - data.output = fopen(data.filename, "w"); + data.output = fopen(data.filename, "we"); if (data.output == NULL) { fprintf(stderr, "Can't open file %s: %m\n", data.filename); return -1; diff --git a/src/tools/pw-top.c b/src/tools/pw-top.c index 3fb25972702dc455702075043d297ba6575ce24b..459dacb11fcf5e774d25b64485b8cfad7713cbaa 100644 --- a/src/tools/pw-top.c +++ b/src/tools/pw-top.c @@ -32,10 +32,14 @@ #include <spa/utils/string.h> #include <spa/pod/parser.h> #include <spa/debug/pod.h> +#include <spa/param/format-utils.h> +#include <spa/param/audio/format-utils.h> +#include <spa/param/video/format-utils.h> #include <pipewire/impl.h> #include <pipewire/extensions/profiler.h> +#define MAX_FORMAT 16 #define MAX_NAME 128 struct driver { @@ -59,13 +63,17 @@ struct measurement { struct node { struct spa_list link; uint32_t id; - char name[MAX_NAME]; + char name[MAX_NAME+1]; struct measurement measurement; struct driver info; struct node *driver; uint32_t errors; int32_t last_error_status; uint32_t generation; + char format[MAX_FORMAT+1]; + struct pw_proxy *proxy; + struct spa_hook proxy_listener; + struct spa_hook object_listener; }; struct data { @@ -131,6 +139,98 @@ static struct node *find_node(struct data *d, uint32_t id) return NULL; } +static void on_node_removed(void *data) +{ + struct node *n = data; + pw_proxy_destroy(n->proxy); +} + +static void on_node_destroy(void *data) +{ + struct node *n = data; + n->proxy = NULL; + spa_hook_remove(&n->proxy_listener); + spa_hook_remove(&n->object_listener); +} + +static const struct pw_proxy_events proxy_events = { + PW_VERSION_PROXY_EVENTS, + .removed = on_node_removed, + .destroy = on_node_destroy, +}; + +static void node_param(void *data, int seq, + uint32_t id, uint32_t index, uint32_t next, + const struct spa_pod *param) +{ + struct node *n = data; + + switch (id) { + case SPA_PARAM_Format: + { + uint32_t media_type, media_subtype; + + spa_format_parse(param, &media_type, &media_subtype); + + switch(media_type) { + case SPA_MEDIA_TYPE_audio: + switch(media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + { + struct spa_audio_info_raw info; + if (spa_format_audio_raw_parse(param, &info) >= 0) { + snprintf(n->format, sizeof(n->format), "%6.6s %d %d", + spa_debug_type_find_short_name(spa_type_audio_format, info.format), + info.channels, info.rate); + } + break; + } + case SPA_MEDIA_SUBTYPE_dsd: + { + struct spa_audio_info_dsd info; + if (spa_format_audio_dsd_parse(param, &info) >= 0) { + snprintf(n->format, sizeof(n->format), "DSD%d %d ", + 8 * info.rate / 44100, info.channels); + + } + break; + } + } + break; + case SPA_MEDIA_TYPE_video: + switch(media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + { + struct spa_video_info_raw info; + if (spa_format_video_raw_parse(param, &info) >= 0) { + snprintf(n->format, sizeof(n->format), "%6.6s %dx%d", + spa_debug_type_find_short_name(spa_type_video_format, info.format), + info.size.width, info.size.height); + } + break; + } + } + break; + case SPA_MEDIA_TYPE_application: + switch(media_subtype) { + case SPA_MEDIA_SUBTYPE_control: + snprintf(n->format, sizeof(n->format), "%s", "CONTROL"); + break; + } + break; + } + break; + } + default: + break; + } +} + +static const struct pw_node_events node_events = { + PW_VERSION_NODE, + .param = node_param, +}; + static struct node *add_node(struct data *d, uint32_t id, const char *name) { struct node *n; @@ -139,11 +239,23 @@ static struct node *add_node(struct data *d, uint32_t id, const char *name) return NULL; if (name) - strncpy(n->name, name, MAX_NAME-1); + strncpy(n->name, name, MAX_NAME); else snprintf(n->name, sizeof(n->name), "%u", id); n->id = id; n->driver = n; + n->proxy = pw_registry_bind(d->registry, id, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0); + if (n->proxy) { + uint32_t ids[1] = { SPA_PARAM_Format }; + + pw_proxy_add_listener(n->proxy, + &n->proxy_listener, &proxy_events, n); + pw_proxy_add_object_listener(n->proxy, + &n->object_listener, &node_events, n); + + pw_node_subscribe_params((struct pw_node*)n->proxy, + ids, 1); + } spa_list_append(&d->node_list, &n->link); d->n_nodes++; @@ -152,6 +264,8 @@ static struct node *add_node(struct data *d, uint32_t id, const char *name) static void remove_node(struct data *d, struct node *n) { + if (n->proxy) + pw_proxy_destroy(n->proxy); spa_list_remove(&n->link); d->n_nodes--; free(n); @@ -235,7 +349,7 @@ static const char *print_time(char *buf, size_t len, uint64_t val) else if (val == (uint64_t)-2) snprintf(buf, len, " +++ "); else if (val < 1000000llu) - snprintf(buf, len, "%5.1fµs", val/1000.f); + snprintf(buf, len, "%5.1fus", val/1000.f); else if (val < 1000000000llu) snprintf(buf, len, "%5.1fms", val/1000000.f); else @@ -290,7 +404,7 @@ static void print_node(struct data *d, struct driver *i, struct node *n, int y) else busy = -1; - mvwprintw(d->win, y, 0, "%s %4.1u %6.1u %6.1u %s %s %s %s %3.1u %s%s", + mvwprintw(d->win, y, 0, "%s %4.1u %6.1u %6.1u %s %s %s %s %3.1u %16.16s %s%s", n->measurement.status != 3 ? "!" : " ", n->id, frac.num, frac.denom, @@ -299,6 +413,7 @@ static void print_node(struct data *d, struct driver *i, struct node *n, int y) print_perc(buf3, 64, waiting, quantum), print_perc(buf4, 64, busy, quantum), i->xrun_count + n->errors, + n->measurement.status != 3 ? "" : n->format, n->driver == n ? "" : " + ", n->name); } @@ -310,7 +425,7 @@ static void do_refresh(struct data *d) wclear(d->win); wattron(d->win, A_REVERSE); - wprintw(d->win, "%-*.*s", COLS, COLS, "S ID QUANT RATE WAIT BUSY W/Q B/Q ERR NAME "); + wprintw(d->win, "%-*.*s", COLS, COLS, "S ID QUANT RATE WAIT BUSY W/Q B/Q ERR FORMAT NAME "); wattroff(d->win, A_REVERSE); wprintw(d->win, "\n"); @@ -327,6 +442,7 @@ static void do_refresh(struct data *d) f->driver = f; spa_zero(f->measurement); spa_zero(f->info); + spa_zero(f->format); f->errors = 0; f->last_error_status = 0; } diff --git a/test/pwtest.c b/test/pwtest.c index 18be5c6ad5a38679eb2965fbce6d045443b1395e..2d668184b849bf157c4e83ef32c603b3b0ae5c60 100644 --- a/test/pwtest.c +++ b/test/pwtest.c @@ -835,7 +835,7 @@ static int init_pipes(int read_fds[_FD_LAST], int write_fds[_FD_LAST]) #ifdef __linux__ { FILE *f; - f = fopen("/proc/sys/fs/pipe-max-size", "r"); + f = fopen("/proc/sys/fs/pipe-max-size", "re"); if (f) { if (fscanf(f, "%d", &r) == 1) pipe_max_size = SPA_MIN(r, pipe_max_size); @@ -1244,7 +1244,7 @@ static char* make_xdg_runtime_dir(void) /* Marker file to avoid removing a random directory during cleanup */ r = spa_scnprintf(path, sizeof(path), "%s/pwtest.dir", dir); spa_assert_se((size_t)r == strlen(dir) + 11); - fp = fopen(path, "w"); + fp = fopen(path, "we"); spa_assert_se(fp); fprintf(fp, "pwtest\n"); fclose(fp); diff --git a/test/test-config.c b/test/test-config.c index d1397bafbc66d9c5a1a8c676a2f854fed2adfa31..9ebbc26d17225470446ffcfc32074f81e24bfce6 100644 --- a/test/test-config.c +++ b/test/test-config.c @@ -35,7 +35,7 @@ PWTEST(config_load_abspath) char *basename; pwtest_mkstemp(path); - fp = fopen(path, "w"); + fp = fopen(path, "we"); fputs("data = x", fp); fclose(fp); diff --git a/test/test-logger.c b/test/test-logger.c index a1b0ff01dd466c0e35be379262c982016a14927d..5aca7d1998448472ebc274bfa473517069601136 100644 --- a/test/test-logger.c +++ b/test/test-logger.c @@ -63,7 +63,7 @@ PWTEST(logger_truncate_long_lines) /* Print a line expected to be truncated */ spa_log_error(iface, "MARK: %1100s", "foo"); - fp = fopen(fname, "r"); + fp = fopen(fname, "re"); while (fgets(buffer, sizeof(buffer), fp) != NULL) { if (strstr(buffer, "MARK:")) { const char *suffix = ".. (truncated)\n"; @@ -110,7 +110,7 @@ PWTEST(logger_no_ansi) * tty so expect none despite colors being enabled */ spa_log_error(iface, "MARK\n"); - fp = fopen(fname, "r"); + fp = fopen(fname, "re"); while (fgets(buffer, sizeof(buffer), fp) != NULL) { if (strstr(buffer, "MARK")) { mark_line_found = true; @@ -157,7 +157,7 @@ test_log_levels(enum spa_log_level level) if (level < SPA_LOG_LEVEL_TRACE) pw_log(level + 1, "ABOVE"); - fp = fopen(fname, "r"); + fp = fopen(fname, "re"); while (fgets(buffer, sizeof(buffer), fp) != NULL) { if (strstr(buffer, "CURRENT")) current_level_found = true; @@ -427,7 +427,7 @@ PWTEST(logger_topics) spa_logt_info(iface, &topic, "MARK\n"); - fp = fopen(fname, "r"); + fp = fopen(fname, "re"); while (fgets(buffer, sizeof(buffer), fp) != NULL) { if (strstr(buffer, "MARK")) { mark_line_found = true; @@ -602,7 +602,7 @@ PWTEST(logger_journal_chain) /* Now check that the line is in the chained file logger too */ spa_memzero(buffer, sizeof(buffer)); mark_line_found = false; - fp = fopen(fname, "r"); + fp = fopen(fname, "re"); while (fgets(buffer, sizeof(buffer), fp) != NULL) { if (strstr(buffer, token)) { mark_line_found = true; diff --git a/test/test-properties.c b/test/test-properties.c index 0a4d38754ba07204238ccfcdd96b6a58afc8d4cd..89e66bb370a7fe70e151948fabba6dc07f82b5bd 100644 --- a/test/test-properties.c +++ b/test/test-properties.c @@ -484,7 +484,7 @@ PWTEST(properties_serialize_dict_stack_overflow) dict = SPA_DICT_INIT(items, 2); pwtest_mkstemp(tmpfile); - fp = fopen(tmpfile, "w"); + fp = fopen(tmpfile, "we"); pwtest_ptr_notnull(fp); r = pw_properties_serialize_dict(fp, &dict, 0); pwtest_int_eq(r, 1); 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-pod.c b/test/test-spa-pod.c index 989d3dc5dd9dab3606a9aef7632a6b2a2bfbf705..24c3303cb07b78ed623e5b3a23192b2daf5df5c1 100644 --- a/test/test-spa-pod.c +++ b/test/test-spa-pod.c @@ -1639,6 +1639,54 @@ PWTEST(pod_overflow) return PWTEST_PASS; } +static int handle_overflow(void *data, uint32_t size) +{ + uint32_t *d = data; + (*d)++; + return -ENOSPC; +} + +static struct spa_pod_builder_callbacks overflow_cb = { + SPA_VERSION_POD_BUILDER_CALLBACKS, + .overflow = handle_overflow +}; + +PWTEST(pod_overflow2) +{ + uint8_t buffer[1024]; + struct spa_pod_builder b = { 0 }; + struct spa_pod_builder_state state; + struct spa_pod_frame f[2]; + uint32_t idx, overflow_count = 0; + struct spa_pod *pod; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + spa_pod_builder_set_callbacks(&b, &overflow_cb, &overflow_count); + + spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo); + spa_pod_builder_add(&b, + SPA_PROP_INFO_id, SPA_POD_Id(32567359), + SPA_PROP_INFO_type, SPA_POD_CHOICE_ENUM_Int(1, 0), + SPA_PROP_INFO_description, SPA_POD_String("DV Timings"), + 0); + + spa_pod_builder_get_state(&b, &state), + + spa_pod_builder_prop(&b, SPA_PROP_INFO_labels, 0); + spa_pod_builder_push_struct(&b, &f[1]); + + for (idx = 0; idx < 512; idx++) { + spa_pod_builder_int(&b, idx); + spa_pod_builder_string(&b, "foo"); + } + spa_assert_se(b.state.offset > sizeof(buffer)); + pod = spa_pod_builder_pop(&b, &f[1]); + spa_assert_se(pod == NULL); + spa_assert_se(overflow_count == 1); + + return PWTEST_PASS; +} + PWTEST_SUITE(spa_pod) { pwtest_add(pod_abi_sizes, PWTEST_NOARG); @@ -1652,6 +1700,7 @@ PWTEST_SUITE(spa_pod) pwtest_add(pod_parser2, PWTEST_NOARG); pwtest_add(pod_static, PWTEST_NOARG); pwtest_add(pod_overflow, PWTEST_NOARG); + pwtest_add(pod_overflow2, 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; }